5
5
<template >
6
6
<div ref =" taskTypeSelect"
7
7
class =" task-type-select" >
8
- <NcButton v-for =" (t, i) in buttonTypes"
9
- :key =" i + t.id"
10
- :variant =" getButtonType(t)"
11
- :title =" t.description"
12
- @click =" onTaskSelected(t)" >
13
- {{ t.name }}
14
- </NcButton >
15
- <NcActions v-if =" !onlyInline && actionTypes.length > 0"
16
- :force-menu =" true"
17
- :container =" $refs.taskTypeSelect" >
18
- <NcActionButton v-for =" (t, i) in actionTypes"
19
- :key =" i + t.id"
20
- class =" no-icon-action"
21
- :aria-label =" t.name"
22
- :close-after-click =" true"
23
- @click =" onMenuTaskSelected(t)" >
8
+ <template v-for =" variants in buttonTypesByInlineStatus .inline " >
9
+ <NcActions v-if =" hasSubMenu(variants)"
10
+ :key =" variants.id"
11
+ :force-menu =" true"
12
+ :menu-name =" variants.text"
13
+ :container =" $refs.taskTypeSelect"
14
+ :primary =" isCategorySelected(variants)"
15
+ @click =" onMenuCategorySelected(variants)" >
16
+ <NcActionButton v-for =" t in variants.tasks"
17
+ :key =" t.id"
18
+ :disabled =" selectedTask(t)"
19
+ :title =" t.description"
20
+ :close-after-click =" true"
21
+ @click =" onTaskSelected(t)" >
22
+ <template #icon >
23
+ <div style =" width : 16px " />
24
+ </template >
25
+ {{ t.name }}
26
+ </NcActionButton >
27
+ <template #icon >
28
+ <component :is =" variants.icon" />
29
+ </template >
30
+ </NcActions >
31
+ <NcButton v-else
32
+ :key =" variants.id + '-button'"
33
+ :variant =" isCategorySelected(variants) ? 'primary' : 'secondary'"
34
+ :title =" variants.text"
35
+ @click =" onMenuCategorySelected(variants)" >
24
36
<template #icon >
25
- <div style = " width : 16 px " />
37
+ <component :is = " variants.icon " />
26
38
</template >
27
- {{ t.name }}
28
- </NcActionButton >
39
+ {{ variants.text }}
40
+ </NcButton >
41
+ </template >
42
+ <NcActions
43
+ :force-menu =" true"
44
+ :container =" $refs.taskTypeSelect"
45
+ @close =" categorySubmenu = null" >
46
+ <template v-if =" ! categorySubMenuTaskType " >
47
+ <NcActionButton v-for =" variant in buttonTypesByInlineStatus.overflow"
48
+ :key =" variant.id"
49
+ :is-menu =" variant.tasks.length > 1 || variant.id === 'other'"
50
+ :title =" variant.text"
51
+ @click =" onMenuCategorySelected(variant)" >
52
+ <template #icon >
53
+ <component :is =" variant.icon" />
54
+ </template >
55
+ {{ variant.text }}
56
+ </NcActionButton >
57
+ </template >
58
+ <template v-else >
59
+ <NcActionButton v-for =" t in categorySubMenuTaskType.tasks"
60
+ :key =" t.id"
61
+ :disabled =" selectedTask(t)"
62
+ :title =" t.description"
63
+ :close-after-click =" true"
64
+ @click =" onTaskSelected(t)" >
65
+ <template #icon >
66
+ <div style =" width : 16px " />
67
+ </template >
68
+ {{ t.name }}
69
+ </NcActionButton >
70
+ </template >
29
71
</NcActions >
30
72
</div >
31
73
</template >
32
74
33
75
<script >
34
- import NcButton from ' @nextcloud/vue/components/NcButton'
35
76
import NcActions from ' @nextcloud/vue/components/NcActions'
77
+ import NcButton from ' @nextcloud/vue/components/NcButton'
36
78
import NcActionButton from ' @nextcloud/vue/components/NcActionButton'
79
+ import MessageOutlineIcon from ' vue-material-design-icons/MessageOutline.vue'
80
+ import DotsHorizontalIcon from ' vue-material-design-icons/DotsHorizontal.vue'
81
+ import TextLongIcon from ' vue-material-design-icons/TextLong.vue'
82
+ import ImageOutlineIcon from ' vue-material-design-icons/ImageOutline.vue'
83
+ import WebIcon from ' vue-material-design-icons/Web.vue'
84
+ import FileIcon from ' vue-material-design-icons/File.vue'
85
+ import ContentPasteSearchIcon from ' ./icons/ContentPasteSearch.vue'
86
+ import WaveformIcon from ' ./icons/Waveform.vue'
37
87
38
88
export default {
39
89
name: ' TaskTypeSelect' ,
40
90
41
91
components: {
42
- NcButton,
43
92
NcActions,
44
93
NcActionButton,
94
+ MessageOutlineIcon,
95
+ NcButton,
45
96
},
46
97
47
98
props: {
@@ -69,7 +120,7 @@ export default {
69
120
70
121
data () {
71
122
return {
72
- extraButtonType : null ,
123
+ categorySubmenu : null ,
73
124
}
74
125
},
75
126
@@ -78,66 +129,142 @@ export default {
78
129
return this .inline === null
79
130
},
80
131
buttonTypes () {
81
- if (this .onlyInline ) {
82
- return this .options
132
+ const taskTypes = {}
133
+ for (const task of this .options ) {
134
+ const type = this .getTaskCategory (task .id )
135
+ if (! taskTypes[type]) {
136
+ taskTypes[type] = []
137
+ }
138
+ taskTypes[type].push (task)
83
139
}
84
- // extra button replaces the last one
85
- if (this .extraButtonType !== null ) {
86
- const types = this .options .slice (0 , this .inline - 1 )
87
- types .push (this .extraButtonType )
88
- return types
89
- } else {
90
- return this .options .slice (0 , this .inline )
140
+ const result = []
141
+ for (const entry of Object .entries (taskTypes)) {
142
+ if (entry[0 ] === ' other' ) {
143
+ continue
144
+ }
145
+ result .push ({
146
+ id: entry[0 ],
147
+ text: this .getTextForCategory (entry[0 ]),
148
+ icon: this .getCategoryIcon (entry[0 ]),
149
+ tasks: entry[1 ],
150
+ })
151
+ }
152
+ // Ensure the "other" category is always last
153
+ if (taskTypes .other ) {
154
+ result .push ({
155
+ id: ' other' ,
156
+ text: this .getTextForCategory (' other' ),
157
+ icon: this .getCategoryIcon (' other' ),
158
+ tasks: taskTypes .other ,
159
+ })
91
160
}
161
+ return result
92
162
},
93
- actionTypes () {
94
- if (this .extraButtonType !== null ) {
95
- // the extra button replaces the last one so we need the last one as an action
96
- // take all non-inline options that are not selected and that are not the extra button
97
- const types = this .options .slice (this .inline ).filter (t => t .id !== this .modelValue && t .id !== this .extraButtonType .id )
98
- // add the one that was a button and that has been replaced
99
- if (this .extraButtonType .id !== this .options [this .inline - 1 ].id ) {
100
- types .unshift (this .options [this .inline - 1 ])
163
+ buttonTypesByInlineStatus () {
164
+ if (this .onlyInline ) {
165
+ return { inline: this .buttonTypes , overflow: [] }
166
+ }
167
+ const inlineButtonTypes = this .buttonTypes .slice (0 , this .inline )
168
+ let overflowButtonTypes = this .buttonTypes .slice (this .inline )
169
+
170
+ // Ensure that the selection is never inline otherwise swap with the last uninlined category
171
+ const selection = overflowButtonTypes .find (t => this .isCategorySelected (t))
172
+ if (selection) {
173
+ const removal = inlineButtonTypes .pop ()
174
+ inlineButtonTypes .push (selection)
175
+ overflowButtonTypes = overflowButtonTypes .filter (t => t .id !== selection .id )
176
+ if (removal) {
177
+ overflowButtonTypes .unshift (removal)
101
178
}
102
- return types
103
- } else {
104
- return this .options .slice (this .inline )
105
179
}
180
+ return { overflow: overflowButtonTypes, inline: inlineButtonTypes }
106
181
},
107
- },
108
-
109
- watch: {
110
- options () {
111
- this .moveSelectedIfInMenu ()
182
+ categorySubMenuTaskType () {
183
+ return this .buttonTypesByInlineStatus .overflow .find (t => t .id === this .categorySubmenu )
112
184
},
113
185
},
114
186
115
187
mounted () {
116
- this .moveSelectedIfInMenu ()
117
188
},
118
189
119
190
methods: {
120
- moveSelectedIfInMenu () {
121
- if (this .onlyInline ) {
122
- return
123
- }
124
- // if the initially selected value is in the dropdown, get it out
125
- const selectedAction = this .actionTypes .find (a => a .id === this .modelValue )
126
- if (this .actionTypes .find (a => a .id === this .modelValue )) {
127
- this .extraButtonType = selectedAction
128
- }
129
- },
130
- getButtonType (taskType ) {
191
+ selectedTask (taskType ) {
131
192
return taskType .id === this .modelValue
132
- ? ' primary'
133
- : ' secondary'
193
+ },
194
+ isCategorySelected (category ) {
195
+ return category .id === this .getTaskCategory (this .modelValue || ' ' )
134
196
},
135
197
onTaskSelected (taskType ) {
136
198
this .$emit (' update:model-value' , taskType .id )
137
199
},
138
- onMenuTaskSelected (taskType ) {
139
- this .extraButtonType = taskType
140
- this .onTaskSelected (taskType)
200
+ hasSubMenu (taskType ) {
201
+ return taskType .tasks .length > 1 || taskType .id === ' other'
202
+ },
203
+ onMenuCategorySelected (taskType ) {
204
+ if (this .hasSubMenu (taskType)) {
205
+ this .categorySubmenu = taskType .id
206
+ } else {
207
+ this .onTaskSelected (taskType .tasks [0 ])
208
+ this .categorySubmenu = null
209
+ }
210
+ },
211
+ getTaskCategory (id ) {
212
+ if (id .startsWith (' chatty' )) {
213
+ return ' chat'
214
+ } else if (id .startsWith (' context_chat' )) {
215
+ return ' context'
216
+ } else if (id .includes (' translate' )) {
217
+ return ' translate'
218
+ } else if (id .startsWith (' richdocuments' )) {
219
+ return ' generate'
220
+ } else if (id .includes (' image' )) {
221
+ return ' image'
222
+ } else if (id .includes (' audio' ) || id .includes (' speech' )) {
223
+ return ' audio'
224
+ } else if (id .includes (' text' )) {
225
+ return ' text'
226
+ }
227
+ return ' other'
228
+ },
229
+ getTextForCategory (category ) {
230
+ switch (category) {
231
+ case ' chat' :
232
+ return t (' assistant' , ' Chat with AI' )
233
+ case ' context' :
234
+ return t (' assistant' , ' Context Chat' )
235
+ case ' text' :
236
+ return t (' assistant' , ' Work with text' )
237
+ case ' image' :
238
+ return t (' assistant' , ' Work with images' )
239
+ case ' translate' :
240
+ return t (' assistant' , ' Translate' )
241
+ case ' audio' :
242
+ return t (' assistant' , ' Work with audio' )
243
+ case ' generate' :
244
+ return t (' assistant' , ' Generate file' )
245
+ default :
246
+ return t (' assistant' , ' Other' )
247
+ }
248
+ },
249
+ getCategoryIcon (category ) {
250
+ switch (category) {
251
+ case ' chat' :
252
+ return MessageOutlineIcon
253
+ case ' context' :
254
+ return ContentPasteSearchIcon
255
+ case ' text' :
256
+ return TextLongIcon
257
+ case ' image' :
258
+ return ImageOutlineIcon
259
+ case ' translate' :
260
+ return WebIcon
261
+ case ' audio' :
262
+ return WaveformIcon
263
+ case ' generate' :
264
+ return FileIcon
265
+ default :
266
+ return DotsHorizontalIcon
267
+ }
141
268
},
142
269
},
143
270
}
0 commit comments