@opentiny/tiny-robot-cli 0.4.2 → 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 +10 -0
- package/bin/commands/add.js +106 -33
- package/bin/utils.js +4 -0
- package/package.json +9 -2
- package/templates/basic/package.json +3 -3
- package/templates/basic/src/models.ts +5 -5
- package/templates/chat/.env.example +4 -0
- package/templates/chat/README.md +36 -0
- package/templates/chat/index.html +12 -0
- package/templates/chat/package.json +28 -0
- package/templates/chat/src/TinyRobotChat.vue +88 -27
- package/templates/chat/src/components/ComposerTools.vue +99 -0
- package/templates/chat/src/components/WindowHeader.vue +192 -0
- package/templates/chat/src/composables/useWindow.ts +83 -0
- package/templates/chat/src/config/chat-runtime.ts +57 -0
- package/templates/chat/src/config/chat-ui.ts +289 -0
- package/templates/chat/src/index.css +116 -0
- package/templates/chat/src/main.ts +7 -0
- package/templates/chat/tsconfig.app.json +13 -0
- package/templates/chat/tsconfig.json +7 -0
- package/templates/chat/tsconfig.node.json +19 -0
- package/templates/chat/vite.config.ts +16 -0
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:
|
package/bin/commands/add.js
CHANGED
|
@@ -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
|
|
23
|
-
const
|
|
24
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
for (const styleImport of STYLE_IMPORTS) {
|
|
109
|
+
if (lines.some((line) => line.trim() === styleImport)) {
|
|
110
|
+
continue
|
|
100
111
|
}
|
|
101
|
-
}
|
|
102
112
|
|
|
103
|
-
|
|
113
|
+
let lastImportIndex = -1
|
|
104
114
|
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
continue
|
|
121
|
+
if (lastImportIndex !== -1) {
|
|
122
|
+
break
|
|
123
|
+
}
|
|
111
124
|
}
|
|
112
125
|
|
|
113
|
-
if (lastImportIndex
|
|
114
|
-
|
|
126
|
+
if (lastImportIndex === -1) {
|
|
127
|
+
lines.unshift(styleImport)
|
|
128
|
+
} else {
|
|
129
|
+
lines.splice(lastImportIndex + 1, 0, styleImport)
|
|
115
130
|
}
|
|
116
|
-
}
|
|
117
131
|
|
|
118
|
-
|
|
119
|
-
lines.unshift(STYLE_IMPORT)
|
|
120
|
-
} else {
|
|
121
|
-
lines.splice(lastImportIndex + 1, 0, STYLE_IMPORT)
|
|
132
|
+
inserted = true
|
|
122
133
|
}
|
|
123
134
|
|
|
124
|
-
|
|
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}@${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
[
|
|
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.
|
|
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": "
|
|
35
|
+
"gitHead": "09317e7ea1f51d2b8b6a97ebb7b2ceee9be4a963"
|
|
29
36
|
}
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.18.1",
|
|
14
|
-
"@opentiny/tiny-robot": "^0.
|
|
15
|
-
"@opentiny/tiny-robot-kit": "^0.
|
|
16
|
-
"@opentiny/tiny-robot-svgs": "^0.
|
|
14
|
+
"@opentiny/tiny-robot": "^0.5.0",
|
|
15
|
+
"@opentiny/tiny-robot-kit": "^0.5.0",
|
|
16
|
+
"@opentiny/tiny-robot-svgs": "^0.5.0",
|
|
17
17
|
"dompurify": "^3.3.1",
|
|
18
18
|
"markdown-it": "^14.1.0",
|
|
19
19
|
"vue": "^3.5.30"
|
|
@@ -64,17 +64,17 @@ export const ModelConfigs: Record<string, ProviderModelConfig> = {
|
|
|
64
64
|
],
|
|
65
65
|
},
|
|
66
66
|
deepseek: {
|
|
67
|
-
apiUrl: 'https://api.deepseek.com/
|
|
67
|
+
apiUrl: 'https://api.deepseek.com/chat/completions',
|
|
68
68
|
icon: IconModelDeepseek,
|
|
69
69
|
apiKey: import.meta.env.VITE_DEEPSEEK_API_KEY,
|
|
70
70
|
models: [
|
|
71
71
|
{
|
|
72
|
-
name: 'DeepSeek
|
|
73
|
-
model: 'deepseek-
|
|
72
|
+
name: 'DeepSeek V4 Flash',
|
|
73
|
+
model: 'deepseek-v4-flash',
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
|
-
name: 'DeepSeek
|
|
77
|
-
model: 'deepseek-
|
|
76
|
+
name: 'DeepSeek V4 Pro',
|
|
77
|
+
model: 'deepseek-v4-pro',
|
|
78
78
|
},
|
|
79
79
|
],
|
|
80
80
|
},
|
|
@@ -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 {
|
|
3
|
-
import { TrThemeProvider } from '@opentiny/tiny-robot'
|
|
4
|
-
import { TrChat } from '@opentiny/tiny-robot
|
|
5
|
-
import {
|
|
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
|
|
8
|
-
const
|
|
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
|
-
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
provider: 'deepseek',
|
|
14
|
-
name: 'DeepSeek Chat',
|
|
15
|
-
model: 'deepseek-chat',
|
|
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
|
-
|
|
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
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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>
|