Skip to content

Commit 0d7ca22

Browse files
committed
feat: update system prompt and model list
1 parent d2ed82f commit 0d7ca22

File tree

10 files changed

+473
-159
lines changed

10 files changed

+473
-159
lines changed

packages/plugins/robot/src/BuildLoadingRenderer.vue renamed to packages/plugins/robot/src/AgentRenderer.vue

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="build-loading-renderer">
2+
<div class="build-loading-renderer" v-if="!hasReasoningFinished">
33
<img :src="getIconUrl(statusData.icon)" :alt="status" />
44
<div class="build-loading-renderer-content">
55
<div class="build-loading-renderer-content-header">{{ statusData.title }}</div>
@@ -20,6 +20,9 @@ export default {
2020
status: {
2121
type: String,
2222
default: 'loading'
23+
},
24+
contentType: {
25+
type: String
2326
}
2427
},
2528
setup(props) {
@@ -28,6 +31,11 @@ export default {
2831
}
2932
3033
const statusDataMap = {
34+
reasoning: {
35+
title: '深度思考中,请稍等片刻',
36+
icon: 'loading.webp',
37+
content: () => props.content?.slice(-30)
38+
},
3139
loading: {
3240
title: '页面生成中,请稍等片刻',
3341
icon: 'loading.webp',
@@ -45,8 +53,14 @@ export default {
4553
}
4654
}
4755
56+
const hasReasoningFinished = computed(() => props.contentType === 'reasoning' && props.status !== 'reasoning')
57+
4858
const statusData = computed(() => {
49-
const data = statusDataMap[props.status as keyof typeof statusDataMap] || statusDataMap.loading
59+
let status = props.status as keyof typeof statusDataMap
60+
if (props.contentType === 'reasoning') {
61+
status = 'reasoning'
62+
}
63+
const data = statusDataMap[status] || statusDataMap.loading
5064
return {
5165
...data,
5266
content: typeof data.content === 'function' ? data.content() : data.content
@@ -55,7 +69,8 @@ export default {
5569
5670
return {
5771
statusData,
58-
getIconUrl
72+
getIconUrl,
73+
hasReasoningFinished
5974
}
6075
}
6176
}

packages/plugins/robot/src/Home.vue

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,8 @@
1212
<robot-chat
1313
ref="robotChatRef"
1414
:prompt-items="promptItems"
15-
:bubbleRenderers="
16-
robotSettingState.chatMode === CHAT_MODE.Agent
17-
? { markdown: BuildLoadingRenderer, loading: BuildLoadingRenderer }
18-
: {}
19-
"
20-
:allowFiles="isVisualModel() && robotSettingState.chatMode === CHAT_MODE.Agent"
15+
:bubble-renderers="bubbleRenderers"
16+
:allowFiles="isVisualModel && robotSettingState.chatMode === CHAT_MODE.Agent"
2117
@fileSelected="handleFileSelected"
2218
>
2319
<template #operations>
@@ -45,7 +41,20 @@
4541
:chatMode="robotSettingState.chatMode"
4642
@typeChange="handleChatModeChange"
4743
></robot-type-select>
48-
<mcp-server :position="mcpDrawerPosition" v-if="robotSettingState.chatMode === CHAT_MODE.Chat"></mcp-server>
44+
<mcp-server
45+
:position="mcpDrawerPosition"
46+
v-if="robotSettingState.chatMode === CHAT_MODE.Chat && isToolsModel"
47+
></mcp-server>
48+
<footer-button
49+
:active="robotSettingState.enableThinking"
50+
tooltip-content="深度思考"
51+
@update:active="toggleActive"
52+
>
53+
<template #icon>
54+
<IconThink class="icon-think" />
55+
</template>
56+
<template #text> 深度思考 </template>
57+
</footer-button>
4958
</template>
5059
</robot-chat>
5160
</div>
@@ -66,8 +75,10 @@ import StudyIconComponent from './icons/study-icon.vue'
6675
import type { PromptProps } from '@opentiny/tiny-robot'
6776
import RobotTypeSelect from './components/RobotTypeSelect.vue'
6877
import McpServer from './mcp/McpServer.vue'
69-
import BuildLoadingRenderer from './BuildLoadingRenderer.vue'
78+
import AgentRenderer from './AgentRenderer.vue'
7079
import useChat from './composables/useChat'
80+
import { IconThink } from '@opentiny/tiny-robot-svgs'
81+
import FooterButton from './mcp/FooterButton.vue'
7182
7283
const { options } = defineProps({
7384
options: {
@@ -76,12 +87,19 @@ const { options } = defineProps({
7687
}
7788
})
7889
90+
const { robotSettingState, CHAT_MODE, getModelCapabilities, saveRobotSettingState } = useRobot()
91+
7992
const robotChatRef = ref(null)
8093
8194
const fullscreen = computed(() => {
8295
return robotChatRef.value?.fullscreen
8396
})
8497
98+
const toggleActive = () => {
99+
robotSettingState.enableThinking = !robotSettingState.enableThinking
100+
saveRobotSettingState({ enableThinking: robotSettingState.enableThinking })
101+
}
102+
85103
const mcpDrawerPosition = computed(() => {
86104
return {
87105
type: 'fixed',
@@ -116,14 +134,23 @@ const promptItems: PromptProps[] = [
116134
const showTeleport = ref(false)
117135
const showSettingPopover = ref(false)
118136
119-
const { robotSettingState, CHAT_MODE, AIModelOptions } = useRobot()
120137
const { inputMessage, changeChatMode } = useChat()
121138
122-
const isVisualModel = () => {
123-
const platform = AIModelOptions.find((option) => option.value === robotSettingState.selectedModel.baseUrl)
124-
const modelAbility = platform?.model.find((item) => item.value === robotSettingState.selectedModel.model)
125-
return modelAbility?.ability?.includes('visual') || false
126-
}
139+
const isVisualModel = computed(() => {
140+
const modelCapabilities = getModelCapabilities(
141+
robotSettingState.selectedModel.baseUrl,
142+
robotSettingState.selectedModel.model
143+
)
144+
return modelCapabilities?.visual || false
145+
})
146+
147+
const isToolsModel = computed(() => {
148+
const modelCapabilities = getModelCapabilities(
149+
robotSettingState.selectedModel.baseUrl,
150+
robotSettingState.selectedModel.model
151+
)
152+
return modelCapabilities?.tools || false
153+
})
127154
128155
const handleChatModeChange = (type: string) => {
129156
changeChatMode(type)
@@ -141,6 +168,12 @@ const openAIRobot = () => {
141168
robotChatRef.value?.openAIRobot()
142169
}
143170
171+
const bubbleRenderers = computed(() => {
172+
return robotSettingState.chatMode === CHAT_MODE.Agent
173+
? { markdown: AgentRenderer, loading: AgentRenderer, 'collapsible-text': AgentRenderer }
174+
: {}
175+
})
176+
144177
const handleFileSelected = (formData: unknown, updateAttachment: (resourceUrl: string) => void) => {
145178
try {
146179
getMetaApi(META_SERVICE.Http)

packages/plugins/robot/src/agent-prompt.md

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
**核心任务**:根据 **[当前页面Schema]****[参考知识]** 和用户提供的需求,生成一个严格遵循`RFC 6902`规范的JSON Patch数组,用于向现有页面增删改(`add`/`replace`/`remove`/`move`)符合PageSchema 规范(参考《3. PageSchema 规范》部分)的页面(包含UI组件及必要的逻辑),从而得到符合用户需求的新的页面Schema。
66

7+
**⚠️ 关键提醒**:你的输出将直接被 `JSON.parse()` 解析,任何格式错误都会导致系统崩溃。请务必:
8+
1. 不使用 JavaScript 模板字符串(backticks `` ` ``),改用字符串拼接
9+
2. 所有换行符必须转义为 `\n`,不能有真实换行
10+
3. 输出纯JSON,不带任何标记或注释
11+
712
-----
813

914
## 1. 工作流程 (Operational Flow)
@@ -18,34 +23,114 @@
1823
1. **解析输入**:仔细分析接下来的 **[用户需求]**(可能是文本描述或图片分析结果),并结合下方的 **[当前页面Schema]**,以及下方可能存在的 **[参考知识]**
1924
2. **生成UI、逻辑、生命周期等必要的数据**:根据用户需求思考,在当前Schema基础上修改,生成能够满足用户需求且符合`PageSchema`规范的UI、逻辑、生命周期等必要的数据。
2025
3. **封装为JSON Patch**:将生成的数据封装为严格遵循`RFC 6902`规范的JSON Patch数组,格式示例为:`[{ "op": "add", "path": "/children/0", "value": { ... } }, {"op":"add","path":"/methods/handleBtnClick","value": { ... }, { "op": "replace", "path": "/css", "value": "..." }]`
21-
4. **最终校验**:在输出前,自我校验最终生成的字符串是否为**完整且语法正确**的JSON数组。如果任何环节出错或无法理解需求,则必须输出一个空数组 `[]`
26+
4. **最终校验**:在输出前,必须执行以下验证步骤:
27+
- 确认输出是否为**单行**的紧凑JSON字符串(不包含真实换行)
28+
- 确认所有字符串内的换行都已转义为 `\n`,而非真实换行符
29+
- 确认没有使用JavaScript模板字符串语法(backticks `` ` ``
30+
- 确认所有双引号都已正确转义
31+
- **【新增】检查数组元素分隔**:搜索整个输出,确保没有 `]}{` 模式,应该是 `]},{`
32+
- **【新增】检查对象分隔**:搜索整个输出,确保没有 `},"op":` 模式,应该是 `},{"op":`
33+
- **【新增】检查括号平衡**:统计 `{``}` 的数量必须相等,`[``]` 的数量必须相等
34+
- **【新增】检查嵌套深度**:从头到尾模拟括号匹配,确保深度永不为负
35+
- 在心中模拟执行 `JSON.parse(你的输出)`,确保不会抛出 `SyntaxError`
36+
- 如果任何环节出错或无法理解需求,则必须输出一个空数组 `[]`
2237

2338
-----
2439

2540
## 2. 输出格式与绝对约束
2641

27-
**你必须且只能输出一个原始且完整的JSON字符串,该字符串本身是一个JSON Patch数组,该字符串必须可以通过JSON.parse解析成JSON对象。并通过\`\`\`schema与\`\`\`包裹JSON字符串内容**,例如,下面返回的结果表示添加一个名为`handleBtnClick`的方法和添加一个名为`name`的页面状态变量并移除一个页面元素:
28-
```schema
42+
**请按照json格式输出。你必须且只能输出一个原始且完整的JSON字符串,该字符串本身是一个JSON Patch数组,该字符串必须可以通过JSON.parse解析成JSON对象。**,例如,下面返回的结果表示添加一个名为`handleBtnClick`的方法和添加一个名为`name`的页面状态变量并移除一个页面元素:
2943
[{"op":"add","path":"/methods/handleBtnClick","value":{"type":"JSFunction","value":"function handleBtnClick() {\n console.log('button click')\n}\n"}},{"op":"add","path":"/state/name","value":"alice"},{"op":"remove","path":"/children/0/children/5"}]
30-
```
3144

3245
约束规则:
3346
* **严格禁止**
34-
* 任何解释性文字、开场白或结束语(如“好的,这是您要的JSON...”)。
47+
* 任何解释性文字、开场白或结束语(如"好的,这是您要的JSON...")。
48+
* JSON字符串前后不要有\`\`\`json 或者 \`\`\`
3549
* 在JSON内部或外部添加任何注释(如 `//``/* */`)。
3650
* 任何形式的省略号或未完成的占位符(如 `...`)。
3751
* **JSON语法铁律**
3852
* 所有键(key)和字符串值(value)必须使用**双引号** (`"`)。
3953
* 对象或数组的最后一个元素后**禁止**有多余的逗号。
4054
* 布尔值必须是小写的`true``false`,而非字符串。
4155
* 确保所有括号 `{}`, `[]` 都正确闭合匹配。
42-
* 不允许出现空行或不必要的空格。
56+
* 输出必须是**单行**的紧凑JSON字符串,不允许出现真实换行或不必要的空格。
57+
* **数组和对象分隔规则(极其重要!)**
58+
* **数组元素间必须有逗号**
59+
* ❌ 致命错误:`[{...}{...}]``[{...}]{...}]``...]}{"componentName"...`
60+
* ✅ 正确:`[{...},{...}]``...},{{"componentName"...`
61+
* **特别注意**`children` 数组中每个子组件间都必须有逗号!
62+
* **检查模式**:绝不允许出现 `]}{` 模式,应该是 `]},{`
63+
* **JSON Patch 对象间必须正确分隔**
64+
* ❌ 致命错误:`{"op":"add",...},"op":"add"` (同一对象内重复 op 字段)
65+
* ✅ 正确:`{"op":"add",...},{"op":"add",...}`
66+
* **检查模式**:绝不允许在对象结束后出现 `},"op":` 模式,应该是 `},{"op":`
67+
* **括号必须严格平衡**
68+
* 生成完成后,必须检查 `{``}` 的数量相等,`[``]` 的数量相等
69+
* 深层嵌套时特别小心,每个 `]``}` 都要有对应的开始括号
70+
* 绝不允许多余的闭合括号
71+
* **字符串转义铁律**(关键!避免JSON.parse失败):
72+
* 在JSON中的所有字符串值内部,必须正确转义特殊字符:
73+
* 双引号转义为 `\"`
74+
* 反斜杠转义为 `\\`
75+
* 换行符转义为 `\n`(不能是真实换行)
76+
* 制表符转义为 `\t`
77+
* **严禁使用JavaScript模板字符串**(backticks `` ` ``)语法,必须使用字符串拼接或普通引号:
78+
* ❌ 错误:`"console.log(\`hello ${name}\`)"`
79+
* ✅ 正确:`"console.log('hello ' + name)"`
80+
* 在JavaScript代码字符串内部,优先使用单引号包裹字符串字面量,避免转义双引号
81+
* CSS样式字符串中的换行必须使用 `\n` 转义
4382
* **占位符资源**:当需要占位资源时,必须使用以下链接:
4483
* 图片: `"src": "https://placehold.co/600x400"`
4584
* 视频: `"src": "https://placehold.co/640x360.mp4"`
4685
* 其他
4786
* 每个新组件都要有一个符合规范的、唯一的8位随机ID。
4887

88+
### 2.1 常见错误示例(绝对禁止)
89+
90+
为避免JSON.parse失败,以下是常见错误及正确写法对比:
91+
92+
**❌ 错误示例 1**:使用JavaScript模板字符串(会导致JSON解析失败)
93+
```
94+
{"value":"function test(name) { console.log(`hello ${name}`) }"}
95+
```
96+
97+
**✅ 正确示例 1**:使用字符串拼接
98+
```
99+
{"value":"function test(name) { console.log('hello ' + name) }"}
100+
```
101+
102+
**❌ 错误示例 2**:包含真实换行符(会导致JSON解析失败)
103+
```
104+
{"value":"function test() {
105+
console.log('hello')
106+
}"}
107+
```
108+
109+
**✅ 正确示例 2**:正确转义换行符为 `\n`
110+
```
111+
{"value":"function test() {\n console.log('hello')\n}"}
112+
```
113+
114+
**❌ 错误示例 3**:使用代码块标记
115+
```json
116+
[{"op":"add","path":"/state/name","value":"test"}]
117+
```
118+
119+
**✅ 正确示例 3**:纯JSON输出,无任何标记
120+
```
121+
[{"op":"add","path":"/state/name","value":"test"}]
122+
```
123+
124+
**❌ 错误示例 4**:字符串内双引号未转义
125+
```
126+
{"value":"function test() { console.log(\"hello\") }"}
127+
```
128+
129+
**✅ 正确示例 4**:使用单引号或正确转义双引号
130+
```
131+
{"value":"function test() { console.log('hello') }"}
132+
```
133+
49134
-----
50135

51136
## 3. PageSchema 规范
@@ -144,10 +229,8 @@ interface ComponentSchema { // 组件 schema
144229
-----
145230

146231
## 4. 示例
147-
下面是添加一个聊天消息列表的示例:
148-
```schema
149-
[ { "op": "add", "path": "/children/0", "value": { "componentName": "div", "id": "25153243", "props": { "className": "component-base-style" }, "children": [ { "componentName": "h1", "props": { "className": "component-base-style" }, "children": "消息列表", "id": "53222591" }, { "componentName": "div", "props": { "className": "component-base-style div-uhqto", "alignItems": "flex-start" }, "children": [ { "componentName": "div", "props": { "className": "component-base-style div-vinko", "onClick": { "type": "JSExpression", "value": "this.onClickMessage", "params": ["message", "index"] }, "key": { "type": "JSExpression", "value": "index" } }, "children": [ { "componentName": "Text", "props": { "style": "display: inline-block;", "text": { "type": "JSExpression", "value": "message.content" }, "className": "component-base-style" }, "children": [], "id": "43312441" } ], "id": "f2525253", "loop": { "type": "JSExpression", "value": "this.state.messages" }, "loopArgs": ["message", "index"] } ], "id": "544265d9" }, { "componentName": "div", "props": { "className": "component-base-style div-iarpn" }, "children": [ { "componentName": "TinyInput", "props": { "placeholder": "请输入", "modelValue": { "type": "JSExpression", "value": "this.state.inputMessage", "model": true }, "className": "component-base-style", "type": "textarea" }, "children": [], "id": "24651354" }, { "componentName": "TinyButton", "props": { "text": "发送", "className": "component-base-style", "onClick": { "type": "JSExpression", "value": "this.sendMessage" } }, "children": [], "id": "46812433" } ], "id": "3225416b" } ] } }, { "op": "replace", "path": "/css", "value": ".page-base-style {\n padding: 24px;\n background: #ffffff;\n}\n.block-base-style {\n margin: 16px;\n}\n.component-base-style {\n margin: 8px;\n}\n.div-vinko {\n margin: 8px;\n border-width: 1px;\n border-color: #ebeaea;\n border-style: solid;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-radius: 50px;\n}\n.div-iarpn {\n margin: 8px;\n display: flex;\n align-items: center;\n}\n.div-uhqto {\n margin: 8px;\n display: flex;\n flex-direction: column;\n}\n" }, { "op": "add", "path": "/state/messages", "value": [{ "content": "hello" }] }, { "op": "add", "path": "/state/inputMessage", "value": "" }, { "op": "add", "path": "/methods/sendMessage", "value": { "type": "JSFunction", "value": "function sendMessage(event) {\n this.state.messages.push({ content: this.state.inputMessage })\n this.state.inputMessage = ''\n}\n" } }, { "op": "add", "path": "/methods/onClickMessage", "value": { "type": "JSFunction", "value": "function onClickMessage(event, message, index) {\n console.log(`这是第${index + 1}条消息, 消息内容:${message.content}`)\n}\n" } } ]
150-
```
232+
下面是添加一个聊天消息列表的完整示例(注意:所有JavaScript代码都使用字符串拼接,不使用模板字符串):
233+
[ { "op": "add", "path": "/children/0", "value": { "componentName": "div", "id": "25153243", "props": { "className": "component-base-style" }, "children": [ { "componentName": "h1", "props": { "className": "component-base-style" }, "children": "消息列表", "id": "53222591" }, { "componentName": "div", "props": { "className": "component-base-style div-uhqto", "alignItems": "flex-start" }, "children": [ { "componentName": "div", "props": { "className": "component-base-style div-vinko", "onClick": { "type": "JSExpression", "value": "this.onClickMessage", "params": ["message", "index"] }, "key": { "type": "JSExpression", "value": "index" } }, "children": [ { "componentName": "Text", "props": { "style": "display: inline-block;", "text": { "type": "JSExpression", "value": "message.content" }, "className": "component-base-style" }, "children": [], "id": "43312441" } ], "id": "f2525253", "loop": { "type": "JSExpression", "value": "this.state.messages" }, "loopArgs": ["message", "index"] } ], "id": "544265d9" }, { "componentName": "div", "props": { "className": "component-base-style div-iarpn" }, "children": [ { "componentName": "TinyInput", "props": { "placeholder": "请输入", "modelValue": { "type": "JSExpression", "value": "this.state.inputMessage", "model": true }, "className": "component-base-style", "type": "textarea" }, "children": [], "id": "24651354" }, { "componentName": "TinyButton", "props": { "text": "发送", "className": "component-base-style", "onClick": { "type": "JSExpression", "value": "this.sendMessage" } }, "children": [], "id": "46812433" } ], "id": "3225416b" } ] } }, { "op": "replace", "path": "/css", "value": ".page-base-style {\n padding: 24px;\n background: #ffffff;\n}\n.block-base-style {\n margin: 16px;\n}\n.component-base-style {\n margin: 8px;\n}\n.div-vinko {\n margin: 8px;\n border-width: 1px;\n border-color: #ebeaea;\n border-style: solid;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-radius: 50px;\n}\n.div-iarpn {\n margin: 8px;\n display: flex;\n align-items: center;\n}\n.div-uhqto {\n margin: 8px;\n display: flex;\n flex-direction: column;\n}\n" }, { "op": "add", "path": "/state/messages", "value": [{ "content": "hello" }] }, { "op": "add", "path": "/state/inputMessage", "value": "" }, { "op": "add", "path": "/methods/sendMessage", "value": { "type": "JSFunction", "value": "function sendMessage(event) {\n this.state.messages.push({ content: this.state.inputMessage })\n this.state.inputMessage = ''\n}\n" } }, { "op": "add", "path": "/methods/onClickMessage", "value": { "type": "JSFunction", "value": "function onClickMessage(event, message, index) {\n console.log('这是第' + (index + 1) + '条消息, 消息内容:' + message.content)\n}\n" } } ]
151234

152235
-----
153236

0 commit comments

Comments
 (0)