@opentiny/tiny-robot-cli 0.5.2-alpha.3 → 1.0.0-beta.1

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.
Files changed (53) hide show
  1. package/README.md +49 -1
  2. package/bin/commands/add.js +563 -269
  3. package/bin/commands/create.js +6 -3
  4. package/bin/runtime-version.js +37 -0
  5. package/bin/utils.js +147 -45
  6. package/package.json +5 -3
  7. package/templates/basic/.env.example +8 -0
  8. package/templates/basic/README.md +53 -22
  9. package/templates/basic/index.html +2 -2
  10. package/templates/basic/package.json +12 -16
  11. package/templates/basic/public/favicon.svg +1 -0
  12. package/templates/basic/public/icons.svg +24 -0
  13. package/templates/basic/src/App.vue +111 -119
  14. package/templates/basic/src/main.ts +2 -2
  15. package/templates/basic/tsconfig.app.json +2 -5
  16. package/templates/basic/tsconfig.node.json +3 -6
  17. package/templates/basic/vite.config.ts +6 -1
  18. package/templates/chat/.env.example +9 -0
  19. package/templates/chat/README.md +46 -0
  20. package/templates/chat/index.html +12 -0
  21. package/templates/chat/package.json +28 -0
  22. package/templates/chat/src/TinyRobotChat.vue +88 -27
  23. package/templates/chat/src/components/ComposerTools.vue +99 -0
  24. package/templates/chat/src/components/WindowHeader.vue +192 -0
  25. package/templates/chat/src/composables/useWindow.ts +83 -0
  26. package/templates/chat/src/config/chat-runtime.ts +82 -0
  27. package/templates/chat/src/config/chat-ui.ts +289 -0
  28. package/templates/chat/src/index.css +109 -0
  29. package/templates/chat/src/main.ts +7 -0
  30. package/templates/chat/tsconfig.app.json +13 -0
  31. package/templates/chat/tsconfig.json +7 -0
  32. package/templates/chat/tsconfig.node.json +19 -0
  33. package/templates/chat/vite.config.ts +16 -0
  34. package/templates/basic/public/modelcontextprotocol.png +0 -0
  35. package/templates/basic/src/components/ChatList.vue +0 -82
  36. package/templates/basic/src/components/ChatSender.vue +0 -125
  37. package/templates/basic/src/components/ConversationHistory.vue +0 -136
  38. package/templates/basic/src/components/HistoryDrawerButton.vue +0 -43
  39. package/templates/basic/src/components/McpServerPickerButton.vue +0 -278
  40. package/templates/basic/src/components/ThemeToggleButton.vue +0 -44
  41. package/templates/basic/src/components/icons/IconDeepThink.vue +0 -29
  42. package/templates/basic/src/components/icons/IconModelAliyunBailian.vue +0 -51
  43. package/templates/basic/src/components/icons/IconModelDeepseek.vue +0 -29
  44. package/templates/basic/src/components/icons/IconMoon.vue +0 -29
  45. package/templates/basic/src/components/icons/IconPlugin.vue +0 -29
  46. package/templates/basic/src/components/icons/IconSun.vue +0 -35
  47. package/templates/basic/src/components/icons/IconWebSearch.vue +0 -36
  48. package/templates/basic/src/components/icons/index.ts +0 -7
  49. package/templates/basic/src/composables/useChat.ts +0 -129
  50. package/templates/basic/src/composables/useMcp.ts +0 -170
  51. package/templates/basic/src/composables/useModel.ts +0 -82
  52. package/templates/basic/src/mcpServers.ts +0 -40
  53. package/templates/basic/src/models.ts +0 -81
@@ -1,130 +1,122 @@
1
- <template>
2
- <TrTheme>
3
- <main class="app chat-card">
4
- <ConversationHistory />
5
-
6
- <section class="chat-panel">
7
- <div class="chat-panel-content">
8
- <p v-if="!hasApiConfig" class="config-warning">
9
- 缺少 API 配置,请在 <code>.env</code> 中设置当前模型服务商对应的 Key。
10
- </p>
11
- <header class="chat-header">
12
- <HistoryDrawerButton @click="historyDrawerOpen = true" />
13
- <h3 v-if="currentConversationTitle">{{ currentConversationTitle }}</h3>
14
- <ThemeToggleButton v-if="isWelcomePage" />
15
- </header>
16
-
17
- <ChatList />
18
-
19
- <ChatSender />
20
- </div>
21
- </section>
22
- </main>
23
- </TrTheme>
24
- </template>
25
-
26
1
  <script setup lang="ts">
2
+ import {
3
+ TrChat,
4
+ useLocalChatRuntime,
5
+ type ChatMcpServers,
6
+ type ChatProviderConfig,
7
+ type ChatUIOptions,
8
+ } from '@opentiny/tiny-robot-chat'
27
9
  import { TrThemeProvider as TrTheme } from '@opentiny/tiny-robot'
28
- import { computed, provide, ref } from 'vue'
29
- import ChatList from './components/ChatList.vue'
30
- import ChatSender from './components/ChatSender.vue'
31
- import ConversationHistory from './components/ConversationHistory.vue'
32
- import HistoryDrawerButton from './components/HistoryDrawerButton.vue'
33
- import ThemeToggleButton from './components/ThemeToggleButton.vue'
34
- import { useChat } from './composables/useChat'
35
- import { useModel } from './composables/useModel'
36
-
37
- const { activeConversation, messages } = useChat()
38
- const { hasApiConfig } = useModel()
39
- const historyDrawerOpen = ref(false)
40
- provide('historyDrawerOpen', historyDrawerOpen)
41
-
42
- const isWelcomePage = computed(() => {
43
- return messages.value.filter((item) => item.role !== 'system').length === 0
10
+ import { IconBailian, IconDeepseek } from '@opentiny/tiny-robot-svgs'
11
+
12
+ const modelProviders: ChatProviderConfig[] = [
13
+ {
14
+ type: 'qwen',
15
+ label: 'DashScope',
16
+ apiUrl: import.meta.env.VITE_QWEN_API_URL?.trim() || undefined,
17
+ apiKey: import.meta.env.VITE_ALIYUN_DASHSCOPE_KEY?.trim(),
18
+ models: [
19
+ {
20
+ id: 'qwen3.7-flash',
21
+ label: 'Qwen3.7 Flash',
22
+ icon: IconBailian,
23
+ capabilities: {
24
+ thinking: true,
25
+ search: true,
26
+ },
27
+ },
28
+ {
29
+ id: 'qwen3.7-plus',
30
+ label: 'Qwen3.7 Plus',
31
+ icon: IconBailian,
32
+ capabilities: {
33
+ thinking: true,
34
+ search: true,
35
+ },
36
+ },
37
+ {
38
+ id: 'qwen3.7-max',
39
+ label: 'Qwen3.7 Max',
40
+ icon: IconBailian,
41
+ capabilities: {
42
+ thinking: true,
43
+ search: true,
44
+ },
45
+ },
46
+ ],
47
+ },
48
+ {
49
+ type: 'deepseek',
50
+ apiUrl: import.meta.env.VITE_DEEPSEEK_API_URL?.trim() || undefined,
51
+ apiKey: import.meta.env.VITE_DEEPSEEK_API_KEY?.trim(),
52
+ models: [
53
+ {
54
+ id: 'deepseek-v4-flash',
55
+ label: 'DeepSeek V4 Flash',
56
+ icon: IconDeepseek,
57
+ capabilities: {
58
+ thinking: true,
59
+ },
60
+ },
61
+ {
62
+ id: 'deepseek-v4-pro',
63
+ label: 'DeepSeek V4 Pro',
64
+ icon: IconDeepseek,
65
+ capabilities: {
66
+ thinking: true,
67
+ },
68
+ },
69
+ ],
70
+ },
71
+ ]
72
+
73
+ const amapMcpApiKey = import.meta.env.VITE_ALIYUN_DASHSCOPE_KEY?.trim()
74
+
75
+ const mcpServers = [
76
+ {
77
+ id: 'amap-maps',
78
+ name: '高德地图',
79
+ description: '覆盖地图、导航、地理编码、天气、路径规划、距离测量、关键词搜索和周边搜索等地理信息服务。',
80
+ baseUrl: import.meta.env.VITE_AMAP_MCP_URL?.trim() || 'https://dashscope.aliyuncs.com/api/v1/mcps/amap-maps/mcp',
81
+ icon: 'https://img.alicdn.com/imgextra/i4/O1CN01iPPabT1EGRN6uatHP_!!6000000000324-0-tps-512-512.jpg',
82
+ headers: amapMcpApiKey ? { Authorization: `Bearer ${amapMcpApiKey}` } : undefined,
83
+ },
84
+ {
85
+ id: 'model-context-protocol-mcp',
86
+ name: 'Model Context Protocol MCP',
87
+ description: 'MCP Server',
88
+ baseUrl: '/modelcontextprotocol-mcp',
89
+ installed: true,
90
+ },
91
+ ] satisfies ChatMcpServers
92
+
93
+ const runtime = useLocalChatRuntime({
94
+ mcpServers,
95
+ modelProviders,
44
96
  })
45
97
 
46
- const currentConversationTitle = computed(() => {
47
- return activeConversation.value?.title
48
- })
98
+ const chatUi = {
99
+ layout: {
100
+ rightAside: {
101
+ mode: 'dock',
102
+ width: 400,
103
+ },
104
+ },
105
+ } satisfies ChatUIOptions
49
106
  </script>
50
107
 
51
- <style scoped>
52
- .app {
53
- height: 100vh;
54
- display: flex;
55
- justify-content: center;
56
- align-items: stretch;
57
- padding: 0;
58
- }
59
-
60
- .chat-card {
61
- width: 100%;
62
- height: 100vh;
63
- display: flex;
64
- gap: 0;
65
- overflow: hidden;
66
- align-items: stretch;
67
- flex-direction: row;
68
- padding: 0;
69
- margin: 0;
70
- border-radius: 0;
71
- background: var(--tr-container-bg-default);
72
- box-shadow: none;
73
- }
74
-
75
- .chat-panel {
76
- flex: 1;
77
- min-width: 0;
78
- display: flex;
79
- padding: 12px;
80
- }
81
-
82
- .chat-panel-content {
83
- width: 100%;
84
- max-width: 980px;
85
- margin: 0 auto;
86
- display: flex;
87
- flex-direction: column;
88
- gap: 12px;
89
- min-height: 0;
90
- }
91
-
92
- .chat-header h3 {
93
- margin: 0;
94
- flex: 1;
95
- min-width: 0;
96
- white-space: nowrap;
97
- overflow: hidden;
98
- text-overflow: ellipsis;
99
- }
100
-
101
- .chat-header {
102
- display: flex;
103
- align-items: center;
104
- gap: 8px;
105
- }
106
-
107
- .config-warning {
108
- margin: 0;
109
- padding: 10px 12px;
110
- border-radius: 8px;
111
- background: var(--tr-color-warning-light);
112
- color: var(--tr-color-warning);
113
- font-size: 14px;
114
- }
108
+ <template>
109
+ <TrTheme>
110
+ <div class="chat-basic">
111
+ <TrChat :runtime="runtime" :ui="chatUi" />
112
+ </div>
113
+ </TrTheme>
114
+ </template>
115
115
 
116
+ <style scoped>
116
117
  @media (max-width: 959px) {
117
- .chat-header {
118
- position: relative;
119
- justify-content: center;
120
- min-height: 32px;
121
- }
122
-
123
- .chat-header h3 {
124
- width: 100%;
125
- text-align: center;
126
- padding: 0 48px;
127
- flex: none;
118
+ .chat-basic :deep(.tr-bubble__avatar) {
119
+ display: none;
128
120
  }
129
121
  }
130
122
  </style>
@@ -1,7 +1,7 @@
1
1
  import { createApp } from 'vue'
2
- import App from './App.vue'
3
-
2
+ import '@opentiny/tiny-robot-chat/dist/style.css'
4
3
  import '@opentiny/tiny-robot/dist/style.css'
5
4
  import './style.css'
5
+ import App from './App.vue'
6
6
 
7
7
  createApp(App).mount('#app')
@@ -3,14 +3,11 @@
3
3
  "compilerOptions": {
4
4
  "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5
5
  "types": ["vite/client"],
6
-
7
- /* Linting */
8
- "strict": true,
6
+ "allowArbitraryExtensions": true,
9
7
  "noUnusedLocals": true,
10
8
  "noUnusedParameters": true,
11
9
  "erasableSyntaxOnly": true,
12
- "noFallthroughCasesInSwitch": true,
13
- "noUncheckedSideEffectImports": true
10
+ "noFallthroughCasesInSwitch": true
14
11
  },
15
12
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
16
13
  }
@@ -1,26 +1,23 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
- "target": "ES2023",
4
+ "target": "es2023",
5
5
  "lib": ["ES2023"],
6
- "module": "ESNext",
7
6
  "types": ["node"],
8
7
  "skipLibCheck": true,
9
8
 
10
9
  /* Bundler mode */
11
- "moduleResolution": "bundler",
10
+ "module": "nodenext",
12
11
  "allowImportingTsExtensions": true,
13
12
  "verbatimModuleSyntax": true,
14
13
  "moduleDetection": "force",
15
14
  "noEmit": true,
16
15
 
17
16
  /* Linting */
18
- "strict": true,
19
17
  "noUnusedLocals": true,
20
18
  "noUnusedParameters": true,
21
19
  "erasableSyntaxOnly": true,
22
- "noFallthroughCasesInSwitch": true,
23
- "noUncheckedSideEffectImports": true
20
+ "noFallthroughCasesInSwitch": true
24
21
  },
25
22
  "include": ["vite.config.ts"]
26
23
  }
@@ -1,10 +1,15 @@
1
1
  import { defineConfig } from 'vite'
2
2
  import vue from '@vitejs/plugin-vue'
3
+ import { resolve } from 'node:path'
4
+
5
+ const workspaceRoot = resolve(import.meta.dirname, '..')
3
6
 
4
- // https://vite.dev/config/
5
7
  export default defineConfig({
6
8
  plugins: [vue()],
7
9
  server: {
10
+ fs: {
11
+ allow: [workspaceRoot],
12
+ },
8
13
  proxy: {
9
14
  '/modelcontextprotocol-mcp': {
10
15
  target: 'https://modelcontextprotocol.io/mcp',
@@ -1 +1,10 @@
1
+ # Optional custom model endpoints. Leave blank to use official Provider defaults.
2
+ VITE_QWEN_API_URL=
3
+ VITE_DEEPSEEK_API_URL=
4
+
5
+ # Optional custom MCP endpoint. Leave blank to use the official endpoint.
6
+ VITE_AMAP_MCP_URL=
7
+
8
+ # Optional local direct-connection credentials.
9
+ VITE_ALIYUN_DASHSCOPE_KEY=
1
10
  VITE_DEEPSEEK_API_KEY=
@@ -0,0 +1,46 @@
1
+ # TinyRobot Chat Add
2
+
3
+ 这是一个独立的 Chat 扩展模板,提供通用工作助手场景的最小工程结构。
4
+
5
+ ## 启动
6
+
7
+ ```powershell
8
+ pnpm dev
9
+ ```
10
+
11
+ ## 构建
12
+
13
+ ```powershell
14
+ pnpm type-check
15
+ pnpm build
16
+ ```
17
+
18
+ ## 接入真实模型
19
+
20
+ 复制项目根目录的 `.env.example` 为 `.env.local`,配置 `VITE_QWEN_API_URL`、`VITE_ALIYUN_DASHSCOPE_KEY`、`VITE_DEEPSEEK_API_URL` 和 `VITE_DEEPSEEK_API_KEY`,重启 Vite 后即可使用模型选择器中的真实模型。模板只提供 `.env.example`,不会自动创建 `.env`。配置读取逻辑位于 `src/config/chat-runtime.ts`;使用 `add chat` 注入后,对应文件位于 `src/tiny-robot-chat/config/chat-runtime.ts`。
21
+
22
+ ## 模型配置
23
+
24
+ 模型配置中的 `icon` 用于模型选择器触发器;在窄容器中,带图标的模型会只显示图标。Qwen 模型支持深度思考和联网搜索,DeepSeek 模型支持深度思考。
25
+
26
+ DeepSeek 模型默认继承 `low`、`high`、`max` 三档思考强度,默认值为 `high`。开启 `深度思考` 后,模型选择器才会显示思考等级。业务侧可以在模型对象中通过 `efforts` 和 `defaultEffort` 覆盖选项;只有必须始终思考的模型才配置 `thinkingRequired: true`。
27
+
28
+ ## MCP 代理
29
+
30
+ 独立模板的 `vite.config.ts` 已配置 `/modelcontextprotocol-mcp` 代理。使用 `add chat` 时,CLI 不会复制或修改业务项目的 `vite.config.*`,需要手动把该代理配置添加到业务项目的 `server.proxy` 下,并重启 Vite。
31
+
32
+ ## 目录职责
33
+
34
+ - `src/config/chat-ui.ts`: Chat UI、提示词、模板和菜单配置。
35
+ - `src/config/chat-runtime.ts`: MCP 示例和模型配置。
36
+ - `src/components`: 窗口头部和输入区工具。
37
+ - `src/composables/useWindow.ts`: floating、fullscreen 窗口状态。
38
+ - `src/index.css`: 仅作用于 Chat 容器的组件样式和 Surface 样式变量,不重置宿主页面的全局样式。
39
+
40
+ ## 替换数据
41
+
42
+ 直接修改 `src/config/chat-ui.ts` 和 `src/config/chat-runtime.ts` 中导出的数据;组件通过 props 使用这些配置。
43
+
44
+ ## 切换窗口模式
45
+
46
+ 默认显示 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": "__TINY_ROBOT_VERSION__",
14
+ "@opentiny/tiny-robot-chat": "__TINY_ROBOT_VERSION__",
15
+ "@opentiny/tiny-robot-kit": "__TINY_ROBOT_VERSION__",
16
+ "@opentiny/tiny-robot-svgs": "__TINY_ROBOT_VERSION__",
17
+ "@vueuse/core": "13.9.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>