Skip to content

Commit 8005437

Browse files
committed
feat: update prompt
1 parent b8746a2 commit 8005437

File tree

5 files changed

+104
-88
lines changed

5 files changed

+104
-88
lines changed

packages/plugins/robot/src/Home.vue

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,20 +90,21 @@ const mcpDrawerPosition = computed(() => {
9090
})
9191
9292
const promptItems: PromptProps[] = [
93+
{
94+
label: '页面搭建场景',
95+
description: '在当前页面中生成一个满意度调查表单',
96+
icon: h(PageIconComponent),
97+
badge: 'NEW'
98+
},
9399
{
94100
label: 'MCP工具',
95101
description: '帮我查询当前的页面列表',
96102
icon: h(McpIconComponent),
97103
badge: 'NEW'
98104
},
99105
{
100-
label: '页面搭建场景',
101-
description: '给当前页面中添加一个问卷调查表单',
102-
icon: h(PageIconComponent)
103-
},
104-
{
105-
label: '学习/知识型场景',
106-
description: 'Vue3 和 React 有什么区别?',
106+
label: '日常开发问答',
107+
description: '如何实现前端节流与防抖?',
107108
icon: h(StudyIconComponent)
108109
}
109110
]

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

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,34 @@
44

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

7-
例如,下面返回的结果表示添加一个名为`handleBtnClick`的方法和添加一个名为`name`的页面状态变量并移除一个页面元素:
8-
```json
9-
[{"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"}]
10-
```
117
-----
128

139
## 1. 工作流程 (Operational Flow)
1410

15-
请严格遵循以下步骤思考和执行:
11+
低代码平台流程如下:
12+
当前页面Schema --> 根据用户需求生成新页面Schema对应的JSON Patch数据 --> 应用JSON Patch生成新页面Schema --> 根据用户要求继续修改,基于新Schema生成新的JSON Patch --> 应用新的JSON Patch更新当前页面Schema
13+
14+
页面Schema为用特定JSON格式描述的页面UI功能的数据,页面Schema可以通过出码功能生成对于的Vue代码,因此页面Schema也等效于特定格式的Vue单文件组件代码。
1615

17-
1. **解析输入**:仔细分析 **[用户需求]**(可能是文本描述或图片分析结果)、**[参考知识]****[当前页面Schema]**
18-
2. **识别组件**:将用户需求解构为符合`PageSchema`规范的一个或多个组件(如`TinyInput`, `img`, `Text`等)。
19-
3. **构建组件结构**
20-
* 为每个新组件生成一个符合规范的、唯一的8位随机ID。
21-
* 根据`PageSchema`组件转换规则,确定每个组件的`componentName`
22-
* **精确还原样式**:根据用户需求(尤其是图片),在每个组件的`props.className`中生成`Tailwind`样式类,例如:`"className": "size-48 shadow-xl rounded-md"`,或者生成`props.style`字段中生成详细的行内样式字符串,例如:`"style": "display: flex; align-items: center; background-color: #FFFFFF; padding: 16px;";` **优先使用`Tailwind`样式类**;优先使用弹性布局(Flex)来保证结构和对齐;精确匹配颜色、内外边距、字体大小等视觉元素。
23-
* 递归地构建`children`数组,形成正确的嵌套关系。
24-
4. **封装为JSON Patch**:将生成的所有顶级组件封装到一个JSON Patch对象中,格式为:`{ "op": "add", "path": "/children/-", "value": { ... } }`
25-
5. **最终校验**:在输出前,自我校验最终生成的字符串是否为**完整且语法正确**的JSON数组。如果任何环节出错或无法理解需求,则必须输出一个空数组 `[]`
16+
因此请严格遵循以下步骤思考和执行,来为用户生成符合需求的页面Schema(JSON Patch格式):
17+
18+
1. **解析输入**:仔细分析接下来的 **[用户需求]**(可能是文本描述或图片分析结果),并结合下方的 **[当前页面Schema]**,以及下方可能存在的 **[参考知识]**
19+
2. **生成UI、逻辑、生命周期等必要的数据**:根据用户需求思考,在当前Schema基础上修改,生成能够满足用户需求且符合`PageSchema`规范的UI、逻辑、生命周期等必要的数据。
20+
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数组。如果任何环节出错或无法理解需求,则必须输出一个空数组 `[]`
2622

2723
-----
2824

2925
## 2. 输出格式与绝对约束
3026

31-
**你必须且只能输出一个原始的JSON字符串,该字符串本身是一个JSON Patch数组,该字符串必须可以通过JSON.parse解析成JSON对象。**
27+
**你必须且只能输出一个原始且完整的JSON字符串,该字符串本身是一个JSON Patch数组,该字符串必须可以通过JSON.parse解析成JSON对象。并通过\`\`\`schema与\`\`\`包裹JSON字符串内容**,例如,下面返回的结果表示添加一个名为`handleBtnClick`的方法和添加一个名为`name`的页面状态变量并移除一个页面元素:
28+
```schema
29+
[{"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+
```
3231

32+
约束规则:
3333
* **严格禁止**
3434
* 任何解释性文字、开场白或结束语(如“好的,这是您要的JSON...”)。
35-
* 使用` ```json `代码块包裹最终输出。直接输出原始文本。
3635
* 在JSON内部或外部添加任何注释(如 `//``/* */`)。
3736
* 任何形式的省略号或未完成的占位符(如 `...`)。
3837
* **JSON语法铁律**
@@ -44,6 +43,8 @@
4443
* **占位符资源**:当需要占位资源时,必须使用以下链接:
4544
* 图片: `"src": "https://placehold.co/600x400"`
4645
* 视频: `"src": "https://placehold.co/640x360.mp4"`
46+
* 其他
47+
* 每个新组件都要有一个符合规范的、唯一的8位随机ID。
4748

4849
-----
4950

@@ -100,7 +101,7 @@ interface ComponentSchema { // 组件 schema
100101

101102
### 3.3 组件规则
102103

103-
组件(componentName)可以使用低代码平台中的组件或者HTML原生组件(div、img、h1、a、span等)。所有低代码平台可用组件如下:
104+
组件(componentName)可以使用低代码平台中的组件(TinyVue组件库)或者HTML原生组件(div、img、h1、a、span等)。所有低代码平台可用组件如下:
104105
```jsonl
105106
{"component":"Box","name":"盒子容器","demo":{"componentName":"div","props":{}}}
106107
{"component":"Text","name":"文本","properties":["text"],"events":["onClick"],"demo":{"componentName":"Text","props":{"style":"display: inline-block;","text":"TinyEngine 前端可视化设计器,为设计器开发者提供定制服务,在线构建出自己专属的设计器。"}}}
@@ -144,8 +145,8 @@ interface ComponentSchema { // 组件 schema
144145

145146
## 4. 示例
146147
下面是添加一个聊天消息列表的示例:
147-
```json
148-
[{ "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" } }, { "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" } ]
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" } } ]
149150
```
150151

151152
-----

packages/plugins/robot/src/components/RobotChat.vue

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ import LoadingRenderer from '../mcp/LoadingRenderer.vue'
120120
import MarkdownRenderer from '../mcp/MarkdownRenderer.vue'
121121
import ImgRenderer from '../mcp/ImgRenderer.vue'
122122
import { serializeError } from '../utils/common-utils'
123-
import { initDebugWindow } from '../composables/debug'
124123
125124
const { promptItems, allowFiles, bubbleRenderers } = defineProps({
126125
promptItems: {
@@ -354,7 +353,6 @@ const handlePromptItemClick = (ev: unknown, item: { description?: string }) => {
354353
355354
onMounted(() => {
356355
createConversation()
357-
initDebugWindow()
358356
})
359357
360358
defineExpose({
Lines changed: 65 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,100 @@
11
import { jsonrepair } from 'jsonrepair'
2-
import { checkComponentNameExists } from '../js/utils'
32
import * as jsonpatch from 'fast-json-patch'
43
import { utils } from '@opentiny/tiny-engine-utils'
54
import { useCanvas, useHistory } from '@opentiny/tiny-engine-meta-register'
65
import useRobot from '../js/useRobot'
76
import SvgICons from '@opentiny/vue-icon'
87

9-
const { string2Obj, reactiveObj2String: obj2String, deepClone } = utils
8+
const { deepClone } = utils
109
import { useThrottleFn } from '@vueuse/core'
1110

11+
const logger = console
12+
1213
const setSchema = (schema: object) => {
1314
const { importSchema, setSaved } = useCanvas()
1415
importSchema(schema)
1516
setSaved(false)
1617
}
1718

18-
const fixInvalidIconComponent = (data: any) => {
19-
if (data.componentName === 'Icon' && data.props?.name && !SvgICons[data.props.name as keyof typeof SvgICons]) {
19+
const fixIconComponent = (data: unknown) => {
20+
if (data?.componentName === 'Icon' && data.props?.name && !SvgICons[data.props.name as keyof typeof SvgICons]) {
2021
data.props.name = 'IconWarning'
21-
}
22-
23-
if (data.children && Array.isArray(data.children)) {
24-
data.children.forEach((child: any) => fixInvalidIconComponent(child))
22+
logger.log('autofix icon to warning:', data)
2523
}
2624
}
2725

28-
const updateStreamCanvasPageSchema = async (streamContent: string, currentPageSchema: object) => {
29-
try {
30-
const repaired = jsonrepair(streamContent)
31-
const parsedJson = JSON.parse(repaired)
32-
const latestPatch = parsedJson.at(-1)
33-
if (latestPatch?.path && !latestPatch.path.startsWith('/children')) {
34-
parsedJson.pop()
35-
}
36-
const result = parsedJson.reduce((acc: object, patch: any) => {
37-
fixInvalidIconComponent(patch.value)
38-
return jsonpatch.applyPatch(acc, [patch], false, false).newDocument
39-
}, deepClone(currentPageSchema))
40-
const editorValue = string2Obj(obj2String(result))
26+
const isPlainObject = (value: unknown) =>
27+
typeof value === 'object' && value !== null && Object.prototype.toString.call(value) === '[object Object]'
4128

42-
if (editorValue && checkComponentNameExists(result)) {
43-
setSchema(result)
44-
}
45-
} catch (error) {
46-
const logger = console
47-
logger.error('updateStreamCanvasPageSchema error', error)
29+
const fixComponentName = (data: object) => {
30+
if (isPlainObject(data) && !data.componentName) {
31+
data.componentName = 'div'
32+
logger.log('autofix component to div:', data)
4833
}
4934
}
5035

51-
// 节流更新schema
52-
export const throttledUpdateCanvasPageSchema = useThrottleFn(updateStreamCanvasPageSchema, 500, true)
53-
54-
export const handleStreamData = (streamContent: string, currentJson: object) => {
55-
const { aiMode, CHAT_MODE } = useRobot()
56-
if (aiMode.value !== CHAT_MODE.Agent) {
57-
return
36+
const schemaAutoFix = (data: object | object[]) => {
37+
if (!data) return
38+
if (Array.isArray(data)) data.forEach((item) => schemaAutoFix(item))
39+
fixIconComponent(data)
40+
fixComponentName(data)
41+
if (data.children && Array.isArray(data.children)) {
42+
data.children.forEach((child: any) => schemaAutoFix(child))
5843
}
59-
throttledUpdateCanvasPageSchema(streamContent, currentJson)
6044
}
6145

62-
export const updateCanvasPageSchema = (streamContent: string, currentJson: object, messages: RobotMessage[]) => {
46+
const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => {
6347
const { aiMode, CHAT_MODE, isValidFastJsonPatch } = useRobot()
6448
if (aiMode.value !== CHAT_MODE.Agent) {
6549
return
6650
}
67-
const regex = /```json([\s\S]*?)```/
68-
const match = streamContent.match(regex)
69-
const content = (match && match[1]) || streamContent
7051

52+
// 解析流式返回的schema patch
53+
const regex = /```(json|schema)?([\s\S]*?)```/
54+
const match = streamContent.match(regex)
55+
const content = (match && match[2]) || streamContent
56+
let jsonPatches = []
7157
try {
72-
const schemaPatch = JSON.parse(content)
73-
if (isValidFastJsonPatch(schemaPatch)) {
74-
const result = schemaPatch.reduce((acc: object, patch: any) => {
75-
fixInvalidIconComponent(patch.value)
76-
return jsonpatch.applyPatch(acc, [patch], false, false).newDocument
77-
}, deepClone(currentJson))
78-
const logger = console
79-
logger.log('current schema:', deepClone(currentJson))
80-
logger.log('new Schema:', result)
81-
setSchema(result)
82-
useHistory().addHistory()
83-
84-
messages.at(-1).renderContent.at(-1).status = 'success'
85-
messages.at(-1).renderContent.at(-1).schema = result
86-
}
58+
jsonPatches = JSON.parse(jsonrepair(content))
8759
} catch (error) {
88-
const logger = console
89-
logger.error('updateCanvasPageSchema error', error)
90-
messages.at(-1).renderContent.at(-1).status = 'failed'
60+
if (isFinial) {
61+
logger.error('parse json patch error:', error)
62+
}
63+
return { isError: true, error }
64+
}
65+
66+
// 流式渲染过程中,画布只渲染children字段,避免不完整的methods/states/css等字段导致解析报错
67+
const childrenFilter = (patch) => isFinial || patch.path?.startsWith('/children')
68+
69+
// 过滤有效的json patch
70+
if (!isFinial && !isValidFastJsonPatch(jsonPatches)) {
71+
return { isError: true, error: 'format error: not a valid json patch.' }
72+
}
73+
const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch)
74+
75+
// 生成新schema
76+
const originSchema = deepClone(currentPageSchema)
77+
const newSchema = validJsonPatches.reduce((acc: object, patch: any) => {
78+
try {
79+
return jsonpatch.applyPatch(acc, [patch], false, false).newDocument
80+
} catch (error) {
81+
if (isFinial) {
82+
logger.error('apply patch error:', error, patch)
83+
}
84+
return acc
85+
}
86+
}, originSchema)
87+
88+
// schema纠错
89+
schemaAutoFix(newSchema.children)
90+
91+
// 更新Schema
92+
setSchema(newSchema)
93+
if (isFinial) {
94+
useHistory().addHistory()
9195
}
96+
97+
return { schema: newSchema, isError: false }
9298
}
99+
100+
export const updatePageSchema = useThrottleFn(_updatePageSchema, 200, true)

0 commit comments

Comments
 (0)