@opentiny/tiny-robot-cli 0.5.0 → 0.5.2-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,7 @@ A lightweight CLI for scaffolding TinyRobot-based product projects.
7
7
  ```bash
8
8
  npx @opentiny/tiny-robot-cli create my-app
9
9
  pnpm dlx @opentiny/tiny-robot-cli create my-app
10
+ npx @opentiny/tiny-robot-cli add chat
10
11
  ```
11
12
 
12
13
  ## Options
@@ -14,6 +15,15 @@ pnpm dlx @opentiny/tiny-robot-cli create my-app
14
15
  - `-t, --template <name>`: template name, currently supports `basic`
15
16
  - `-h, --help`: show help
16
17
 
18
+ `add chat` copies the complete Chat example from the `chat-add` template, including its runtime, UI configuration, composables, components, and styles. It updates these dependencies to `0.5.2-alpha.10`:
19
+
20
+ - `@opentiny/tiny-robot`
21
+ - `@opentiny/tiny-robot-chat`
22
+ - `@opentiny/tiny-robot-kit`
23
+ - `@opentiny/tiny-robot-svgs`
24
+
25
+ Configure the generated `.env` file with the provider API URL and API key before starting the project.
26
+
17
27
  ## Template Documentation
18
28
 
19
29
  Template-specific features and environment variables are documented in each template directory, for example:
@@ -7,6 +7,7 @@ import semver from 'semver'
7
7
 
8
8
  import {
9
9
  copyFile,
10
+ copyDirectory,
10
11
  findProjectRoot,
11
12
  findSubPackageRoot,
12
13
  findWorkspacePackages,
@@ -19,9 +20,17 @@ import {
19
20
  mergeEnvFile,
20
21
  } from '../utils.js'
21
22
 
22
- const DEP_NAME = '@opentiny/tiny-robot'
23
- const TARGET_VERSION = '0.5.1-alpha.2'
24
- const STYLE_IMPORT = "import '@opentiny/tiny-robot/dist/style.css'"
23
+ const TARGET_VERSION = '0.5.2-alpha.10'
24
+ const DEPENDENCIES = {
25
+ '@opentiny/tiny-robot': TARGET_VERSION,
26
+ '@opentiny/tiny-robot-chat': TARGET_VERSION,
27
+ '@opentiny/tiny-robot-kit': TARGET_VERSION,
28
+ '@opentiny/tiny-robot-svgs': TARGET_VERSION,
29
+ }
30
+ const STYLE_IMPORTS = [
31
+ "import '@opentiny/tiny-robot/dist/style.css'",
32
+ "import '@opentiny/tiny-robot-chat/dist/style.css'",
33
+ ]
25
34
 
26
35
  function logUnavailable(label) {
27
36
  logSkip(`${label} could not be applied`)
@@ -93,38 +102,42 @@ function findMainEntry(targetDir) {
93
102
 
94
103
  function ensureStyleImport(mainFile) {
95
104
  const content = fs.readFileSync(mainFile, 'utf-8')
105
+ const lines = content.split('\n')
106
+ let inserted = false
96
107
 
97
- if (content.includes(STYLE_IMPORT)) {
98
- return {
99
- type: 'skipped',
108
+ for (const styleImport of STYLE_IMPORTS) {
109
+ if (lines.some((line) => line.trim() === styleImport)) {
110
+ continue
100
111
  }
101
- }
102
112
 
103
- const lines = content.split('\n')
113
+ let lastImportIndex = -1
104
114
 
105
- let lastImportIndex = -1
115
+ for (let i = 0; i < lines.length; i++) {
116
+ if (/^\s*import\s/.test(lines[i])) {
117
+ lastImportIndex = i
118
+ continue
119
+ }
106
120
 
107
- for (let i = 0; i < lines.length; i++) {
108
- if (/^\s*import\s/.test(lines[i])) {
109
- lastImportIndex = i
110
- continue
121
+ if (lastImportIndex !== -1) {
122
+ break
123
+ }
111
124
  }
112
125
 
113
- if (lastImportIndex !== -1) {
114
- break
126
+ if (lastImportIndex === -1) {
127
+ lines.unshift(styleImport)
128
+ } else {
129
+ lines.splice(lastImportIndex + 1, 0, styleImport)
115
130
  }
116
- }
117
131
 
118
- if (lastImportIndex === -1) {
119
- lines.unshift(STYLE_IMPORT)
120
- } else {
121
- lines.splice(lastImportIndex + 1, 0, STYLE_IMPORT)
132
+ inserted = true
122
133
  }
123
134
 
124
- fs.writeFileSync(mainFile, lines.join('\n'))
135
+ if (inserted) {
136
+ fs.writeFileSync(mainFile, lines.join('\n'))
137
+ }
125
138
 
126
139
  return {
127
- type: 'inserted',
140
+ type: inserted ? 'inserted' : 'skipped',
128
141
  }
129
142
  }
130
143
 
@@ -186,7 +199,7 @@ function ensureDependency(pkg, name, targetVersion) {
186
199
  function printDependencyResult(result, name) {
187
200
  switch (result.type) {
188
201
  case 'added':
189
- logSuccess(`Added ${name}@${TARGET_VERSION}`)
202
+ logSuccess(`Added ${name}@${result.to}`)
190
203
  break
191
204
 
192
205
  case 'updated':
@@ -201,6 +214,7 @@ function printDependencyResult(result, name) {
201
214
 
202
215
  function getChatFeatureFiles(targetDir) {
203
216
  const mainEntry = findMainEntry(targetDir)
217
+ const templateSource = getTemplateFile('src')
204
218
 
205
219
  return [
206
220
  {
@@ -212,12 +226,44 @@ function getChatFeatureFiles(targetDir) {
212
226
  },
213
227
  },
214
228
  {
215
- label: 'main entry style import',
229
+ label: 'main entry style imports',
216
230
  target: mainEntry,
217
231
  action() {
218
232
  return 'modify'
219
233
  },
220
234
  },
235
+ {
236
+ label: 'Chat UI components',
237
+ target: path.join(targetDir, 'src/components'),
238
+ template: path.join(templateSource, 'components'),
239
+ action() {
240
+ return 'merge'
241
+ },
242
+ },
243
+ {
244
+ label: 'Chat composables',
245
+ target: path.join(targetDir, 'src/composables'),
246
+ template: path.join(templateSource, 'composables'),
247
+ action() {
248
+ return 'merge'
249
+ },
250
+ },
251
+ {
252
+ label: 'Chat configuration',
253
+ target: path.join(targetDir, 'src/config'),
254
+ template: path.join(templateSource, 'config'),
255
+ action() {
256
+ return 'merge'
257
+ },
258
+ },
259
+ {
260
+ label: 'Chat app styles',
261
+ target: path.join(targetDir, 'src/index.css'),
262
+ template: path.join(templateSource, 'index.css'),
263
+ action(fileExists) {
264
+ return fileExists ? 'overwrite' : 'create'
265
+ },
266
+ },
221
267
  {
222
268
  label: '.env',
223
269
  target: path.join(targetDir, '.env'),
@@ -247,7 +293,11 @@ async function selectFileChanges(files) {
247
293
 
248
294
  const descriptions = {
249
295
  'TinyRobotChat.vue': 'integrate TinyRobot chat component',
250
- 'main entry style import': 'import TinyRobot styles',
296
+ 'main entry style imports': 'import TinyRobot styles',
297
+ 'Chat UI components': 'copy Chat UI components',
298
+ 'Chat composables': 'copy Chat composables',
299
+ 'Chat configuration': 'copy Chat runtime and UI configuration',
300
+ 'Chat app styles': 'copy Chat application styles',
251
301
  '.env': 'add environment variables',
252
302
  'package.json': 'add TinyRobot dependencies',
253
303
  }
@@ -310,14 +360,29 @@ async function addFeature(targetDir, type) {
310
360
  logSkippedSelection(componentFile.label)
311
361
  }
312
362
 
363
+ for (const file of files.filter((item) => item.label.startsWith('Chat '))) {
364
+ if (!isSelected(selectedFiles, file.label)) {
365
+ logSkippedSelection(file.label)
366
+ continue
367
+ }
368
+
369
+ if (file.target.endsWith('.css')) {
370
+ copyFile(file.template, file.target)
371
+ } else {
372
+ copyDirectory(file.template, file.target)
373
+ }
374
+
375
+ logSuccess(`Copied ${file.label}`)
376
+ }
377
+
313
378
  const mainFile = files.find((f) => {
314
- return f.label === 'main entry style import'
379
+ return f.label === 'main entry style imports'
315
380
  })
316
381
 
317
382
  if (!mainFile?.target) {
318
383
  needsManualStyleImport = true
319
384
 
320
- logUnavailable('main entry style import (main.ts/js not found)')
385
+ logUnavailable('main entry style imports (main.ts/js not found)')
321
386
  } else {
322
387
  if (isSelected(selectedFiles, mainFile.label)) {
323
388
  const result = ensureStyleImport(mainFile.target)
@@ -369,15 +434,17 @@ async function addFeature(targetDir, type) {
369
434
  const pkg = readPackageJson(pkgPath)
370
435
 
371
436
  if (isSelected(selectedFiles, 'package.json')) {
372
- const result = ensureDependency(pkg, DEP_NAME, TARGET_VERSION)
437
+ for (const [name, version] of Object.entries(DEPENDENCIES)) {
438
+ const result = ensureDependency(pkg, name, version)
439
+
440
+ if (result.type !== 'skipped') {
441
+ dependencyChanged = true
442
+ }
373
443
 
374
- if (result.type !== 'skipped') {
375
- dependencyChanged = true
444
+ printDependencyResult(result, name)
376
445
  }
377
446
 
378
447
  writePackageJson(pkgPath, pkg)
379
-
380
- printDependencyResult(result, DEP_NAME)
381
448
  } else {
382
449
  logSkippedSelection('package.json')
383
450
  }
@@ -396,7 +463,13 @@ function printNextSteps({ needsManualStyleImport, envChanged, dependencyChanged
396
463
 
397
464
  if (needsManualStyleImport) {
398
465
  steps.push(
399
- ['Import TinyRobot styles in your application entry file.', '', 'Example:', '', ` ${STYLE_IMPORT}`].join('\n'),
466
+ [
467
+ 'Import TinyRobot styles in your application entry file.',
468
+ '',
469
+ 'Example:',
470
+ '',
471
+ ...STYLE_IMPORTS.map((styleImport) => ` ${styleImport}`),
472
+ ].join('\n'),
400
473
  )
401
474
  }
402
475
 
package/bin/utils.js CHANGED
@@ -88,6 +88,10 @@ export function validateProjectName(name) {
88
88
  }
89
89
 
90
90
  export function copyTemplate(sourceDir, targetDir) {
91
+ copyDirectory(sourceDir, targetDir)
92
+ }
93
+
94
+ export function copyDirectory(sourceDir, targetDir) {
91
95
  fs.cpSync(sourceDir, targetDir, {
92
96
  recursive: true,
93
97
  filter: (source) => {
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "@opentiny/tiny-robot-cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.2-alpha.11",
4
4
  "description": "CLI to scaffold TinyRobot product projects",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/opentiny/tiny-robot.git"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
5
12
  "type": "module",
6
13
  "bin": {
7
14
  "tiny-robot-cli": "./bin/cli.js"
@@ -25,5 +32,5 @@
25
32
  "semver": "^7.8.1",
26
33
  "yaml": "^2.9.0"
27
34
  },
28
- "gitHead": "47f98d6b156e80289b8b7b5b213b9575dd230210"
35
+ "gitHead": "09317e7ea1f51d2b8b6a97ebb7b2ceee9be4a963"
29
36
  }
@@ -1 +1,5 @@
1
1
  VITE_DEEPSEEK_API_KEY=
2
+ VITE_DEEPSEEK_API_URL=
3
+ VITE_QWEN_API_URL=
4
+ VITE_ALIYUN_DASHSCOPE_KEY=
5
+ VITE_AMAP_MCP_URL=
@@ -0,0 +1,36 @@
1
+ # chat-add
2
+
3
+ `chat-add` 是一个独立的 Chat 扩展模板,提供通用工作助手场景的最小工程结构。
4
+
5
+ ## 启动
6
+
7
+ ```powershell
8
+ pnpm --filter chat-add dev
9
+ ```
10
+
11
+ ## 构建
12
+
13
+ ```powershell
14
+ pnpm --filter chat-add type-check
15
+ pnpm --filter chat-add build
16
+ ```
17
+
18
+ ## 接入真实模型
19
+
20
+ 在 `packages/chat-add/.env.local` 中配置 `VITE_QWEN_API_URL`、`VITE_ALIYUN_DASHSCOPE_KEY`、`VITE_DEEPSEEK_API_URL` 和 `VITE_DEEPSEEK_API_KEY`,重启 Vite 后即可使用模型选择器中的真实模型。配置读取逻辑位于 `src/config/chat-runtime.ts`。
21
+
22
+ ## 目录职责
23
+
24
+ - `src/config/chat-ui.ts`: Chat UI、提示词、模板和菜单配置。
25
+ - `src/config/chat-runtime.ts`: MCP 示例和模型配置。
26
+ - `src/components`: 窗口头部和输入区工具。
27
+ - `src/composables/useWindow.ts`: floating、fullscreen 窗口状态。
28
+ - `src/index.css`: 模板公共样式和 Surface 样式变量。
29
+
30
+ ## 替换数据
31
+
32
+ 直接修改 `src/config/chat-ui.ts` 和 `src/config/chat-runtime.ts` 中导出的数据;组件通过 props 使用这些配置。
33
+
34
+ ## 切换窗口模式
35
+
36
+ 默认显示 AI 头像按钮,点击后展开 Chat。窗口头部提供 floating、fullscreen 两种模式。floating 支持拖拽、缩放和位置恢复;fullscreen 铺满对应区域,可使用 Escape 返回悬浮窗口。点击关闭后返回 AI 头像按钮。
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Chat Add Template</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vue-tsc -b && vite build",
9
+ "preview": "vite preview",
10
+ "type-check": "vue-tsc -b"
11
+ },
12
+ "dependencies": {
13
+ "@opentiny/tiny-robot": "0.5.2-alpha.10",
14
+ "@opentiny/tiny-robot-chat": "0.5.2-alpha.10",
15
+ "@opentiny/tiny-robot-kit": "0.5.2-alpha.10",
16
+ "@opentiny/tiny-robot-svgs": "0.5.2-alpha.10",
17
+ "@vueuse/core": "13.1.0",
18
+ "vue": "^3.5.40"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^24.13.3",
22
+ "@vitejs/plugin-vue": "^6.0.8",
23
+ "@vue/tsconfig": "^0.9.1",
24
+ "typescript": "~6.0.2",
25
+ "vite": "^8.2.0",
26
+ "vue-tsc": "^3.3.8"
27
+ }
28
+ }
@@ -1,35 +1,96 @@
1
1
  <script setup lang="ts">
2
- import type { ChatMcpServerConfig, ChatModelOption } from '@opentiny/tiny-robot/experimental'
3
- import { TrThemeProvider } from '@opentiny/tiny-robot'
4
- import { TrChat } from '@opentiny/tiny-robot/experimental'
5
- import { ref } from 'vue'
2
+ import type { HistoryMenuItem, PromptProps, TemplateItem } from '@opentiny/tiny-robot'
3
+ import { TrSender, TrThemeProvider as TrTheme } from '@opentiny/tiny-robot'
4
+ import { TrChat, useChatHistoryItems, useLocalChatRuntime, type ChatHistoryItem } from '@opentiny/tiny-robot-chat'
5
+ import { IconAi } from '@opentiny/tiny-robot-svgs'
6
+ import { computed, reactive, ref } from 'vue'
7
+ import ComposerTools from './components/ComposerTools.vue'
8
+ import WindowHeader from './components/WindowHeader.vue'
9
+ import { composerMenus, createChatUi, templateCategories } from './config/chat-ui'
10
+ import { mcpExamples, mcpServers, modelProviders } from './config/chat-runtime'
11
+ import { useWindow } from './composables/useWindow'
6
12
 
7
- const show = ref(false)
8
- const fullscreen = ref(false)
13
+ const runtime = useLocalChatRuntime({ modelProviders, mcpServers })
14
+ const window = reactive(useWindow())
15
+ const chatRef = ref<{ send: (payload: { text: string }) => Promise<boolean> } | null>(null)
16
+ const currentTemplate = ref<TemplateItem[]>([])
17
+ const showHistory = ref(false)
18
+ const historyData = useChatHistoryItems({ conversations: () => runtime.conversations.value, defaultTitle: '' })
19
+ const activeConversationId = computed(() => runtime.activeConversation.value?.id)
20
+ const templateExtensions = [TrSender.template(currentTemplate as never, { appendTo: '.chat-add-window' })]
21
+ const chatUi = computed(() => createChatUi({ floatingOptions: window.floatingOptions, templateExtensions }))
9
22
 
10
- const chatModelOptions: ChatModelOption[] = [
11
- {
12
- id: 'deepseek-chat',
13
- provider: 'deepseek',
14
- name: 'DeepSeek V4 Flash',
15
- model: 'deepseek-v4-flash',
16
- apiUrl: 'https://api.deepseek.com/chat/completions',
17
- apiKey: import.meta.env.VITE_DEEPSEEK_API_KEY || '',
18
- },
19
- ]
23
+ function handlePromptClick({ item }: { item: PromptProps }): void {
24
+ if (item.description) void chatRef.value?.send({ text: item.description })
25
+ }
20
26
 
21
- const chatMcpServers: Record<string, ChatMcpServerConfig> = {}
27
+ function handleNewSession(): void {
28
+ void runtime.actions.createConversation()
29
+ showHistory.value = false
30
+ }
31
+
32
+ function handleHistorySelect(item: ChatHistoryItem): void {
33
+ void runtime.actions.switchConversation(item.raw.id)
34
+ showHistory.value = false
35
+ }
36
+
37
+ function handleHistoryRename(title: string, item: ChatHistoryItem): void {
38
+ void runtime.actions.renameConversation(item.raw.id, title)
39
+ }
40
+
41
+ function handleHistoryAction(action: HistoryMenuItem, item: ChatHistoryItem): void {
42
+ if (action.id === 'delete') void runtime.actions.deleteConversation(item.raw.id)
43
+ }
22
44
  </script>
23
45
 
24
46
  <template>
25
- <TrThemeProvider>
26
- <TrChat
27
- v-model:show="show"
28
- v-model:fullscreen="fullscreen"
29
- title="TrChat Preview"
30
- system-prompt="You are a helpful assistant."
31
- :model-options="chatModelOptions"
32
- :mcp-servers="chatMcpServers"
33
- />
34
- </TrThemeProvider>
47
+ <TrTheme>
48
+ <main class="chat-add-app">
49
+ <button
50
+ v-if="!window.show"
51
+ class="chat-add-launcher"
52
+ type="button"
53
+ title="打开 TinyRobot"
54
+ aria-label="打开 TinyRobot"
55
+ @click="window.open"
56
+ >
57
+ <IconAi style="font-size: 32px" />
58
+ </button>
59
+ <TrChat
60
+ v-if="window.show"
61
+ ref="chatRef"
62
+ :class="['chat-add-window', `chat-add-window--${window.displayMode}`]"
63
+ :style="window.layoutStyle"
64
+ :runtime="runtime"
65
+ :ui="chatUi"
66
+ v-model:floating-state="window.floatingState"
67
+ @prompt-click="handlePromptClick"
68
+ >
69
+ <template #layout-header>
70
+ <WindowHeader
71
+ :display-mode="window.displayMode"
72
+ :show-history="showHistory"
73
+ :history-data="historyData"
74
+ :active-conversation-id="activeConversationId"
75
+ @new-session="handleNewSession"
76
+ @select-history="handleHistorySelect"
77
+ @rename-history="handleHistoryRename"
78
+ @history-action="handleHistoryAction"
79
+ @update:show-history="showHistory = $event"
80
+ @change-mode="window.setDisplayMode"
81
+ @close="window.close"
82
+ />
83
+ </template>
84
+ <template #composer-before="{ submit }">
85
+ <ComposerTools
86
+ v-model:current-template="currentTemplate"
87
+ :template-categories="templateCategories"
88
+ :mcp-examples="mcpExamples"
89
+ :menus="composerMenus"
90
+ :submit="submit"
91
+ />
92
+ </template>
93
+ </TrChat>
94
+ </main>
95
+ </TrTheme>
35
96
  </template>
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ import type { SuggestionData, SuggestionItem, TemplateItem } from '@opentiny/tiny-robot'
3
+ import { TrDropdownMenu, TrSuggestionPillButton, TrSuggestionPills, TrSuggestionPopover } from '@opentiny/tiny-robot'
4
+ import type { ChatSenderSlotProps } from '@opentiny/tiny-robot-chat'
5
+ import { IconSparkles } from '@opentiny/tiny-robot-svgs'
6
+ import type { ChatMenuItem, TemplateCategory, TemplateSuggestionItem } from '../config/chat-ui'
7
+ import type { McpExample } from '../config/chat-runtime'
8
+ import { computed } from 'vue'
9
+
10
+ const props = defineProps<{
11
+ templateCategories: readonly TemplateCategory[]
12
+ mcpExamples: readonly McpExample[]
13
+ menus: readonly ChatMenuItem[]
14
+ submit: ChatSenderSlotProps['submit']
15
+ }>()
16
+
17
+ const currentTemplate = defineModel<TemplateItem[]>('currentTemplate', { required: true })
18
+ const suggestionData: SuggestionData = props.mcpExamples.map((item) => ({ id: item.id, text: item.request }))
19
+ const mcpMenuItems = props.mcpExamples.map((item) => ({ id: item.id, text: item.request }))
20
+ const mcpMenu = computed(() => props.menus.find((menu) => menu.action === 'mcp'))
21
+ const templateMenus = computed(() =>
22
+ props.templateCategories.map((category) => ({
23
+ ...category,
24
+ items: category.items.map((item) => ({ id: item.id, text: item.text, template: item })),
25
+ })),
26
+ )
27
+ const overlayTarget = '.chat-add-window'
28
+
29
+ function fillTemplate(template: TemplateSuggestionItem): void {
30
+ currentTemplate.value = template.template.map((item) => ({ ...item }))
31
+ }
32
+
33
+ function submitSuggestion(item: SuggestionItem): void {
34
+ props.submit({ text: item.text })
35
+ }
36
+ </script>
37
+
38
+ <template>
39
+ <div class="chat-add-tools">
40
+ <TrSuggestionPopover :append-to="overlayTarget" :data="suggestionData" @item-click="submitSuggestion">
41
+ <template #trigger>
42
+ <TrSuggestionPillButton title="常用查询"
43
+ ><IconSparkles style="font-size: 16px; color: #1476ff"
44
+ /></TrSuggestionPillButton>
45
+ </template>
46
+ </TrSuggestionPopover>
47
+ <TrSuggestionPills class="chat-add-tools__pills">
48
+ <TrDropdownMenu
49
+ v-if="mcpMenu"
50
+ :append-to="overlayTarget"
51
+ :items="mcpMenuItems"
52
+ trigger="click"
53
+ @item-click="(item) => submitSuggestion(item as SuggestionItem)"
54
+ >
55
+ <template #trigger
56
+ ><TrSuggestionPillButton>{{ mcpMenu.title }}</TrSuggestionPillButton></template
57
+ >
58
+ </TrDropdownMenu>
59
+ <TrDropdownMenu
60
+ v-for="category in templateMenus"
61
+ :key="category.id"
62
+ :append-to="overlayTarget"
63
+ :items="category.items as never"
64
+ trigger="click"
65
+ @item-click="(item) => fillTemplate((item as unknown as { template: TemplateSuggestionItem }).template)"
66
+ >
67
+ <template #trigger
68
+ ><TrSuggestionPillButton>{{ category.title }}</TrSuggestionPillButton></template
69
+ >
70
+ </TrDropdownMenu>
71
+ </TrSuggestionPills>
72
+ </div>
73
+ </template>
74
+
75
+ <style scoped>
76
+ .chat-add-tools {
77
+ display: flex;
78
+ box-sizing: border-box;
79
+ width: 100%;
80
+ min-width: 0;
81
+ flex-shrink: 0;
82
+ align-items: center;
83
+ gap: 8px;
84
+ padding: 8px 12px;
85
+ }
86
+ .chat-add-tools__pills {
87
+ min-width: 0;
88
+ flex: 1;
89
+ }
90
+ .chat-add-tools__pills :deep(.tr-suggestion-pills__container) {
91
+ min-width: 0;
92
+ mask: linear-gradient(to right, rgb(0 0 0 / 100%) 80%, rgb(0 0 0 / 0%) 100%);
93
+ }
94
+ @container (max-width: 519px) {
95
+ .chat-add-tools {
96
+ padding: 8px;
97
+ }
98
+ }
99
+ </style>