-
Notifications
You must be signed in to change notification settings - Fork 437
feat: ai plugin refactor #1657
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
feat: ai plugin refactor #1657
Conversation
WalkthroughRefactors the robot plugin into a composable-driven architecture: adds mode-based lifecycle hooks (Agent/Chat), streaming LLM/provider integration, tool-call orchestration, JSON-Patch prompt/repair flows, new UI components and renderers, extensive types/utilities, config migration/persistence, and removes legacy monolithic modules. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as RobotChat
participant ChatC as useChat
participant Mode as useMode (Agent/Chat)
participant Client as AIClient
participant Provider as LLM
participant MCP as MCP Server
participant Canvas
User->>UI: sendMessage(text, attachment?)
UI->>ChatC: sendUserMessage(...)
ChatC->>ChatC: insert loading assistant message
ChatC->>Mode: onBeforeRequest(requestParams)
Mode->>Mode: build prompt & request params
ChatC->>Client: chatStream(request)
Client->>Provider: open stream request
Provider-->>Client: SSE chunks
Client->>ChatC: onStreamData(chunk)
ChatC->>Mode: onStreamData(chunk)
Mode->>Canvas: updatePageSchema(stream patches) (Agent mode)
ChatC->>ChatC: detect tool_calls in stream
ChatC->>MCP: callTool(...) (when needed)
MCP-->>ChatC: tool result
ChatC->>Mode: onPostCallTool(...)
Provider-->>Client: [DONE]
Client->>ChatC: onFinishRequest()
ChatC->>Mode: onMessageProcessed(...)
Mode->>Mode: validate/repair JSON (Agent)
Mode->>Canvas: apply final schema (Agent)
ChatC->>UI: update messages
UI->>User: render response
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
0d7f889 to
57ac995
Compare
57ac995 to
5eb53e9
Compare
|
感觉可以增强一下代码结构,增强插件的可拓展性与可维护性,比如:
好处:将大模型提供商+模型的静态配置集中放置,容易阅读+可维护;也方便后续集中提供配置进行新增或者隐藏 增强示例结构: export default {
name: 'DeepSeek',
apiBase: 'https://api.deepseek.com/v1',
models: [
{
id: 'deepseek-chat',
name: 'deepseek-chat',
contextWindow: 65536, // 上下文大小
maxTokens: 8192,
defaultMaxTokens: 8000,
inputPrice: 0.0006, // 输入 token 价格
outputPrice: 0.002, // 输出 token 价格
isDefault: true,
description: `60 tokens/second, Enhanced capabilities,API compatibility intact`, // 描述
capabilities: { // 模型能力
tools: {
enabled: true,
},
},
},
]
}
好处:不同的大模型提供商、甚至不同的大模型的 tool_call 格式、以及传参可能都有细微的差别,我们将通用的处理模式全部内聚到一个 provider 里面,后续如果有定制化的需求,直接开放配置出来,让二开用户传入自己的 provider 即可处理 tool_call 格式、传参的相关差异。 总结:增强扩展性+高内聚
|
It feels like the code structure can be enhanced to enhance the scalability and maintainability of the plug-in, such as:
Benefits: Centralize the static configuration of large model providers and models, making it easy to read and maintain; it also facilitates subsequent centralized provision of configurations for adding or hiding Enhanced example structure: export default {
name: 'DeepSeek',
apiBase: 'https://api.deepseek.com/v1',
models: [
{
id: 'deepseek-chat',
name: 'deepseek-chat',
contextWindow: 65536, //Context size
maxTokens: 8192,
defaultMaxTokens: 8000,
inputPrice: 0.0006, //Input token price
outputPrice: 0.002, // Output token price
isDefault: true,
description: `60 tokens/second, Enhanced capabilities,API compatibility intact`, // description
capabilities: { // Model capabilities
tools: {
enabled: true,
},
},
},
]
}
Benefits: Different large model providers, or even different large models, may have subtle differences in the tool_call format and parameter passing. We have integrated all common processing modes into one provider. If there is a need for customization in the future, the configuration can be directly opened, allowing secondary users to pass in their own providers to handle the differences in tool_call format and parameter passing. Summary: Enhanced scalability + high cohesion
|
cd3d8c0 to
8005437
Compare
f8a401a to
0d7ca22
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 21
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/plugins/robot/src/components/McpServer.vue (1)
34-81: MakeactiveCountreactive to picker state.
activeCountis initialized withref(1)and never updated, even though the picker exposesv-model:activeCount. The template now binds that model directly toactiveCount, so you must remove the hard-coded default and rely on the picker to drive the value (or initialize it from real data) to avoid incorrect badge states.packages/plugins/robot/src/components/RobotSettingPopover.vue (1)
204-220: Restorestate.modelOptionsremoval side-effect.
state.modelOptionswas removed in favor of computedmodelOptions, butchangeBaseUrlstill assigns tostate.modelOptions, leavingstate.existFormData.modelreading fromundefined[0]on the next line. Update the assignment to usemodelOptions.value(or remove the stale line) so selecting a provider always seeds the model dropdown correctly.
🧹 Nitpick comments (26)
packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue (1)
79-79: Consider using optional chaining for consistency.While
nameis guaranteed to exist inSvgICons(since it's derived fromObject.keys(SvgICons)), applying optional chaining here would maintain consistency with lines 57 and 61.Apply this diff:
- component: name && SvgICons[name]() + component: name && SvgICons[name]?.()designer-demo/src/composable/http/index.js (1)
19-19: Good UX improvement; consider a more robust cancellation check.Silencing canceled requests reduces noise and is appropriate. However, the string comparison
message === 'canceled'is fragile—Axios may use different messages across versions or locales (e.g., 'cancelled'), and legitimate errors containing "canceled" could be suppressed.Consider refactoring
showErrorto accept the full error object and use Axios's built-in cancellation check:-const showError = (url, message) => { - if (message === 'canceled') return // 取消请求场景不报错 +import axios from 'axios' + +const showError = (url, error) => { + if (axios.isCancel(error)) return // 取消请求场景不报错 + const message = error?.message || error globalNotify({ type: 'error', title: '接口报错',Then update the call sites:
const preResponse = (res) => { if (res.data?.error) { - showError(res.config?.url, res?.data?.error?.message) + showError(res.config?.url, res?.data?.error) return Promise.reject(res.data.error) }const errorResponse = (error) => { ... - showError(error.config?.url, error?.message) + showError(error.config?.url, error) return response?.data.error ? Promise.reject(response.data.error) : Promise.reject(error.message)packages/plugins/robot/package.json (1)
37-37: Standardize version specifiers for consistency.The dependencies use inconsistent version specifiers:
fast-json-patchuses~3.1.1(patch-level),jsonrepairuses exact3.13.0, andmarkdown-ituses^14.1.0(minor-level). For maintainability, adopt a consistent pinning strategy across similar utility dependencies.- "fast-json-patch": "~3.1.1", + "fast-json-patch": "^3.1.1", - "jsonrepair": "3.13.0", + "jsonrepair": "^3.13.0",Also applies to: 39-40
packages/plugins/robot/src/types/types.ts (1)
3-8: Clarify the distinction betweenurlandbaseUrl.RequestOptions now contains both
url?: string(line 4) andbaseUrl?: string(line 7). Additionally, LLMRequestBody (line 45) also hasbaseUrl?: string. This creates potential confusion about their roles and precedence. Consider:
- If
baseUrlis the API endpoint root andurlis a path, document this clearly.- If they serve the same purpose, remove the duplication.
packages/plugins/robot/src/components/FooterButton.vue (1)
17-32: Potential reactivity issue: destructured props are not reactive.In Vue 3
<script setup>, destructuring props (line 17) breaks reactivity. While the template binding at line 3 will work correctly, ifhandleVisibleToggleor any other composition logic directly accessesactive, it won't be reactive.Consider using
defineProps()without destructuring:-const { active, tooltipContent } = defineProps({ +const props = defineProps({ active: { type: Boolean, default: false }, tooltipContent: { type: String, default: '' } }) const emit = defineEmits(['update:active']) const handleVisibleToggle = () => { - emit('update:active', !active) + emit('update:active', !props.active) }packages/plugins/robot/index.ts (1)
25-25: Consider guarding debug initialization for production.
initDebugWindow()is called unconditionally at module load. Consider wrapping it in a development-only guard to avoid debug overhead in production builds.-initDebugWindow() +if (import.meta.env.DEV) { + initDebugWindow() +}packages/plugins/robot/src/composables/agent.ts (6)
19-24: Add type safety tofixIconComponent.Line 20 accesses
data?.componentNamewithout a type guard, and line 21 assigns todata.props.nameassuming the shape is correct. Ifdatadoesn't match the expected structure, this could cause runtime errors.Consider adding a type guard:
-const fixIconComponent = (data: unknown) => { - if (data?.componentName === 'Icon' && data.props?.name && !SvgICons[data.props.name as keyof typeof SvgICons]) { +const fixIconComponent = (data: any) => { + if ( + isPlainObject(data) && + data.componentName === 'Icon' && + data.props?.name && + typeof data.props.name === 'string' && + !SvgICons[data.props.name as keyof typeof SvgICons] + ) { data.props.name = 'IconWarning' logger.log('autofix icon to warning:', data) } }
29-34: Add type safety tofixComponentName.Line 30 assigns
data.componentName = 'div'without proper type checking. TypeScript will allow this onobjecttype, but it's not type-safe.-const fixComponentName = (data: object) => { - if (isPlainObject(data) && !data.componentName) { +const fixComponentName = (data: any) => { + if (isPlainObject(data) && !data.componentName) { data.componentName = 'div' logger.log('autofix component to div:', data) } }
42-42: Add explicit type to child parameter.Line 42 uses
anytype for thechildparameter. Consider defining a proper Schema type.- data.children.forEach((child: any) => schemaAutoFix(child)) + data.children.forEach((child: unknown) => schemaAutoFix(child))
46-53: Add explicit types to arrow function parameters.Lines 48-49 define an arrow function with implicit
anytypes forpatch,index, andarr.const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => { // 流式渲染过程中,画布只渲染children字段,避免不完整的methods/states/css等字段导致解析报错 - const childrenFilter = (patch, index, arr) => + const childrenFilter = (patch: any, index: number, arr: any[]) => isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children')) const validJsonPatches = jsonPatches.filter(childrenFilter).filter(useRobot().isValidFastJsonPatch) return validJsonPatches }
86-95: Clarify boolean parameters inapplyPatch.Line 88 calls
jsonpatch.applyPatch(acc, [patch], false, false)with two boolean parameters. Without context, it's unclear what these flags control. Consider adding comments or using named options if the library supports them.try { - return jsonpatch.applyPatch(acc, [patch], false, false).newDocument + // Parameters: validateOperation=false, mutateDocument=false + return jsonpatch.applyPatch(acc, [patch], false, false).newDocument } catch (error) {
111-127: Silent error swallowing insearchfunction.The
searchfunction (lines 111-127) silently catches and ignores all errors at line 123-125, which makes debugging difficult. Consider logging the error or propagating it to the caller.} catch (error) { - // error + logger.error('Search API request failed:', error) } return result }packages/plugins/robot/src/prompts/agent-prompt-en.md (1)
46-76: Fix Markdown list indentation for critical constraints.The nested bullet lists under “Constraint Rules” violate Markdown indentation (MD007) and render incorrectly, which muddles non-negotiable output rules for the agent. Normalize list indentation so every sub-list is indented by two spaces per level; this keeps the rendered instructions clear and prevents lint failures.
packages/plugins/robot/src/components/RobotChat.vue (2)
326-333: Improve conversation title generation.The automatic title generation uses a simple 20-character substring without considering word boundaries or multi-byte characters. For structured content (when
messageContentis an object),JSON.stringify(messageContent).substring(0, 20)may produce an incomplete or invalid fragment like{"type":"image_url",.Consider using a more robust approach:
const currentTitle = conversationState.conversations.find( (conversation) => conversation.id === conversationState.currentId )?.title const DEFAULT_TITLE = '新会话' if (currentTitle === DEFAULT_TITLE && conversationState.currentId) { - const contentStr = typeof messageContent === 'string' ? messageContent : JSON.stringify(messageContent) - updateTitle(conversationState.currentId, contentStr.substring(0, 20)) + let titleStr = typeof messageContent === 'string' ? messageContent : '图文消息' + // Trim to 20 chars at word boundary + if (titleStr.length > 20) { + titleStr = titleStr.substring(0, 20).trim() + '...' + } + updateTitle(conversationState.currentId, titleStr) }
91-363: Consider extracting business logic from the component.The component handles UI rendering, file uploads, message formatting, history management, and error handling all in one place. While functional, this makes the component harder to test and maintain.
Consider extracting the following into separate composables:
- File attachment handling (lines 163-218)
- Message formatting and sending logic (lines 273-343)
- History interaction handlers (lines 255-268)
This would align with the composable-driven architecture introduced elsewhere in this PR and improve testability.
packages/plugins/robot/src/utils/common-utils.ts (1)
32-48: Centralize model configuration defaults.The function hardcodes
'deepseek-chat'as the default model (Line 35) and uses a fixed endpoint. Per the PR comments suggesting centralized model/provider configuration, these defaults should come from a shared configuration source rather than being scattered across utility functions.Consider importing defaults from a centralized config:
Based on PR comments.
+import { DEFAULT_MODEL, DEFAULT_CHAT_ENDPOINT } from '../config/model-config' + export const fetchLLM = async (messages: LLMMessage[], tools: RequestTool[], options: RequestOptions = {}) => { const bodyObj: LLMRequestBody = { baseUrl: options.baseUrl, - model: options?.model || 'deepseek-chat', + model: options?.model || DEFAULT_MODEL, stream: false, messages: toRaw(messages) } if (tools.length > 0) { bodyObj.tools = toRaw(tools) } - return getMetaApi(META_SERVICE.Http).post(options?.url || '/app-center/api/chat/completions', bodyObj, { + return getMetaApi(META_SERVICE.Http).post(options?.url || DEFAULT_CHAT_ENDPOINT, bodyObj, {packages/plugins/robot/src/composables/useChat.ts (3)
204-223: Add circular reference protection to recursive merge.The
mergeStringFieldsfunction recursively merges objects without protection against circular references. While tool_calls data from LLM responses typically won't have circular structures, defensive coding suggests adding a WeakSet to track visited objects./** * 合并字符串字段。如果值是对象,则递归合并字符串字段 * @param target 目标对象 * @param source 源对象 + * @param visited WeakSet to track visited objects and prevent circular references * @returns 合并后的对象 */ -const mergeStringFields = (target: Record<string, any>, source: Record<string, any>) => { +const mergeStringFields = ( + target: Record<string, any>, + source: Record<string, any>, + visited: WeakSet<object> = new WeakSet() +) => { + if (visited.has(source)) return target + visited.add(source) + for (const [key, value] of Object.entries(source)) { const targetValue = target[key] if (targetValue) { if (typeof targetValue === 'string' && typeof value === 'string') { // 都是字符串,直接拼接 target[key] = targetValue + value } else if (targetValue && typeof targetValue === 'object' && value && typeof value === 'object') { // 都是对象,递归合并 - target[key] = mergeStringFields(targetValue, value) + target[key] = mergeStringFields(targetValue, value, visited) } } else {
252-347: Ensure AbortController cleanup and consider splitting function.The
afterToolCallAbortControllercreated at Line 262 is not explicitly cleaned up in all paths. While JavaScript GC will eventually collect it, explicitly setting it tonullafter completion improves clarity and prevents potential memory leaks if references are held elsewhere.Additionally, this 95-line function handles both tool execution (lines 267-304) and subsequent streaming (lines 308-346). Consider splitting into:
executeToolCalls(tool_calls, messages, contextMessages)streamToolCallResponse(toolMessages, currentMessage, messages)For the cleanup issue:
onDone: async () => { removeLoading(messages, 'latest') const toolCalls = messages.at(-1)!.tool_calls if (toolCalls?.length) { await handleToolCall(toolCalls, messages, toolMessages) } else { + afterToolCallAbortController = null getMessageManager().messageState.status = STATUS.FINISHED } }
349-361: Clarify conversation creation logic in mode switching.The logic for when to create a new conversation versus updating the existing one is unclear. Line 353 checks if
usedConversationId === newConversationId, which would only be true ifcreateConversationreturned the existing conversation ID (implying the conversation was empty and reused).Consider adding a comment explaining this behavior, or refactoring to be more explicit:
const changeChatMode = (chatMode: string) => { - // 空会话更新metadata const usedConversationId = conversationState.currentId const newConversationId = createConversation('新会话', { chatMode }) + // If the conversation was reused (empty), update its metadata + // Otherwise, the new conversation already has the correct metadata if (usedConversationId === newConversationId) { rest.updateMetadata(newConversationId, { chatMode }) rest.saveConversations() }packages/plugins/robot/src/Main.vue (1)
198-202: Add a comment explaining the teleport timing logic.The code combines three timing mechanisms (
deferattribute,v-if="showTeleport", and a 1000ms delay) without explanation. While the target element.tiny-engine-right-robotexists in the layout component, the hardcoded delay appears redundant with Vue'sdefersupport and warrants clarification.Add a comment explaining whether:
- The delay is needed due to specific component initialization order
- Both
deferand the timeout are intentional or if one can be removed- The 1000ms value is based on measured timing requirements or conservative estimation
Consider whether
nextTickcombined with a DOM observer or event-based approach would be more deterministic than a hardcoded delay.packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (4)
51-81: Constructor looks good with minor documentation need.The validation logic correctly ensures
axiosClientis provided whenhttpClientTypeis 'axios'. However, thebeforeRequesthook lacks documentation about what transformations are safe and whether async operations are fully supported in all code paths.Consider adding JSDoc examples for the
beforeRequesthook:/** * @param config AI模型配置 - * @param options 额外选项 + * @param options 额外选项 + * @param options.beforeRequest 请求前处理钩子,可用于添加自定义参数或修改请求体。支持同步和异步函数。 + * @example + * new OpenAICompatibleProvider(config, { + * beforeRequest: (request) => ({ ...request, temperature: 0.7 }) + * }) */
169-249: Consider consolidating fetch logic to reduce duplication.Both
createFetchAdapter(lines 178-183) andsendFetchRequest(lines 236-241) perform nearly identical fetch calls with the same headers, body serialization, error checks, and response handling. This duplication increases maintenance burden.Refactor to a shared helper:
+ private async executeFetch( + url: string, + requestData: ChatRequestData, + headers: Record<string, string>, + signal?: AbortSignal + ): Promise<Response> { + const response = await fetch(url, { + method: 'POST', + headers, + body: JSON.stringify(requestData), + signal + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`) + } + + return response + } private sendFetchRequest(...): Promise<Response> { - const response = await fetch(this.baseURL, { - method: 'POST', - headers, - body: JSON.stringify(requestData), - signal - }) - ... + return this.executeFetch(this.baseURL, requestData, headers, signal) }Then simplify
createFetchAdapterto callexecuteFetch.
255-276: Custom adapter forces fetch even when using axios.The method always injects
createFetchAdapter(line 271), which means axios never uses its native request adapters (Node.js http/https or browser XMLHttpRequest). This undermines the flexibility of thehttpClientTypeoption.If the goal is to support axios-specific features (interceptors, custom adapters, etc.), consider making the fetch adapter optional:
private async sendAxiosRequest(...): Promise<unknown> { ... const requestOptions: AxiosRequestConfig = { method: 'POST', url: this.baseURL, headers, data: requestData, - signal, - adapter: this.createFetchAdapter(requestData, isStream) + signal } + + // Only inject fetch adapter if axios doesn't have native browser support + if (typeof window === 'undefined' || isStream) { + requestOptions.adapter = this.createFetchAdapter(requestData, isStream) + } const axiosClient = typeof this.axiosClient === 'function' ? this.axiosClient() : this.axiosClientAlternatively, clarify in documentation that axios mode still uses fetch under the hood.
309-336: Streaming implementation works but has complex response extraction for axios.Lines 320-322 perform nested response extraction that is hard to follow:
const fetchResponse = ( (response as { data: { response: Response } }).data || (response as { response: Response }) ).responseThis suggests uncertainty about the axios response structure when using a custom adapter.
Simplify with explicit checks:
const response = await this.sendAxiosRequest(requestData, headers, true, signal) - const fetchResponse = ( - (response as { data: { response: Response } }).data || (response as { response: Response }) - ).response + // Extract Response from axios wrapper + const axiosData = (response as any).data + const fetchResponse = (axiosData?.response || (response as any).response) as Response + if (!fetchResponse || typeof fetchResponse.body === 'undefined') { + throw new Error('Invalid streaming response structure from axios') + } await handleSSEStream(fetchResponse, handler, signal)The abort handling (lines 331-333) is good—silent return prevents noisy errors from user cancellations.
packages/plugins/robot/src/composables/useRobot.ts (2)
134-138: Consider adding error handling for localStorage quota limits.
localStorage.setItemcan throwQuotaExceededErrorif storage is full. While rare, it can cause unexpected errors.Add graceful degradation:
const saveRobotSettingState = (state: object) => { const currentState = loadRobotSettingState() || {} const newState = { ...currentState, ...state } - localStorage.setItem(SETTING_STORAGE_KEY, JSON.stringify(newState)) + try { + localStorage.setItem(SETTING_STORAGE_KEY, JSON.stringify(newState)) + } catch (error) { + console.error('Failed to save robot settings:', error) + } }
157-198: Consider moving JSON Patch validation to a separate utility.These validation functions are generic and unrelated to robot settings or model configuration. Placing them in
useRobot.tsreduces discoverability and reusability.Extract to a dedicated file:
// packages/plugins/robot/src/utils/json-patch-validator.ts export const isValidOperation = (operation: unknown): boolean => { // ... current implementation } export const isValidFastJsonPatch = (patch: unknown): boolean => { // ... current implementation }Then import in
useRobot.ts:+import { isValidOperation, isValidFastJsonPatch } from '../utils/json-patch-validator' -const isValidOperation = (operation: object) => { ... } -const isValidFastJsonPatch = (patch) => { ... }This improves modularity and allows other modules to validate patches without importing robot settings.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
packages/plugins/robot/assets/failed.svgis excluded by!**/*.svgpackages/plugins/robot/assets/success.svgis excluded by!**/*.svgpackages/plugins/robot/assets/test.pngis excluded by!**/*.png
📒 Files selected for processing (37)
designer-demo/src/composable/http/index.js(1 hunks)docs/extension-capabilities-tutorial/ai-plugin-configuration.md(1 hunks)packages/common/js/completion.js(1 hunks)packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue(1 hunks)packages/layout/src/DesignSettings.vue(0 hunks)packages/layout/src/Main.vue(1 hunks)packages/plugins/robot/index.ts(1 hunks)packages/plugins/robot/package.json(1 hunks)packages/plugins/robot/src/BuildLoadingRenderer.vue(0 hunks)packages/plugins/robot/src/Main.vue(2 hunks)packages/plugins/robot/src/client/OpenAICompatibleProvider.ts(1 hunks)packages/plugins/robot/src/client/index.ts(1 hunks)packages/plugins/robot/src/components/AgentRenderer.vue(1 hunks)packages/plugins/robot/src/components/FooterButton.vue(1 hunks)packages/plugins/robot/src/components/ImgRenderer.vue(1 hunks)packages/plugins/robot/src/components/McpServer.vue(1 hunks)packages/plugins/robot/src/components/RobotChat.vue(1 hunks)packages/plugins/robot/src/components/RobotSettingPopover.vue(10 hunks)packages/plugins/robot/src/components/RobotTypeSelect.vue(3 hunks)packages/plugins/robot/src/composables/agent.ts(1 hunks)packages/plugins/robot/src/composables/useChat.ts(1 hunks)packages/plugins/robot/src/composables/useMcp.ts(2 hunks)packages/plugins/robot/src/composables/useRobot.ts(1 hunks)packages/plugins/robot/src/icons/mcp-icon.vue(0 hunks)packages/plugins/robot/src/icons/page-icon.vue(0 hunks)packages/plugins/robot/src/icons/study-icon.vue(0 hunks)packages/plugins/robot/src/js/prompts.ts(0 hunks)packages/plugins/robot/src/js/useRobot.ts(0 hunks)packages/plugins/robot/src/js/utils.ts(0 hunks)packages/plugins/robot/src/mcp/utils.ts(0 hunks)packages/plugins/robot/src/prompts/agent-prompt-en.md(1 hunks)packages/plugins/robot/src/prompts/components.json(1 hunks)packages/plugins/robot/src/prompts/examples.json(1 hunks)packages/plugins/robot/src/prompts/index.ts(1 hunks)packages/plugins/robot/src/types/mcp-types.ts(1 hunks)packages/plugins/robot/src/types/types.ts(1 hunks)packages/plugins/robot/src/utils/common-utils.ts(1 hunks)
💤 Files with no reviewable changes (9)
- packages/plugins/robot/src/icons/page-icon.vue
- packages/plugins/robot/src/js/prompts.ts
- packages/plugins/robot/src/icons/study-icon.vue
- packages/plugins/robot/src/icons/mcp-icon.vue
- packages/plugins/robot/src/BuildLoadingRenderer.vue
- packages/plugins/robot/src/js/utils.ts
- packages/plugins/robot/src/mcp/utils.ts
- packages/plugins/robot/src/js/useRobot.ts
- packages/layout/src/DesignSettings.vue
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-01-14T08:42:18.574Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1038
File: packages/plugins/block/index.js:24-24
Timestamp: 2025-01-14T08:42:18.574Z
Learning: In the tiny-engine project, breaking changes are documented in the changelog rather than in JSDoc comments or separate migration guides.
Applied to files:
packages/plugins/robot/src/prompts/agent-prompt-en.mddocs/extension-capabilities-tutorial/ai-plugin-configuration.mdpackages/plugins/robot/package.json
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.
Applied to files:
packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vuepackages/plugins/robot/src/components/RobotTypeSelect.vuepackages/plugins/robot/package.json
📚 Learning: 2024-10-15T02:45:17.168Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 830
File: packages/common/component/MetaChildItem.vue:50-56
Timestamp: 2024-10-15T02:45:17.168Z
Learning: In `packages/common/component/MetaChildItem.vue`, when checking if `text` is an object in the computed property `title`, ensure that `text` is not `null` because `typeof null === 'object'` in JavaScript. Use checks like `text && typeof text === 'object'` to safely handle `null` values.
Applied to files:
packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.
Applied to files:
packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vuepackages/plugins/robot/package.json
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: The SvgIcon component is globally registered and available throughout the application without requiring explicit imports.
Applied to files:
packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.
Applied to files:
packages/plugins/robot/src/components/RobotTypeSelect.vuepackages/plugins/robot/src/components/RobotSettingPopover.vue
📚 Learning: 2025-01-14T04:25:46.281Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/material-function/material-getter.ts:66-80
Timestamp: 2025-01-14T04:25:46.281Z
Learning: In the tiny-engine project, styles from block components are processed through Vite's CSS compilation pipeline, and additional style sanitization libraries should be avoided to maintain consistency with this approach.
Applied to files:
packages/layout/src/Main.vuepackages/plugins/robot/package.json
📚 Learning: 2025-01-14T10:06:25.508Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1041
File: packages/plugins/datasource/src/DataSourceList.vue:138-138
Timestamp: 2025-01-14T10:06:25.508Z
Learning: PR #1041 in opentiny/tiny-engine is specifically for reverting Prettier v3 formatting to v2, without any logical code changes or syntax improvements.
Applied to files:
packages/layout/src/Main.vue
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
Repo: opentiny/tiny-engine PR: 837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
Applied to files:
packages/layout/src/Main.vuepackages/plugins/robot/package.json
📚 Learning: 2025-03-19T03:13:51.520Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1226
File: packages/canvas/container/src/components/CanvasDivider.vue:184-185
Timestamp: 2025-03-19T03:13:51.520Z
Learning: The CSS bug in packages/canvas/container/src/components/CanvasDivider.vue where verLeft already includes "px" but is being appended again in the style object will be fixed in a future update, as confirmed by gene9831.
Applied to files:
packages/layout/src/Main.vue
📚 Learning: 2025-01-14T04:22:02.404Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/builtin/builtin.json:645-850
Timestamp: 2025-01-14T04:22:02.404Z
Learning: In TinyEngine, components must use inline styles instead of CSS classes because components cannot carry class styles when dragged into the canvas.
Applied to files:
packages/layout/src/Main.vuepackages/plugins/robot/src/prompts/components.json
📚 Learning: 2025-07-03T09:22:59.512Z
Learnt from: hexqi
Repo: opentiny/tiny-engine PR: 1501
File: mockServer/src/tool/Common.js:79-82
Timestamp: 2025-07-03T09:22:59.512Z
Learning: In the tiny-engine project, the mockServer code uses ES6 import syntax but is compiled to CommonJS output. This means CommonJS globals like `__dirname` are available at runtime, while ES6 module-specific features like `import.meta` would cause runtime errors.
Applied to files:
packages/plugins/robot/index.tspackages/plugins/robot/package.json
📚 Learning: 2024-12-14T05:53:28.501Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 917
File: docs/开始/快速上手.md:31-31
Timestamp: 2024-12-14T05:53:28.501Z
Learning: The latest stable version of `opentiny/tiny-engine-cli` is `2.0.0`, and documentation should reference this version instead of any release candidates.
Applied to files:
packages/plugins/robot/package.json
🧬 Code graph analysis (8)
packages/plugins/robot/src/client/index.ts (1)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
OpenAICompatibleProvider(39-359)
packages/plugins/robot/src/composables/agent.ts (4)
packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/register/src/hooks.ts (1)
useHistory(82-82)packages/register/src/common.ts (1)
getMetaApi(20-30)packages/register/src/constants.ts (1)
META_SERVICE(1-24)
packages/plugins/robot/src/composables/useChat.ts (8)
packages/plugins/robot/src/types/mcp-types.ts (3)
ResponseToolCall(49-55)LLMMessage(29-33)RobotMessage(35-40)packages/register/src/common.ts (1)
getMetaApi(20-30)packages/register/src/constants.ts (1)
META_SERVICE(1-24)packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/plugins/robot/src/composables/useMcp.ts (1)
useMcpServer(126-137)packages/plugins/robot/src/prompts/index.ts (1)
getAgentSystemPrompt(30-51)packages/plugins/robot/src/composables/agent.ts (1)
updatePageSchema(109-109)packages/plugins/robot/src/utils/common-utils.ts (2)
formatMessages(8-18)serializeError(20-30)
packages/plugins/robot/src/utils/common-utils.ts (4)
packages/plugins/robot/src/types/types.ts (4)
LLMMessage(31-35)RequestTool(10-29)RequestOptions(3-8)LLMRequestBody(44-50)packages/plugins/robot/src/types/mcp-types.ts (4)
LLMMessage(29-33)RequestTool(9-27)RequestOptions(3-7)LLMRequestBody(42-47)packages/register/src/common.ts (1)
getMetaApi(20-30)packages/register/src/constants.ts (1)
META_SERVICE(1-24)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
packages/plugins/robot/src/utils/common-utils.ts (1)
formatMessages(8-18)
packages/plugins/robot/src/composables/useRobot.ts (1)
packages/register/src/common.ts (1)
getOptions(32-34)
packages/plugins/robot/src/composables/useMcp.ts (4)
packages/plugins/robot/src/types/types.ts (1)
RequestTool(10-29)packages/plugins/robot/src/types/mcp-types.ts (1)
RequestTool(9-27)packages/register/src/common.ts (1)
getMetaApi(20-30)packages/register/src/constants.ts (1)
META_SERVICE(1-24)
packages/plugins/robot/index.ts (1)
packages/plugins/robot/src/composables/index.ts (1)
RobotService(4-11)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/prompts/agent-prompt-en.md
46-46: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
47-47: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
48-48: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
49-49: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
50-50: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
51-51: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
52-52: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
53-53: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
54-54: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
55-55: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
56-56: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
57-57: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
58-58: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
59-59: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
60-60: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
61-61: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
62-62: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
63-63: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
64-64: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
65-65: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
66-66: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
67-67: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
68-68: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
69-69: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
70-70: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
71-71: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
72-72: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
73-73: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
74-74: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
75-75: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
76-76: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
77-77: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
78-78: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
79-79: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
80-80: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
81-81: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
82-82: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
83-83: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
84-84: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
85-85: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
86-86: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
93-93: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
98-98: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
103-103: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
110-110: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
120-120: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
125-125: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
130-130: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (16)
packages/layout/src/Main.vue (1)
153-158: CSS positioning context adjustment looks good.Adding
position: relativeto.tiny-engine-right-wrapcreates an explicit positioning context for the child<design-settings>component, which pairs with the removal of top-offset absolute positioning adjustments inDesignSettings.vue.Please verify that the coordinated layout changes in
DesignSettings.vue(removing top offset from absolute positioning) work correctly with this new relative positioning context. You can confirm this by testing that the right panel and settings components render and position correctly in the UI.packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue (1)
57-57: LGTM! Defensive programming improvement.The optional chaining prevents runtime errors when
SvgICons[props.modelValue]is undefined, making the component more resilient to invalid icon names.Also applies to: 61-61
packages/common/js/completion.js (1)
221-221: Good defensive coding practice.The
|| ''fallback prevents the literal strings "undefined" or "null" from appearing in the Authorization header. While technically redundant given the early return guard on line 199 (which already ensuresapiKeyis truthy), this defensive approach improves code resilience against future refactoring and doesn't introduce any issues.packages/plugins/robot/package.json (3)
35-35: Dependency is actively used in the refactored architecture.Verification confirms
@vueuse/coreis properly utilized inpackages/plugins/robot/src/composables/agent.ts: theuseThrottleFnfunction throttles theupdatePageSchemaexport (line 109) with a 200ms delay during schema updates. The agent.ts module is part of the composable-driven architecture and is imported by useChat.
29-29: The workspace dependency is correctly referenced, but a type-only circular reference exists.The robot plugin's dependency on
@opentiny/tiny-engine-meta-registeris properly configured and actively used across 8 source files (Main.vue, RobotSettingPopover.vue, composables, utils, etc.). However, the meta-register package imports a type from the robot plugin (RobotServicefrom@opentiny/tiny-engine-plugin-robotin packages/register/src/types.ts), creating a circular reference.While this is a type-only import (safe from runtime circular dependencies), it aligns with the known circular dependency risks in tiny-engine projects mentioned in your learnings. Verify that your build system and module resolution handle this pattern correctly, or consider refactoring to move shared types to a neutral package if issues arise.
31-33: Verify RC version compatibility through manual testing.The tiny-robot packages are updated to 0.3.0-rc.5 from 0.3.0-rc.0. Code analysis confirms these packages are actively used throughout the codebase:
@opentily/tiny-robot: BubbleContentItem, PromptProps, BubbleRoleConfig, McpServerPicker, PluginInfo@opentiny/tiny-robot-kit: AIClient, AIModelConfig, BaseModelProvider, StreamHandler, ChatMessage, GeneratingStatus (used in provider integration, streaming, and tool calling)@opentiny/tiny-robot-svgs: Icon componentsNo official changelog is available for these RC versions. Given the major refactoring in this PR and the use of RC versions, manually verify that the upgrade doesn't introduce API breaking changes, particularly around streaming responses, tool/plugin integration, and model configuration. Also note: @vueuse/core addition is justified (useThrottleFn is used in agent.ts).
docs/extension-capabilities-tutorial/ai-plugin-configuration.md (1)
19-19: LGTM!The documentation path update correctly reflects the migration from
src/js/useRobot.tstosrc/composables/useRobot.ts.packages/plugins/robot/src/components/FooterButton.vue (1)
116-125: Verify parent container hascontainer-typeset.The
@containerquery (line 116) requires the parent element to havecontainer-type: inline-sizeorcontainer-nameset. Ensure the parent component using FooterButton sets this CSS property, otherwise the responsive behavior won't activate.packages/plugins/robot/src/components/ImgRenderer.vue (1)
1-26: LGTM!The ImgRenderer component is well-structured, with clear prop definitions and appropriate conditional rendering. The use of
v-ifensures the TinyImage only renders when content is available.packages/plugins/robot/src/prompts/components.json (1)
1-998: Static component catalog looks well-structured.This comprehensive JSON catalog serves as a component registry for the AI agent. The structure is consistent across entries, with each component providing metadata, properties, events, and demo schemas. No structural issues detected.
packages/plugins/robot/src/utils/common-utils.ts (2)
8-18: LGTM: Message formatting is clean and correct.The function properly unwraps Vue reactivity with
toRaw, filters out invalid messages, and maps to a clean structure for the LLM API. The conditional spreading oftool_callsandtool_call_idis a good practice to avoid sending undefined fields.
50-79: Add null check for regex match result.Line 68 performs a regex match but Line 71 accesses
dataMatch[1]without checking ifdataMatchisnull. If the SSE format is malformed, this will throw a runtime error despite the try-catch block (the error would be caught, but it's better to handle it explicitly).Apply this diff:
try { // 解析SSE消息 const dataMatch = line.match(/^data: (.+)$/m) - if (!dataMatch) continue + if (!dataMatch || !dataMatch[1]) continue const data = JSON.parse(dataMatch[1]) handler.onData(data)Additionally, Line 54's
lines.pop()appears to remove a trailing empty element but the result is unused—consider adding a comment explaining this is intentional, or uselines.slice(0, -1)for clarity.const lines = data.split('\n\n') - lines.pop() + lines.pop() // Remove trailing empty element after final \n\nLikely an incorrect or invalid review comment.
packages/plugins/robot/src/composables/useChat.ts (1)
119-162: Streaming delta handling is well-structured.The
onReceiveDatahandler effectively processes streaming responses, handling reasoning content, regular content, and tool_calls. The use ofpreventDefault()to override default behavior and the status tracking viachatStatusare good patterns.The integration with
updatePageSchema(Line 138) demonstrates the agent-mode functionality, though per the PR comments, further separation of agent vs chat mode logic into dedicated processors would improve maintainability.packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (2)
152-163: LGTM—clean request preparation with proper fallbacks.The method correctly strips Vue reactivity with
toRaw, sanitizes messages viaformatMessages, and applies a three-tier model fallback. ThebeforeRequesthook is awaited, supporting async transformations.
283-302: Tool_call handling is currently in useChat.ts—future refactor suggested but not required.The non-streaming chat implementation at lines 283-302 correctly handles both axios and fetch paths with appropriate type assertions. Error handling properly wraps and rethrows exceptions.
Current architecture places tool_call processing in the composable layer (useChat.ts, lines 225-305). Moving this logic into the provider would improve cohesion and centralize provider-specific tool formatting. However, this is a future architectural improvement and not a blocker for the current implementation.
packages/plugins/robot/src/composables/useRobot.ts (1)
200-214: LGTM—standard Vue 3 composable pattern.The factory function cleanly exposes the public API. The reactive
robotSettingStateallows components to observe configuration changes.
| private toAIAdapterError(error: unknown): AIAdapterError { | ||
| if (error instanceof Error) { | ||
| // 根据错误消息判断错误类型 | ||
| const message = error.message.toLowerCase() | ||
| let type = ErrorType.UNKNOWN_ERROR | ||
| let statusCode: number | undefined | ||
|
|
||
| if (message.includes('http error')) { | ||
| const statusMatch = message.match(/status:\s*(\d+)/) | ||
| if (statusMatch) { | ||
| statusCode = parseInt(statusMatch[1], 10) | ||
| if (statusCode === 401 || statusCode === 403) { | ||
| type = ErrorType.AUTHENTICATION_ERROR | ||
| } else if (statusCode === 429) { | ||
| type = ErrorType.RATE_LIMIT_ERROR | ||
| } else if (statusCode >= 500) { | ||
| type = ErrorType.SERVER_ERROR | ||
| } else { | ||
| type = ErrorType.NETWORK_ERROR | ||
| } | ||
| } | ||
| } else if (message.includes('network') || message.includes('fetch')) { | ||
| type = ErrorType.NETWORK_ERROR | ||
| } else if (message.includes('timeout')) { | ||
| type = ErrorType.TIMEOUT_ERROR | ||
| } | ||
|
|
||
| return { | ||
| type, | ||
| message: error.message, | ||
| statusCode, | ||
| originalError: error | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| type: ErrorType.UNKNOWN_ERROR, | ||
| message: String(error) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error type detection relies on fragile string matching.
The method infers error types by searching for substrings like "http error", "network", "fetch", and "timeout" in lowercased error messages. This approach is brittle—error messages can vary across browsers, fetch implementations, and network stacks.
Consider enhancing error detection:
private toAIAdapterError(error: unknown): AIAdapterError {
+ // Check for specific error types first
+ if (error instanceof TypeError && error.message.includes('fetch')) {
+ return { type: ErrorType.NETWORK_ERROR, message: error.message, originalError: error }
+ }
+ if (error instanceof DOMException && error.name === 'AbortError') {
+ return { type: ErrorType.TIMEOUT_ERROR, message: error.message, originalError: error }
+ }
+
if (error instanceof Error) {
const message = error.message.toLowerCase()Alternatively, preserve structured error info (status codes, error names) from the source and rely less on message parsing.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private toAIAdapterError(error: unknown): AIAdapterError { | |
| if (error instanceof Error) { | |
| // 根据错误消息判断错误类型 | |
| const message = error.message.toLowerCase() | |
| let type = ErrorType.UNKNOWN_ERROR | |
| let statusCode: number | undefined | |
| if (message.includes('http error')) { | |
| const statusMatch = message.match(/status:\s*(\d+)/) | |
| if (statusMatch) { | |
| statusCode = parseInt(statusMatch[1], 10) | |
| if (statusCode === 401 || statusCode === 403) { | |
| type = ErrorType.AUTHENTICATION_ERROR | |
| } else if (statusCode === 429) { | |
| type = ErrorType.RATE_LIMIT_ERROR | |
| } else if (statusCode >= 500) { | |
| type = ErrorType.SERVER_ERROR | |
| } else { | |
| type = ErrorType.NETWORK_ERROR | |
| } | |
| } | |
| } else if (message.includes('network') || message.includes('fetch')) { | |
| type = ErrorType.NETWORK_ERROR | |
| } else if (message.includes('timeout')) { | |
| type = ErrorType.TIMEOUT_ERROR | |
| } | |
| return { | |
| type, | |
| message: error.message, | |
| statusCode, | |
| originalError: error | |
| } | |
| } | |
| return { | |
| type: ErrorType.UNKNOWN_ERROR, | |
| message: String(error) | |
| } | |
| } | |
| private toAIAdapterError(error: unknown): AIAdapterError { | |
| // Check for specific error types first | |
| if (error instanceof TypeError && error.message.includes('fetch')) { | |
| return { type: ErrorType.NETWORK_ERROR, message: error.message, originalError: error } | |
| } | |
| if (error instanceof DOMException && error.name === 'AbortError') { | |
| return { type: ErrorType.TIMEOUT_ERROR, message: error.message, originalError: error } | |
| } | |
| if (error instanceof Error) { | |
| // 根据错误消息判断错误类型 | |
| const message = error.message.toLowerCase() | |
| let type = ErrorType.UNKNOWN_ERROR | |
| let statusCode: number | undefined | |
| if (message.includes('http error')) { | |
| const statusMatch = message.match(/status:\s*(\d+)/) | |
| if (statusMatch) { | |
| statusCode = parseInt(statusMatch[1], 10) | |
| if (statusCode === 401 || statusCode === 403) { | |
| type = ErrorType.AUTHENTICATION_ERROR | |
| } else if (statusCode === 429) { | |
| type = ErrorType.RATE_LIMIT_ERROR | |
| } else if (statusCode >= 500) { | |
| type = ErrorType.SERVER_ERROR | |
| } else { | |
| type = ErrorType.NETWORK_ERROR | |
| } | |
| } | |
| } else if (message.includes('network') || message.includes('fetch')) { | |
| type = ErrorType.NETWORK_ERROR | |
| } else if (message.includes('timeout')) { | |
| type = ErrorType.TIMEOUT_ERROR | |
| } | |
| return { | |
| type, | |
| message: error.message, | |
| statusCode, | |
| originalError: error | |
| } | |
| } | |
| return { | |
| type: ErrorType.UNKNOWN_ERROR, | |
| message: String(error) | |
| } | |
| } |
| "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" | ||
| } | ||
| ] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix example component IDs to satisfy schema rules.
Several component id values in this example ("25153243", "53222591", "f2525253", "3225416b" …) lack the required mix of uppercase letters, lowercase letters, and digits that Section 3.1 of the agent prompt mandates. Because these examples feed directly into the system prompt, they now teach the model an invalid pattern and will cause generated patches to fail schema validation. Please update every new component ID in this file to an 8-character string containing at least one uppercase letter, one lowercase letter, and one digit, and keep them unique (e.g., "a7Kp2sN9").
| const formatComponentsToJsonl = (components: any[]): string => { | ||
| return '```jsonl\n' + components.map((comp) => JSON.stringify(comp)).join('\n') + '\n```' | ||
| } | ||
|
|
||
| /** | ||
| * Format examples object to readable text | ||
| */ | ||
| const formatExamples = (examples: Record<string, any>): string => { | ||
| return Object.entries(examples) | ||
| .map(([_key, example]) => { | ||
| const { name, description, note, patch } = example | ||
| const header = `### ${name}\n${description ? `${description}\n` : ''}${note ? `**Note**: ${note}\n` : ''}` | ||
| const patchContent = JSON.stringify(patch) | ||
| return `${header}\n${patchContent}` | ||
| }) | ||
| .join('\n\n') | ||
| } | ||
|
|
||
| /** | ||
| * Generate agent system prompt with dynamic components and examples | ||
| */ | ||
| export const getAgentSystemPrompt = (currentPageSchema: object, referenceContext: string) => { | ||
| // Format components list | ||
| const ignoreComponents = ['TinyNumeric'] // 组件报错,先忽略 | ||
| const componentsList = formatComponentsToJsonl( | ||
| componentsData.filter((component) => !ignoreComponents.includes(component.component)) | ||
| ) | ||
|
|
||
| // Format examples section | ||
| const examplesSection = formatExamples(examplesData) | ||
|
|
||
| // Format current page schema | ||
| const currentPageSchemaStr = JSON.stringify(currentPageSchema) | ||
|
|
||
| // Replace all placeholders in the prompt template | ||
| const prompt = agentPrompt | ||
| .replace('{{COMPONENTS_LIST}}', componentsList) | ||
| .replace('{{EXAMPLES_SECTION}}', examplesSection) | ||
| .replace('{{CURRENT_PAGE_SCHEMA}}', currentPageSchemaStr) | ||
| .replace('{{REFERENCE_KNOWLEDGE}}', referenceContext || '') | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Escape braces when injecting JSON into Markdown.
formatExamples stringifies the patch array without escaping braces, so when injected into the Markdown template the raw {/} pairs can be interpreted as Markdown lists or template placeholders, breaking prompt formatting. Wrap the JSON in fenced code (or escape braces) before substitution to keep the agent prompt structurally sound.
🤖 Prompt for AI Agents
In packages/plugins/robot/src/prompts/index.ts around lines 9-49, the code
injects raw JSON into the Markdown prompt which allows unescaped `{`/`}` to be
treated as template/Markdown and break formatting; update formatExamples so the
patch JSON is wrapped in a fenced code block (e.g. ```json\n<JSON>\n```) instead
of inserting raw JSON, and ensure formatComponentsToJsonl already returns a
fenced jsonl block (or change it to do so) so both COMPONENTS_LIST and
EXAMPLES_SECTION are safe when .replace(...) injects them into the agent prompt.
| export interface RequestTool { | ||
| type: 'function' | ||
| function: { | ||
| name: string | ||
| description: string | ||
| parameters: { | ||
| type: 'object' | ||
| required?: string[] | ||
| properties: Record< | ||
| string, | ||
| { | ||
| type: string | ||
| description: string | ||
| [prop: string]: unknown | ||
| } | ||
| > | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Consolidate duplicated RequestTool type definition.
RequestTool is defined in both mcp-types.ts (lines 9-27) and types/types.ts with slight differences:
types/types.tsincludes an optionaltitle?: stringfield in the function objectmcp-types.tsomits this field
This duplication creates maintenance burden and potential type inconsistencies. The useMcp.ts file imports RequestTool from types/types.ts, while other files might use this version from mcp-types.ts.
Consolidate to a single definition in one shared location:
// In types/types.ts - keep this as the canonical definition
export interface RequestTool {
type: 'function'
function: {
name: string
description: string
title?: string // Keep the optional title field
parameters: {
type: 'object'
required?: string[]
properties: Record<string, {
type: string
description: string
[prop: string]: unknown
}>
}
}
}Then in mcp-types.ts:
+export type { RequestTool } from './types'
-export interface RequestTool {
- type: 'function'
- ...
-}🤖 Prompt for AI Agents
In packages/plugins/robot/src/types/mcp-types.ts around lines 9 to 27 you have a
duplicated RequestTool interface that omits the optional title field present in
types/types.ts; replace the local definition by removing the interface here and
importing RequestTool from the canonical types/types.ts, update any local
references to use the imported type, and ensure the shared definition in
types/types.ts includes the optional title?: string in the function object so
all consumers use the same shape.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
packages/plugins/robot/src/prompts/index.ts (1)
16-25: Consider consistent fencing for all JSON injections.While
formatComponentsToJsonlcorrectly wraps output in a fenced code block,formatExamples(line 21) and theCURRENT_PAGE_SCHEMAinjection (line 47) insert raw JSON into the Markdown template. Raw{/}characters can be misinterpreted as template placeholders or Markdown syntax. For consistency and safety, consider wrapping both in fenced code blocks.Apply this pattern to
formatExamples:const formatExamples = (examples: Record<string, any>): string => { return Object.entries(examples) .map(([_key, example]) => { const { name, description, note, patch } = example const header = `### ${name}\n${description ? `${description}\n` : ''}${note ? `**Note**: ${note}\n` : ''}` - const patchContent = JSON.stringify(patch) + const patchContent = '```json\n' + JSON.stringify(patch, null, 2) + '\n```' return `${header}\n${patchContent}` }) .join('\n\n') }And consider similar treatment for
CURRENT_PAGE_SCHEMAif appropriate for the LLM prompt structure.Also applies to: 30-52
🧹 Nitpick comments (3)
packages/plugins/resource/src/ResourceList.vue (1)
430-430: LGTM: Ensures consistent payload structure.The fallback to an empty string ensures the
descriptionfield is always present in the batch creation payload, handling cases where uploaded files or incomplete URL entries lack a description.Optional nitpick: Consider using the nullish coalescing operator (
??) instead of logical OR (||) for semantic precision:- description: item.description || '', + description: item.description ?? '',Both operators produce the same result here, but
??more clearly expresses the intent to default onlynullorundefinedvalues, whereas||also coerces other falsy values (though for string fields this distinction rarely matters).packages/plugins/robot/src/prompts/agent-prompt-en.md (1)
1-213: Consider addressing markdown linting issues for consistency.Static analysis flagged multiple formatting issues: inconsistent list indentation (MD007) and missing language specifiers for fenced code blocks (MD040). While these don't affect functionality, addressing them would improve document consistency and tooling compatibility.
packages/plugins/robot/src/composables/useChat.ts (1)
117-118: Consider encapsulating module-level state.Module-level mutable variables (
chatStatus,pageSchema,afterToolCallAbortController) complicate testing and prevent multiple independent instances. Consider moving them into the composable's closure or the conversation state.Also applies to: 251-251
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
packages/plugins/resource/src/ResourceList.vue(1 hunks)packages/plugins/robot/src/composables/agent.ts(1 hunks)packages/plugins/robot/src/composables/useChat.ts(1 hunks)packages/plugins/robot/src/prompts/agent-prompt-en.md(1 hunks)packages/plugins/robot/src/prompts/index.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/plugins/robot/src/composables/agent.ts (4)
packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/register/src/hooks.ts (1)
useHistory(82-82)packages/register/src/common.ts (1)
getMetaApi(20-30)packages/register/src/constants.ts (1)
META_SERVICE(1-24)
packages/plugins/robot/src/composables/useChat.ts (8)
packages/plugins/robot/src/types/mcp-types.ts (3)
ResponseToolCall(49-55)LLMMessage(29-33)RobotMessage(35-40)packages/register/src/common.ts (1)
getMetaApi(20-30)packages/register/src/constants.ts (1)
META_SERVICE(1-24)packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/plugins/robot/src/composables/useMcp.ts (1)
useMcpServer(126-137)packages/plugins/robot/src/composables/agent.ts (2)
fetchAssets(129-137)updatePageSchema(109-109)packages/plugins/robot/src/prompts/index.ts (1)
getAgentSystemPrompt(30-52)packages/plugins/robot/src/utils/common-utils.ts (2)
formatMessages(8-18)serializeError(20-30)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/prompts/agent-prompt-en.md
46-46: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
47-47: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
48-48: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
49-49: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
50-50: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
51-51: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
52-52: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
53-53: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
54-54: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
55-55: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
56-56: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
57-57: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
58-58: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
59-59: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
60-60: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
61-61: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
62-62: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
63-63: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
64-64: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
65-65: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
66-66: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
67-67: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
68-68: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
69-69: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
70-70: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
71-71: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
72-72: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
73-73: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
74-74: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
75-75: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
76-76: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
77-77: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
78-78: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
79-79: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
80-80: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
81-81: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
82-82: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
83-83: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
84-84: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
85-85: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
86-86: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
93-93: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
98-98: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
103-103: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
110-110: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
120-120: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
125-125: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
130-130: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (10)
packages/plugins/robot/src/composables/agent.ts (4)
19-34: LGTM!The helper functions provide sensible fallbacks for malformed schema data. The icon validation against
SvgIConsand the defaultcomponentNameassignment improve robustness.
55-107: Core streaming logic is well-structured.The function correctly handles progressive JSON patch parsing, validation, and application with appropriate error handling. The use of
jsonrepairfor non-final content and the recursive patch application with error recovery are solid design choices.
109-109: Good use of throttling for streaming updates.The 200ms throttle with leading edge enabled strikes a good balance between responsiveness and performance during streaming schema updates.
129-137: LGTM!The asset fetching logic correctly filters for items with descriptions and maps to the expected shape.
packages/plugins/robot/src/prompts/index.ts (1)
9-11: LGTM!Wrapping the JSONL output in a fenced code block ensures safe injection into the Markdown prompt template.
packages/plugins/robot/src/composables/useChat.ts (5)
1-52: Setup and configuration are well-structured.The module initialization, type definitions, and helper functions provide a clean foundation for the chat workflow. The
addSystemPromptlogic correctly handles both insertion and updates.
120-163: Event handlers correctly orchestrate the streaming workflow.The
onReceiveDataandonFinishhandlers properly manage streaming state, delta processing, and finalization. The conditional logic for agent mode vs. tool_calls is well-structured.
169-241: Delta handlers are well-designed.The incremental rendering strategy with
handleDeltaReasoning,handleDeltaContent, andhandleDeltaToolCallscleanly separates concerns. ThemergeStringFieldsrecursive merge is an elegant solution for assembling fragmented tool_call data.
253-348: Tool-calling flow is comprehensive and robust.The
handleToolCallfunction elegantly handles complex scenarios: tool execution, result streaming, abort signals, error recovery, and recursive tool chains. The separation of UI state management and message flow is well done.
350-396: Public API is well-designed.The composable's exported interface cleanly encapsulates conversation management, mode switching, and request control. The
abortRequestoverride correctly handles both the custom abort controller and the base message manager.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (6)
packages/plugins/robot/src/composables/agent.ts (2)
89-100: RenameisFinialtypo.Line 89 and Line 98 still expose the
isFinialparameter. This typo leaks into the public API and keeps propagating to call sites. Please rename it toisFinal(and update all references) for clarity and consistency.-const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => { +const jsonPatchAutoFix = (jsonPatches: any[], isFinal: boolean) => { // … - isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children')) + isFinal || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children')) const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch) return validJsonPatches } -const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => { +const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinal: boolean = false) => { // … - if (!isFinial) { + if (!isFinal) { content = jsonrepair(content) } - jsonPatches = JSON.parse(content) + jsonPatches = JSON.parse(content) } catch (error) { - if (isFinial) { + if (isFinal) { logger.error('parse json patch error:', error) } return { isError: true, error } } - if (!isFinial && !isValidFastJsonPatch(jsonPatches)) { + if (!isFinal && !isValidFastJsonPatch(jsonPatches)) { return { isError: true, error: 'format error: not a valid json patch.' } } - const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinial) + const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinal) // … - if (isFinial) { + if (isFinal) { useHistory().addHistory() }
166-168: Preserve search errors for observability.Line 167 silently swallows every exception. When META search fails we lose diagnostics, making field debugging painful. Please at least log via
logger.error(or the module logger) before returning so we keep a trace.- } catch (error) { - // error - } + } catch (error) { + logger.error('AI search failed:', error) + }packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
341-357: Make updateConfig honour httpClientType / axiosClient or narrow the signature.Line 341 still accepts
ProviderConfig, but the body only updatesapiUrl,apiKey, anddefaultModel;httpClientTypeandaxiosClientare silently ignored. That breaks the contract for callers that try to switch transports at runtime. Please either support those fields (including validation) or change the signature to exclude them so the API is truthful.packages/plugins/robot/src/Main.vue (1)
177-196: Add visible error feedback for file upload failures.The error handling in
handleFileSelected(lines 191-195) only logs to console and callsupdateAttachment(''). Users receive no visible indication that the upload failed. Consider importing and using a notification component (e.g.,MessageorNotifyfrom@opentiny/vue) to display an error message.Based on past review comments.
Apply this diff to add error notification:
+import { Message } from '@opentiny/vue' // ... in handleFileSelected catch block: } catch (error) { // eslint-disable-next-line no-console console.error('上传失败', error) updateAttachment('') + Message({ + type: 'error', + message: '文件上传失败,请重试' + }) }packages/plugins/robot/src/composables/useRobot.ts (2)
112-119: Fix error handling to return consistent type.When JSON parsing fails (line 117), the function returns
items(a string) instead of an object. Callers expect an object and may destructure it (line 127), causing runtime errors.Based on past review comments.
Apply this diff:
const loadRobotSettingState = () => { const items = localStorage.getItem(SETTING_STORAGE_KEY) || '{}' try { return JSON.parse(items) } catch (error) { - return items + console.warn('Failed to parse robot settings, using defaults:', error) + return {} } }
127-142: Guard against empty model options array.Lines 133, 135, 136, and 137 access
getAIModelOptions()[0]without checking if the array is empty. IfcustomCompatibleAIModelsremoves all default providers or returns an empty array, this will throw a runtime error.Based on past review comments.
Apply this diff to add a guard:
+const defaultOptions = getAIModelOptions() +if (defaultOptions.length === 0 || defaultOptions[0].models.length === 0) { + throw new Error('At least one AI model provider with models must be configured') +} + const robotSettingState = reactive({ selectedModel: { - label: storageSettingState.label || getAIModelOptions()[0].label, + label: storageSettingState.label || defaultOptions[0].label, activeName: activeName || EXISTING_MODELS, - baseUrl: storageSettingState.baseUrl || getAIModelOptions()[0].value, - model: storageSettingState.model || getAIModelOptions()[0].models[0].value, - completeModel: storageSettingState.completeModel || getAIModelOptions()[0].models[0].value || '', + baseUrl: storageSettingState.baseUrl || defaultOptions[0].baseUrl, + model: storageSettingState.model || defaultOptions[0].models[0].name, + completeModel: storageSettingState.completeModel || defaultOptions[0].models[0].name || '', apiKey: storageSettingState.apiKey || '' },Note: Also corrected field names from
valuetobaseUrlandname.
🧹 Nitpick comments (6)
packages/plugins/robot/src/prompts/agent-prompt.md (4)
40-86: Comprehensive constraint rules; consolidate duplicate ID requirements.The constraint rules are well-organized and provide clear error examples. However, the 8-character ID requirement is stated twice—once at line 86 and again at line 166 (in the spec section)—with slightly different phrasing. This duplication risks divergence if one is updated without the other.
Consider consolidating into a single, authoritative statement. Additionally, line 81 mentions CSS style string escaping but lacks a concrete example; adding one (e.g.,
".style { color: red; }\n.other { margin: 0; }") would improve clarity.
88-132: Practical error examples; minor coverage gap.The error examples effectively demonstrate JSON pitfalls. However, Example 4 shows only the "escape double quotes" approach; consider also showing the recommended alternative from line 80 (using single quotes in JS code):
{"value":"function test() { console.log('hello') }"}. This would help agents understand the preferred pattern.
45-86: Fix markdown list indentation violations (MD007).The nested list structure in the constraint rules uses 4–6 space indentation for visual emphasis, but markdownlint expects 0–2 space increments. While the current formatting aids readability of complex nested rules, it conflicts with standard markdown formatting.
Refactor to use consistent 2-space indentation increments per markdownlint (MD007):
Constraint Rules: - * **Strictly Prohibited**: - * Any explanatory text, preamble, or closing remarks (e.g., "Here's the JSON you requested...") + * **Strictly Prohibited**: + * Any explanatory text, preamble, or closing remarks (e.g., "Here's the JSON you requested...")Apply similar corrections throughout lines 46–86 to align all nested lists with 2-space increments.
93-93: Add language specifiers to fenced code blocks (MD040).Code blocks in the error examples section lack language identifiers (markdownlint MD040). Specify the appropriate language for each block (typically
textfor raw JSON output, orjsonfor JSON examples):-**❌ Wrong Example 1**: Using JavaScript template literals (causes JSON parse failure) -``` -{"value":"function test(name) { console.log(`hello ${name}`) }"} -``` +**❌ Wrong Example 1**: Using JavaScript template literals (causes JSON parse failure) +```json +{"value":"function test(name) { console.log(`hello ${name}`) }"} +```Apply similar corrections to all unmarked code blocks at lines 93, 98, 103, 110, 120, 125, and 130.
Also applies to: 98-98, 103-103, 110-110, 120-120, 125-125, 130-130
packages/plugins/robot/src/components/RobotSettingPopover.vue (1)
24-39: Consider extracting the duplicated label template.The label template with tooltip for "补全模型名称" is duplicated between the existing models tab (lines 24-39) and the customize tab (lines 76-92). Consider extracting this into a reusable component or template ref to reduce duplication.
Also applies to: 76-92
packages/plugins/robot/src/Main.vue (1)
155-161: Clean up placeholder code.The function
saveSettingState(line 161) is empty andhandleChatModeChangehas commented code (lines 157-158). If these are no longer needed, remove them to reduce dead code. If they're placeholders for future functionality, add a TODO comment.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
packages/plugins/robot/meta.js(1 hunks)packages/plugins/robot/src/Main.vue(2 hunks)packages/plugins/robot/src/client/OpenAICompatibleProvider.ts(1 hunks)packages/plugins/robot/src/components/RobotSettingPopover.vue(10 hunks)packages/plugins/robot/src/composables/agent.ts(1 hunks)packages/plugins/robot/src/composables/const.ts(1 hunks)packages/plugins/robot/src/composables/useChat.ts(1 hunks)packages/plugins/robot/src/composables/useRobot.ts(1 hunks)packages/plugins/robot/src/prompts/agent-prompt.md(1 hunks)packages/plugins/robot/src/prompts/index.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/plugins/robot/src/prompts/index.ts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.
Applied to files:
packages/plugins/robot/src/components/RobotSettingPopover.vue
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。
Applied to files:
packages/plugins/robot/src/Main.vue
📚 Learning: 2024-10-09T01:47:35.507Z
Learnt from: chilingling
Repo: opentiny/tiny-engine PR: 817
File: packages/vue-generator/src/plugins/appendElePlusStylePlugin.js:46-50
Timestamp: 2024-10-09T01:47:35.507Z
Learning: In `appendElePlusStylePlugin.js`, the code uses `|| {}` to set default values when obtaining files, so additional null checks may not be necessary.
Applied to files:
packages/plugins/robot/src/composables/useRobot.ts
🧬 Code graph analysis (4)
packages/plugins/robot/src/composables/useChat.ts (8)
packages/plugins/robot/src/types/mcp-types.ts (3)
ResponseToolCall(49-55)LLMMessage(29-33)RobotMessage(35-40)packages/register/src/common.ts (2)
getMetaApi(20-30)getOptions(32-34)packages/register/src/constants.ts (1)
META_SERVICE(1-24)packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/plugins/robot/src/composables/useMcp.ts (1)
useMcpServer(126-137)packages/plugins/robot/src/composables/agent.ts (3)
search(154-170)fetchAssets(172-180)updatePageSchema(152-152)packages/plugins/robot/src/prompts/index.ts (1)
getAgentSystemPrompt(30-52)packages/plugins/robot/src/utils/common-utils.ts (2)
formatMessages(8-18)serializeError(20-30)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
packages/plugins/robot/src/utils/common-utils.ts (1)
formatMessages(8-18)
packages/plugins/robot/src/composables/agent.ts (4)
packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/register/src/hooks.ts (1)
useHistory(82-82)packages/register/src/common.ts (1)
getMetaApi(20-30)packages/register/src/constants.ts (1)
META_SERVICE(1-24)
packages/plugins/robot/src/composables/useRobot.ts (2)
packages/register/src/common.ts (1)
getOptions(32-34)packages/plugins/robot/src/composables/const.ts (1)
DEFAULT_LLM_MODELS(11-90)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/prompts/agent-prompt.md
46-46: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
47-47: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
48-48: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
49-49: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
50-50: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
51-51: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
52-52: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
53-53: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
54-54: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
55-55: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
56-56: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
57-57: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
58-58: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
59-59: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
60-60: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
61-61: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
62-62: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
63-63: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
64-64: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
65-65: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
66-66: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
67-67: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
68-68: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
69-69: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
70-70: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
71-71: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
72-72: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
73-73: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
74-74: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
75-75: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
76-76: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
77-77: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
78-78: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
79-79: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
80-80: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
81-81: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
82-82: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
83-83: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
84-84: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
85-85: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
86-86: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
93-93: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
98-98: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
103-103: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
110-110: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
120-120: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
125-125: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
130-130: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (18)
packages/plugins/robot/src/prompts/agent-prompt.md (5)
1-12: Clear and well-scoped mission statement.The preamble effectively establishes the agent's role and early emphasis on JSON formatting constraints is helpful for setting expectations.
14-37: Well-structured operational workflow and validation steps.The workflow and pre-output validation are comprehensive, addressing common JSON formatting failures. The fallback to
[]on validation failure is a reasonable safeguard.
156-185: Clarify lifecycle function semantics to match runtime implementation.The PageSchema and ComponentSchema specifications are comprehensive. However, the setup lifecycle example (line 157) deserves clarification: the destructuring pattern
function({props, state, watch, onMounted})suggests lifecycle hooks are passed as parameters, which differs from standard Vue 3 behavior. If this is a custom convention for TinyEngine, it should be explicitly documented. If it should follow Vue 3 semantics, the example should be corrected to show direct hook registration (e.g.,onMounted(() => { ... })).Verify that the lifecycle names and function signatures match the actual runtime behavior to avoid agent confusion.
187-199: Appropriate use of placeholder content.This section correctly relies on runtime-injected content ({{COMPONENTS_LIST}}, {{EXAMPLES_SECTION}}) and reinforces the form component two-way binding preference. Consistent with the overall template design.
203-212: Clean placeholder structure for runtime context injection.The context section is appropriately parameterized for runtime injection of page schema, reference knowledge, and image assets. Aligns with the template design pattern.
packages/plugins/robot/src/components/RobotSettingPopover.vue (5)
147-154: LGTM!The destructured helpers from
useRobot()align well with the new persistence and configuration workflow described in the PR objectives.
182-199: LGTM!The computed option derivations are well-structured. The separation between compact and non-compact models is clear, and the reactive derivation from
state.existFormData.baseUrlensures the UI updates correctly when the provider changes.
213-228: LGTM!The
changeBaseUrlhandler correctly resets dependent fields (apiKey, model, completeModel) when the provider changes, persists the updated state, and synchronizes withrobotSettingState.selectedModel.
241-258: LGTM!The validation flow has been corrected—
saveRobotSettingStateis now called inside thevalidbranch (lines 246-250), ensuring invalid forms are never persisted. The optional chaining onform.value?.validateis also a good safety improvement.
261-283: Review comment is incorrect. Upstream initialization properly guards the concern.The
getAIModelOptions()function inuseRobot.ts(lines 96-102) includes a guard:if (!customAIModels.length) return DEFAULT_LLM_MODELS. SinceDEFAULT_LLM_MODELSis a non-empty, predefined array with multiple model configurations,getAIModelOptions()always returns a non-empty array. Consequently,robotSettingState.selectedModelis always initialized with complete field values (baseUrl, model, apiKey, completeModel, label) at lines 135-138 ofuseRobot.ts. The code inRobotSettingPopover.vuelines 261-283 correctly relies on this initialization and requires no changes.Likely an incorrect or invalid review comment.
packages/plugins/robot/src/Main.vue (5)
65-82: LGTM!The migration to
<script setup>with well-organized imports aligns with modern Vue composition API patterns. All imports appear necessary for the new component architecture.
94-112: LGTM!The computed properties and
toggleActivefunction are well-structured. The optional chaining infullscreen(line 95) safely handles null refs, andtoggleActivecorrectly persists the state change.
114-132: LGTM!The
promptItemsarray is well-structured with clear labels, descriptions, and icons. Usingh()to render icon components is the correct approach for the composition API.
139-153: LGTM!The
isVisualModelandisToolsModelcomputed properties correctly derive capabilities from the selected model. The checktoolCalling !== false(line 152) appropriately treats bothtrueandundefinedas tool-enabled.
198-202: LGTM!The
onMountedhook with a 1-second delay before showing the teleport ensures the target DOM element is ready. This is a reasonable approach for dealing with dynamic teleport targets.packages/plugins/robot/src/composables/useRobot.ts (3)
19-33: LGTM!The constants and comprehensive JSDoc for
mergeAIModelOptionsclearly document the merge behavior, including provider removal, model removal, merging, and overriding.
96-102: LGTM!The
getAIModelOptionsfunction correctly implements extensibility by allowing custom models viacustomCompatibleAIModelsfrom meta configuration, addressing the PR requirements for configurable model parameters.Based on PR objectives.
144-155: LGTM!The factory function export pattern provides a clean API surface, exposing all necessary functions and reactive state for consumption by components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (5)
packages/plugins/robot/src/composables/useAgent.ts (2)
154-170: Add error logging to aid debugging.The empty catch block (lines 166-168) suppresses all search errors. Consider logging errors to aid debugging, even if the function gracefully returns an empty result.
Apply this diff:
} catch (error) { - // error + logger.error('Search failed:', error) }
89-89: Fix typo:isFinial→isFinal.The parameter name
isFinialat lines 89 and 98 is a typo and should beisFinal. Update all references within both functions.Apply this diff:
-const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => { +const jsonPatchAutoFix = (jsonPatches: any[], isFinal: boolean) => { // 流式渲染过程中,画布只渲染children字段,避免不完整的methods/states/css等字段导致解析报错 const childrenFilter = (patch, index, arr) => - isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children')) + isFinal || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children')) const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch) return validJsonPatches } -const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => { +const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinal: boolean = false) => {And update all other references to
isFinialwithin_updatePageSchema(lines 110, 115, 122, 125, 133, 145).Also applies to: 98-98
packages/plugins/robot/src/Main.vue (1)
178-197: Add user notification for upload failures.The catch block (lines 192-196) only logs to console. Users receive no visible feedback when upload fails, potentially causing confusion.
Apply this diff:
+import { TinyPopover, Notify } from '@opentiny/vue' -import { TinyPopover } from '@opentiny/vue'And update the catch block:
} catch (error) { // eslint-disable-next-line no-console console.error('上传失败', error) updateAttachment('') + Notify({ + type: 'error', + message: '文件上传失败,请重试', + position: 'top-right', + duration: 5000 + }) } }packages/plugins/robot/src/components/chat/RobotChat.vue (2)
163-218: Fix type inconsistency and property access in file upload.Two issues:
Type mismatch: The function signature (line 163) accepts
FileList | null, but whenretryis true (line 217), a singleFileis passed viafile.file. This creates type inconsistency.Wrong property: Line 217 accesses
file.file, but line 185 shows the file is stored asrawFile, notfile.Apply this diff:
-const handleSingleFilesSelected = (files: FileList | null, retry = false) => { +const handleSingleFilesSelected = (files: FileList | File | null, retry = false) => { if (retry) { singleAttachmentItems.value[0].status = 'uploading' singleAttachmentItems.value[0].isUploading = true singleAttachmentItems.value[0].messageType = 'uploading' } else { - if (!files.length) return + if (!files || (files instanceof FileList && !files.length)) return - if (files && files.length > 1) { + if (files instanceof FileList && files.length > 1) { Notify({ type: 'error', message: '当前仅支持上传一张图片', position: 'top-right', duration: 5000 }) return } - if (files && files.length > 0) { + if (files instanceof FileList && files.length > 0) { // 将选中的文件转换为 Attachment 格式并添加到附件列表 const newAttachments = Array.from(files).map((file) => ({And fix the retry handler:
const handleSingleFileRetry = (file: any) => { - handleSingleFilesSelected(file.file, true) + handleSingleFilesSelected(file.rawFile, true) }
345-348: Add bounds check to prevent runtime error.Line 347 uses a non-null assertion (
!) when accessing the last message. Ifmessages.valueis empty when the user cancels, this will throw a runtime error.Apply this diff:
const handleAbortRequest = () => { abortRequest() - messages.value.at(-1)!.aborted = true + const lastMessage = messages.value.at(-1) + if (lastMessage) { + lastMessage.aborted = true + } }
🧹 Nitpick comments (13)
packages/plugins/robot/src/prompts/data/components.json (1)
2-9: Box component demo uses generic div structure.The Box demo shows
"componentName": "div"instead of demonstrating the Box component itself. While this may be intentional (container components often don't have complex internal structure), consider whether a more representative example would better serve AI context generation. For consistency with other container patterns, you might show a Box with child components.packages/plugins/robot/src/prompts/templates/agent-prompt.md (2)
45-86: Markdown list indentation needs correction (MD007).The static analysis flagged 22 instances of improper unordered list indentation throughout the constraint rules section. While the content is clear and correct, standardized markdown formatting improves readability and maintainability.
Consider using a consistent indentation level (typically 2 spaces per level) across all nested lists. This will also resolve the MD007 linting errors.
93-133: Code blocks should specify language identifiers (MD040).The error example code blocks (lines 93, 98, 103, 110, 120, 125, 130) lack language specifiers. Adding
```javascriptor```jsonwill enable proper syntax highlighting and resolve MD040 linting violations.packages/plugins/robot/src/prompts/data/examples.json (1)
1-146: Example structure is well-formed but mixes styling approaches.The chatMessageList example demonstrates good JSON Patch structure with proper event binding, two-way binding via
model: true, and string concatenation (avoiding template literals per spec). However, the example uses both inline styles (line 64:"style": "display: inline-block;") and className bindings to CSS classes defined in the /css section (lines 42, 49, 88, 128).Per the TinyEngine best practice (from PR 1011), components should use inline styles instead of CSS classes to avoid losing styles when dragged in the canvas designer. Consider refactoring this example to prioritize inline styles over className bindings for maximum compatibility.
Replace className-based styling with inline styles. For example, line 42-43:
- "className": "component-base-style div-uhqto", - "alignItems": "flex-start" + "style": "margin: 8px; display: flex; flex-direction: column; align-items: flex-start;"This ensures the example aligns with inline-style best practices and improves component portability within the designer.
packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue (1)
213-228: Consider auto-selecting the first compact model on baseUrl change.When the base URL changes,
completeModelis cleared (line 220) but not automatically set, unlikemodelwhich defaults to the first available model (line 219). For better UX, consider auto-selecting the first compact model if available.Apply this enhancement:
state.existFormData.label = provider?.label || '' state.existFormData.model = models[0]?.name || '' - state.existFormData.completeModel = '' + const compactModels = provider?.models.filter(isCompactModel).map(modelTransformer) || [] + state.existFormData.completeModel = compactModels[0]?.value || ''packages/plugins/robot/src/components/renderers/ImgRenderer.vue (1)
22-25: Consider making the width configurable.The fixed width of 200px may not suit all use cases. Consider exposing a width prop to allow parent components to customize the size.
+const { content, width } = defineProps({ -const { content } = defineProps({ content: { type: String, default: '' + }, + width: { + type: String, + default: '200px' } }).img-renderer-container { - width: 200px; + width: v-bind(width); height: auto; }packages/plugins/robot/src/constants/model-config.ts (2)
15-15: Consider making base URLs configurable.The base URLs for providers are hardcoded, which reduces flexibility for different deployment environments (development, staging, production) or for users who want to use custom API endpoints.
Consider moving these to environment variables or a separate config file:
export const DEFAULT_LLM_MODELS = [ { provider: 'bailian', label: '阿里云百炼', baseUrl: import.meta.env.VITE_BAILIAN_BASE_URL || 'https://dashscope.aliyuncs.com/compatible-mode/v1', models: [ // ... ] }, { provider: 'deepseek', label: 'DeepSeek', baseUrl: import.meta.env.VITE_DEEPSEEK_BASE_URL || 'https://api.deepseek.com/v1', models: [ // ... ] } ]Also applies to: 73-73
11-90: Align with reviewer's recommendation to enhance model configuration schema.The current model configuration provides a good foundation but could be enhanced based on the reviewer's suggestions to include additional metadata such as:
- Context window size
- Max tokens
- Default max tokens
- Input/output pricing
- Default model flag
- More detailed capability descriptions
This would improve extensibility and allow the UI to display richer information to users when selecting models.
Based on coding guidelines from the PR objectives. Consider expanding the model schema:
{ label: 'Qwen 通用模型(Plus)', name: 'qwen-plus', contextWindow: 32768, maxTokens: 8192, defaultMaxTokens: 2000, inputPrice: 0.0004, // per 1k tokens outputPrice: 0.0012, isDefault: true, description: 'Qwen Plus is a general-purpose model with strong reasoning capabilities', capabilities: { toolCalling: true, reasoning: reasoningExtraBody } }packages/plugins/robot/src/components/chat/FooterButton.vue (2)
78-113: Remove unused CSS classes or document their purpose.The
.plugin-commonand.plugin-activeclasses are defined but not referenced in the template. If these are intended for future use or for consumers of this component, consider documenting this. Otherwise, remove them to reduce code bloat.- .plugin-common { - &_text { - font-size: 12px; - font-weight: 400; - line-height: 20px; - letter-spacing: 0; - text-align: left; - } - - &_icon { - font-size: 16px; - } - } - - .plugin-active { - &_count { - width: 12px; - height: 12px; - background: #1476ff; - // border-radius: 100%; - display: flex; - align-items: center; - justify-content: center; - - font-size: 9px; - font-weight: 500; - line-height: 12px; - color: #fff; - } - - &:hover { - color: #1476ff; - background-color: #eaf0f8; - border: 1px solid #1476ff; - } - }
97-97: Remove commented-out code.Line 97 contains a commented-out
border-radius: 100%. Either apply the style or remove the comment.width: 12px; height: 12px; background: #1476ff; - // border-radius: 100%; display: flex;packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1)
95-101: Fixed width may cause layout issues on narrow screens.The
.build-loading-renderer-content-bodyhas a fixed width of 160px, which might be too wide for very narrow screens or too narrow for wider layouts. Consider using max-width instead or making it responsive.&-body { color: var(--te-chat-model-helper-text); font-size: 12px; - width: 160px; + max-width: 160px; height: 30px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }packages/plugins/robot/src/utils/chat.utils.ts (2)
19-29: Enhance error serialization to capture stack traces.The current error serialization only captures
nameandmessage, losing valuable debugging information like stack traces.export const serializeError = (err: unknown): string => { if (err instanceof Error) { - return JSON.stringify({ name: err.name, message: err.message }) + return JSON.stringify({ + name: err.name, + message: err.message, + stack: err.stack + }) } if (typeof err === 'string') return err try { return JSON.stringify(err) } catch { return String(err) } }
58-74: Consider making hardcoded values configurable.The function has several hardcoded values:
- Default model:
'deepseek-chat'(line 61)- Stream flag:
false(line 62)- API endpoint:
'/app-center/api/chat/completions'(line 68)While some are provided via options, the defaults reduce flexibility. Consider making these configurable via a config file or environment variables, especially for the API endpoint.
-export const fetchLLM = async (messages: LLMMessage[], tools: RequestTool[], options: RequestOptions = {}) => { +export const fetchLLM = async ( + messages: LLMMessage[], + tools: RequestTool[], + options: RequestOptions & { stream?: boolean } = {} +) => { const bodyObj: LLMRequestBody = { baseUrl: options.baseUrl, model: options?.model || 'deepseek-chat', - stream: false, + stream: options.stream ?? false, messages: toRaw(messages) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (29)
packages/plugins/robot/index.ts(1 hunks)packages/plugins/robot/src/Main.vue(2 hunks)packages/plugins/robot/src/client/OpenAICompatibleProvider.ts(1 hunks)packages/plugins/robot/src/components/chat/FooterButton.vue(1 hunks)packages/plugins/robot/src/components/chat/RobotChat.vue(1 hunks)packages/plugins/robot/src/components/footer-extension/McpServer.vue(1 hunks)packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue(3 hunks)packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue(11 hunks)packages/plugins/robot/src/components/icons/mcp-icon.vue(0 hunks)packages/plugins/robot/src/components/icons/page-icon.vue(0 hunks)packages/plugins/robot/src/components/icons/study-icon.vue(0 hunks)packages/plugins/robot/src/components/renderers/AgentRenderer.vue(1 hunks)packages/plugins/robot/src/components/renderers/ImgRenderer.vue(1 hunks)packages/plugins/robot/src/components/renderers/LoadingRenderer.vue(1 hunks)packages/plugins/robot/src/composables/useAgent.ts(1 hunks)packages/plugins/robot/src/composables/useChat.ts(1 hunks)packages/plugins/robot/src/composables/useConfig.ts(1 hunks)packages/plugins/robot/src/constants/index.ts(1 hunks)packages/plugins/robot/src/constants/model-config.ts(1 hunks)packages/plugins/robot/src/metas/index.ts(1 hunks)packages/plugins/robot/src/prompts/data/components.json(1 hunks)packages/plugins/robot/src/prompts/data/examples.json(1 hunks)packages/plugins/robot/src/prompts/index.ts(1 hunks)packages/plugins/robot/src/prompts/templates/agent-prompt.md(1 hunks)packages/plugins/robot/src/types/chat.types.ts(3 hunks)packages/plugins/robot/src/types/index.ts(1 hunks)packages/plugins/robot/src/types/mcp.types.ts(1 hunks)packages/plugins/robot/src/utils/chat.utils.ts(1 hunks)packages/plugins/robot/src/utils/index.ts(1 hunks)
💤 Files with no reviewable changes (3)
- packages/plugins/robot/src/components/icons/mcp-icon.vue
- packages/plugins/robot/src/components/icons/study-icon.vue
- packages/plugins/robot/src/components/icons/page-icon.vue
✅ Files skipped from review due to trivial changes (1)
- packages/plugins/robot/src/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/plugins/robot/src/prompts/index.ts
- packages/plugins/robot/src/composables/useChat.ts
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-01-14T04:22:02.404Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/builtin/builtin.json:645-850
Timestamp: 2025-01-14T04:22:02.404Z
Learning: In TinyEngine, components must use inline styles instead of CSS classes because components cannot carry class styles when dragged into the canvas.
Applied to files:
packages/plugins/robot/src/prompts/data/components.json
📚 Learning: 2025-07-03T09:22:59.512Z
Learnt from: hexqi
Repo: opentiny/tiny-engine PR: 1501
File: mockServer/src/tool/Common.js:79-82
Timestamp: 2025-07-03T09:22:59.512Z
Learning: In the tiny-engine project, the mockServer code uses ES6 import syntax but is compiled to CommonJS output. This means CommonJS globals like `__dirname` are available at runtime, while ES6 module-specific features like `import.meta` would cause runtime errors.
Applied to files:
packages/plugins/robot/index.ts
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.
Applied to files:
packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vuepackages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.
Applied to files:
packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vuepackages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-15T02:19:06.755Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 940
File: packages/canvas/DesignCanvas/src/DesignCanvas.vue:0-0
Timestamp: 2025-01-15T02:19:06.755Z
Learning: In Vue components using message subscriptions from opentiny/tiny-engine-meta-register, always clean up subscriptions in the onUnmounted hook using useMessage().unsubscribe() to prevent memory leaks.
Applied to files:
packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.
Applied to files:
packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。
Applied to files:
packages/plugins/robot/src/Main.vue
🧬 Code graph analysis (5)
packages/plugins/robot/src/utils/chat.utils.ts (3)
packages/plugins/robot/src/types/chat.types.ts (4)
LLMMessage(32-36)RequestTool(11-30)RequestOptions(4-9)LLMRequestBody(45-51)packages/register/src/common.ts (1)
getMetaApi(20-30)packages/register/src/constants.ts (1)
META_SERVICE(1-24)
packages/plugins/robot/src/composables/useAgent.ts (4)
packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/register/src/hooks.ts (1)
useHistory(82-82)packages/register/src/common.ts (1)
getMetaApi(20-30)packages/register/src/constants.ts (1)
META_SERVICE(1-24)
packages/plugins/robot/src/composables/useConfig.ts (2)
packages/register/src/common.ts (1)
getOptions(32-34)packages/plugins/robot/src/constants/model-config.ts (1)
DEFAULT_LLM_MODELS(11-90)
packages/plugins/robot/src/types/chat.types.ts (1)
packages/plugins/robot/src/types/mcp.types.ts (1)
ResponseToolCall(1-7)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
packages/plugins/robot/src/utils/chat.utils.ts (1)
formatMessages(7-17)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/prompts/templates/agent-prompt.md
46-46: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
47-47: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
48-48: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
49-49: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
50-50: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
51-51: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
52-52: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
53-53: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
54-54: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
55-55: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
56-56: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
57-57: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
58-58: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
59-59: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
60-60: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
61-61: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
62-62: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
63-63: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
64-64: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
65-65: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
66-66: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
67-67: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
68-68: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
69-69: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
70-70: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
71-71: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
72-72: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
73-73: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
74-74: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
75-75: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
76-76: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
77-77: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
78-78: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
79-79: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
80-80: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
81-81: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
82-82: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
83-83: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
84-84: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
85-85: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
86-86: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
93-93: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
98-98: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
103-103: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
110-110: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
120-120: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
125-125: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
130-130: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (23)
packages/plugins/robot/src/prompts/data/components.json (1)
1-997: Component catalog is well-structured and comprehensive.The catalog provides solid reference data for AI prompt generation with consistent metadata structure (component name, properties, events, and realistic demos). All samples correctly use inline styles per TinyEngine best practices.
packages/plugins/robot/src/prompts/data/examples.json (1)
125-145: Methods are well-structured with proper string handling.The sendMessage and onClickMessage methods correctly use string concatenation instead of template literals (per spec), and event parameter passing is correct. The example effectively demonstrates method definition and event binding patterns.
packages/plugins/robot/src/prompts/templates/agent-prompt.md (1)
1-622: System prompt template is comprehensive and correctly integrated with placeholder replacement logic.The specification provides clear guidance for RFC 6902 JSON Patch generation with strict validation rules, good error examples, and complete PageSchema documentation. Verification confirms that all placeholders ({{COMPONENTS_LIST}}, {{EXAMPLES_SECTION}}, {{CURRENT_PAGE_SCHEMA}}, {{REFERENCE_KNOWLEDGE}}, {{IMAGE_ASSETS}}) in the template are correctly replaced by the generator in packages/plugins/robot/src/prompts/index.ts (lines 45-49).
packages/plugins/robot/src/components/renderers/LoadingRenderer.vue (1)
1-3: LGTM! Simple and functional loading indicator.The template-only component is straightforward and serves its purpose well. The hardcoded dimensions and relative asset path are acceptable for this use case.
packages/plugins/robot/src/types/mcp.types.ts (1)
1-38: LGTM! Well-structured type definitions for MCP interactions.The centralized type definitions appropriately separate MCP-related concerns from chat types. The flexible index signatures (
[prop: string]: unknown) provide good extensibility while maintaining type safety for known fields.packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (4)
51-81: Constructor validation is well-implemented.The constructor properly validates that
axiosClientis provided whenhttpClientTypeis set to 'axios', preventing runtime errors. The initialization logic with sensible defaults and the extensibility via thebeforeRequesthook are good design choices.
132-162: LGTM! Header construction and request preparation are solid.The
buildHeadersmethod appropriately sets headers based on streaming mode and API key presence. TheprepareRequestDatamethod correctly applies message formatting, model selection precedence, and thebeforeRequesthook with proper async handling.
168-224: Well-designed fetch adapter for axios compatibility.The
createFetchAdaptermethod effectively bridges fetch and axios interfaces with proper URL resolution, response structure mapping, and error handling. The JSON parsing error recovery is particularly good.
282-335: Chat methods handle streaming and non-streaming flows correctly.Both
chatandchatStreammethods properly branch onhttpClientType, handle errors appropriately, and the streaming method correctly honors abort signals by silently returning when aborted (lines 330-332).packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue (2)
182-199: Well-structured computed properties for dynamic model options.The computed properties effectively derive model options from the selected provider, with clear separation between regular and compact models. The helper functions (
modelTransformer,isCompactModel,getProviderByBaseUrl) improve readability.
48-53: Good security improvement with password input type.Changing the API key inputs to
type="password"prevents accidental exposure of sensitive credentials during screen sharing or over-the-shoulder viewing.Also applies to: 100-105
packages/plugins/robot/index.ts (1)
17-17: LGTM! Import path updated to match refactored structure.The updated import path correctly references the new location of
RobotServicein the refactored codebase structure.packages/plugins/robot/src/constants/index.ts (1)
1-1: LGTM! Standard barrel export for constants.The barrel export provides centralized access to model configuration constants, following common JavaScript/TypeScript patterns.
packages/plugins/robot/src/utils/index.ts (1)
1-1: LGTM! Barrel export for chat utilities.The export provides centralized access to chat-related utilities, aligning with the refactored architecture.
packages/plugins/robot/src/types/chat.types.ts (3)
2-2: Good type organization improvements.Importing
ResponseToolCallfrom the dedicatedmcp.typesmodule improves type organization. The addition ofbaseUrltoRequestOptionsprovides necessary flexibility for multi-provider scenarios.Also applies to: 8-8
40-40: Enhanced content type enables rich message rendering.The union type
string | BubbleContentItem[]maintains backward compatibility while enabling structured content rendering, aligning with the enhanced chat UI capabilities in this refactor.
58-58: Type reference correctly updated.The
tool_callstype now referencesResponseToolCallfrommcp.types, fixing the previous typo ("ReponseToolCall") and aligning with the centralized MCP type definitions.packages/plugins/robot/src/components/footer-extension/McpServer.vue (1)
3-32: LGTM! Clean refactoring to use FooterButton component.The refactoring successfully extracts the button UI into a reusable
FooterButtoncomponent while maintaining the existing functionality. The active state is correctly derived fromactiveCount > 0, and the visibility toggle logic is properly wired.packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1)
33-54: Verify safe handling of content property in statusDataMap.Lines 37 and 42 use
props.content?.slice(-30), which correctly uses optional chaining. However, ifcontentis an empty string,slice(-30)returns an empty string, which may not provide useful feedback to users during the reasoning/loading states.Consider adding a fallback message:
reasoning: { title: '深度思考中,请稍等片刻', icon: 'loading.webp', - content: () => props.content?.slice(-30) + content: () => props.content?.slice(-30) || '正在处理中...' }, loading: { title: '页面生成中,请稍等片刻', icon: 'loading.webp', - content: () => props.content?.slice(-30) + content: () => props.content?.slice(-30) || '正在生成页面...' },packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue (2)
62-68: LGTM! Clean computed pattern for v-model binding.The computed getter/setter pattern correctly implements two-way binding with the parent component. The guard on line 65 prevents unnecessary event emissions when the value hasn't actually changed.
55-58: No breaking change detected in this codebase.Verification found:
- Zero usages of the old
aiTypeorai-typeprop anywhere in the codebase- RobotTypeSelect has only one consumer:
Main.vue(line 41)- The consumer already uses the new
chatModeprop correctly::chatMode="robotSettingState.chatMode"- All related state management code already references
chatMode, not the old prop nameSince no code in the repository uses the old prop name, this is not a breaking change within this codebase. The concern raised in the review comment does not apply to the actual state of the code.
Likely an incorrect or invalid review comment.
packages/plugins/robot/src/utils/chat.utils.ts (1)
7-17: LGTM! Correct message formatting with reactive proxy unwrapping.The
formatMessagesfunction correctly:
- Uses
toRaw()to unwrap Vue reactive proxies before sending to external APIs- Filters out invalid messages (those without content, tool_calls, or tool_call_id)
- Conditionally includes optional fields to keep the payload clean
packages/plugins/robot/src/metas/index.ts (1)
7-10: Review comment is incorrect. The code does not have reactivity issues.The
useModelConfig()composable implements a module-scope singleton pattern via Vue'sreactive()and closure:
robotSettingStateis created once at module load usingreactive()(not re-created on each call)- Each call to
useModelConfig()returns a wrapper object referencing the same underlying reactive instance via closure- Calling
useModelConfig()twice at module scope (lines 8-9) accesses the same reactive state both times—no separate instances are createdVue's composables can call
reactive()at module scope; this is a valid singleton pattern used throughout the codebase. The current code is functionally correct.Minor note: There is a minor inefficiency (creating two wrapper objects instead of one), but this is not a correctness or reactivity concern.
Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (9)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (2)
87-126: Error type detection relies on fragile string matching.The method infers error types by searching for substrings in lowercased error messages (lines 94-111). This approach is brittle—error messages can vary across browsers and network stacks.
Consider enhancing error detection:
private toAIAdapterError(error: unknown): AIAdapterError { + // Check for specific error types first + if (error instanceof TypeError && error.message.includes('fetch')) { + return { type: ErrorType.NETWORK_ERROR, message: error.message, originalError: error } + } + if (error instanceof DOMException && error.name === 'AbortError') { + return { type: ErrorType.TIMEOUT_ERROR, message: error.message, originalError: error } + } + if (error instanceof Error) { const message = error.message.toLowerCase()
342-358: updateConfig has incomplete ProviderConfig support.The method accepts
ProviderConfigbut only updatesbaseURL,apiKey, anddefaultModel, silently ignoringhttpClientTypeandaxiosClient. This creates a type/behavior mismatch.Consider either:
- Narrow the signature to exclude immutable properties:
updateConfig(config: Omit<ProviderConfig, 'httpClientType' | 'axiosClient'>): void- Or add handling for these properties with appropriate validation.
packages/plugins/robot/src/composables/useChat.ts (3)
55-99: Consider refactoring to align with provider-layer architecture.Per the PR comments, tool_call handling and mode-specific logic (agent vs chat) should be encapsulated within the provider layer rather than in a composable's beforeRequest hook. The current implementation mixes concerns across tool integration, agent-mode prompts, and model capability checks.
Based on PR comments.
This would improve separation of concerns and make it easier to add new modes without modifying core chat logic.
55-99: Guard reasoning extraBody assignments against null.Line 87-92: When
robotSettingState.enableThinkingis false, the code may passnulltoObject.assign, which throwsTypeError: Cannot convert undefined or null to object. This occurs for models like bailian whereextraBody.disableisnull.Apply this diff:
if (modelCapabilities?.reasoning?.extraBody) { - Object.assign( - requestParams, - robotSettingState.enableThinking - ? modelCapabilities.reasoning.extraBody.enable - : modelCapabilities.reasoning.extraBody.disable - ) + const extraBody = robotSettingState.enableThinking + ? modelCapabilities.reasoning.extraBody.enable + : modelCapabilities.reasoning.extraBody.disable + if (extraBody) { + Object.assign(requestParams, extraBody) + } }
147-203: Fixawait updatePageSchema(...)runtime crash.Line 190 awaits
updatePageSchema, butupdatePageSchemais the throttled wrapper fromuseAgent.ts(line 193) returned byuseThrottleFn, which returnsvoid, not a Promise. This will cause incorrect behavior when the agent reaches finalization.Consider exporting an unthrottled helper from
useAgent.tsfor final updates:In
packages/plugins/robot/src/composables/useAgent.ts:export const updatePageSchemaImmediate = _updatePageSchema export const updatePageSchema = useThrottleFn(_updatePageSchema, 200, true)Then in
useChat.ts:+import { updatePageSchema, updatePageSchemaImmediate } from './useAgent' ... - const result = await updatePageSchema(lastMessage.content, pageSchema, true) + const result = updatePageSchemaImmediate(lastMessage.content, pageSchema, true)packages/plugins/robot/src/composables/useAgent.ts (3)
108-115: Fix typo:isFinial→isFinal.The parameter name
isFinialappears to be a typo and should beisFinalfor clarity.Apply this diff:
-const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => { +const jsonPatchAutoFix = (jsonPatches: any[], isFinal: boolean) => { // 流式渲染过程中,画布只渲染完整的字段或流式的children字段,避免不完整的methods/states/css等字段导致解析报错 const childrenFilter = (patch, index, arr) => - isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children')) + isFinal || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children')) const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch)
140-191: Fix typo:isFinial→isFinalthroughout this function.The parameter and variable name
isFinial(line 140 and throughout) should beisFinalfor consistency.Apply this diff:
-const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => { +const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinal: boolean = false) => { const { robotSettingState, CHAT_MODE } = useModelConfig() if (robotSettingState.chatMode !== CHAT_MODE.Agent) { return } // 解析流式返回的schema patch let content = getJsonObjectString(streamContent) let jsonPatches = [] try { - if (!isFinial) { + if (!isFinal) { content = jsonrepair(content) } jsonPatches = JSON.parse(content) } catch (error) { - if (isFinial) { + if (isFinal) { logger.error('parse json patch error:', error) } return { isError: true, error } } // 过滤有效的json patch - if (!isFinial && !isValidFastJsonPatch(jsonPatches)) { + if (!isFinal && !isValidFastJsonPatch(jsonPatches)) { return { isError: true, error: 'format error: not a valid json patch.' } } - const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinial) + const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinal) // 生成新schema const originSchema = deepClone(currentPageSchema) const newSchema = validJsonPatches.reduce((acc: object, patch: any) => { try { return jsonpatch.applyPatch(acc, [patch], false, false).newDocument } catch (error) { - if (isFinial) { + if (isFinal) { logger.error('apply patch error:', error, patch) } return acc } }, originSchema) // schema纠错 fixMethods(newSchema.methods) schemaAutoFix(newSchema.children) // 更新Schema setSchema(newSchema) - if (isFinial) { + if (isFinal) { useHistory().addHistory() } return { schema: newSchema, isError: false } }
195-211: Silent error handling may hide issues.The empty catch block (lines 207-209) suppresses all search errors. Consider logging errors to aid debugging.
Apply this diff:
} catch (error) { - // error + logger.error('Search failed:', error) }packages/plugins/robot/src/prompts/index.ts (1)
16-25: Escape braces when injecting JSON into Markdown.Line 21 stringifies the patch array without escaping braces, so raw
{/}pairs in the template can be interpreted as Markdown syntax, potentially breaking prompt formatting.Apply this diff:
const formatExamples = (examples: Record<string, any>): string => { return Object.entries(examples) .map(([_key, example]) => { const { name, description, note, patch } = example const header = `### ${name}\n${description ? `${description}\n` : ''}${note ? `**Note**: ${note}\n` : ''}` - const patchContent = JSON.stringify(patch) - return `${header}\n${patchContent}` + const patchContent = '```json\n' + JSON.stringify(patch, null, 2) + '\n```' + return `${header}\n\n${patchContent}` }) .join('\n\n') }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts(1 hunks)packages/plugins/robot/src/components/renderers/AgentRenderer.vue(1 hunks)packages/plugins/robot/src/composables/useAgent.ts(1 hunks)packages/plugins/robot/src/composables/useChat.ts(1 hunks)packages/plugins/robot/src/prompts/index.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.
Applied to files:
packages/plugins/robot/src/composables/useChat.ts
📚 Learning: 2025-01-14T06:50:21.158Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/data-function/parser.ts:191-195
Timestamp: 2025-01-14T06:50:21.158Z
Learning: The `newFn` function in `packages/canvas/render/src/data-function/parser.ts` has a known security issue with evaluating untrusted code through `data.value`. This was identified during code review but intentionally deferred as the original implementation was kept during code refactoring.
Applied to files:
packages/plugins/robot/src/composables/useChat.ts
🧬 Code graph analysis (3)
packages/plugins/robot/src/composables/useAgent.ts (5)
packages/plugins/robot/src/utils/chat.utils.ts (1)
serializeError(19-29)packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/register/src/hooks.ts (1)
useHistory(82-82)packages/register/src/common.ts (1)
getMetaApi(20-30)packages/register/src/constants.ts (1)
META_SERVICE(1-24)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
packages/plugins/robot/src/utils/chat.utils.ts (1)
formatMessages(7-17)
packages/plugins/robot/src/composables/useChat.ts (9)
packages/plugins/robot/src/types/mcp.types.ts (1)
ResponseToolCall(1-7)packages/register/src/common.ts (2)
getMetaApi(20-30)getOptions(32-34)packages/register/src/constants.ts (1)
META_SERVICE(1-24)packages/plugins/robot/src/types/chat.types.ts (2)
LLMMessage(32-36)RobotMessage(38-43)packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/plugins/robot/src/composables/useMcp.ts (1)
useMcpServer(126-137)packages/plugins/robot/src/composables/useAgent.ts (4)
search(195-211)fetchAssets(213-225)updatePageSchema(193-193)isValidJsonPatchObjectString(123-138)packages/plugins/robot/src/prompts/index.ts (2)
getAgentSystemPrompt(30-52)getJsonFixPrompt(56-87)packages/plugins/robot/src/utils/chat.utils.ts (3)
mergeStringFields(37-56)formatMessages(7-17)serializeError(19-29)
🔇 Additional comments (38)
packages/plugins/robot/src/composables/useAgent.ts (9)
1-12: LGTM!The imports and initial setup are appropriate. Using
consoleas the logger is acceptable for this context.
14-18: LGTM!The
setSchemafunction correctly updates the canvas schema and marks it as unsaved.
20-25: LGTM!The icon fallback logic appropriately handles missing icons by defaulting to
IconWarning.
30-35: LGTM!The function correctly avoids fixing patch operation objects by checking for
opandpathproperties.
55-63: LGTM!The recursive schema auto-fix logic correctly handles arrays and nested children.
65-97: LGTM! Note non-standard_getoperation.The validation logic is comprehensive and correct. The
_getoperation (line 66) is a non-standard extension to RFC 6902 but appears to be intentionally supported.
99-106: LGTM!The patch validation correctly handles both single operations and arrays.
193-193: LGTM!The 200ms throttle with leading edge is appropriate for streaming schema updates.
213-225: LGTM!The function appropriately returns an empty array on error, which is acceptable for this non-critical helper.
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (9)
1-37: LGTM!The type definitions and imports are well-structured and appropriate for an OpenAI-compatible provider implementation.
39-81: LGTM!The constructor properly validates configuration, especially the axios client requirement when
httpClientTypeis set to'axios'.
132-146: LGTM!The header construction logic correctly handles streaming and authentication scenarios.
152-163: LGTM!The request preparation logic correctly applies the model selection waterfall and beforeRequest hook.
169-225: LGTM!The fetch adapter correctly handles URL construction, streaming responses, and JSON parsing with appropriate error handling.
231-249: LGTM!The fetch request implementation properly handles errors and includes helpful error details.
255-276: LGTM!The axios request handling correctly validates client presence and handles both function and instance forms of
axiosClient.
283-302: LGTM!The non-streaming chat method correctly branches on HTTP client type and handles responses appropriately.
309-336: LGTM!The streaming implementation correctly handles both transport types, abort signals, and error mapping.
packages/plugins/robot/src/prompts/index.ts (4)
1-4: LGTM!The imports appropriately use raw string imports for Markdown templates and JSON data.
9-11: LGTM!The function correctly wraps the JSONL output in a fenced code block, preventing Markdown interpretation issues.
30-52: LGTM!The prompt generation logic correctly filters components, formats sections, and replaces placeholders. The TinyNumeric exclusion (line 32) is appropriately documented.
54-87: LGTM!Both functions provide clear, comprehensive prompts. The JSON fix prompt includes detailed format requirements and examples.
packages/plugins/robot/src/components/renderers/AgentRenderer.vue (4)
1-9: LGTM!The template cleanly renders status information with appropriate conditional rendering.
11-27: LGTM!The props are well-typed with appropriate defaults.
28-80: LGTM!The setup logic appropriately handles different status states and dynamically resolves content. The reasoning completion logic (line 61) correctly determines visibility.
84-109: LGTM!The styles appropriately handle layout and text overflow scenarios.
packages/plugins/robot/src/composables/useChat.ts (12)
1-34: LGTM!The imports and initial setup are comprehensive and appropriate for the chat composable.
36-45: LGTM!The configuration object is well-structured and appropriately uses the meta API for HTTP client access.
47-53: LGTM!The function correctly handles both adding new system prompts and updating existing ones.
101-101: LGTM!The client creation appropriately passes config and beforeRequest hook.
103-106: LGTM!The config update function correctly updates both provider and local configuration.
108-120: LGTM!The loading removal logic correctly handles both specific and latest loading indicators.
205-207: LGTM!The conversation manager initialization correctly passes the client and event handlers.
209-254: LGTM!The delta handlers correctly process reasoning, content, and tool_calls, with appropriate incremental merging logic for tool call chunks.
256-262: LGTM!The argument parsing appropriately falls back to raw string on JSON parse errors.
264-359: LGTM!The tool call orchestration correctly handles execution, error states, abort signals, and recursive tool call chains.
361-373: LGTM!The chat mode change logic correctly updates metadata, persists state, and adjusts the API URL.
375-407: LGTM!The exported API comprehensively exposes chat functionality with appropriate abort handling and conversation mode restoration.
| const fixMethods = (methods: Record<string, any>) => { | ||
| if (methods && Object.keys(methods).length) { | ||
| Object.entries(methods).forEach(([methodName, methodValue]: [string, any]) => { | ||
| if ( | ||
| typeof methodValue !== 'object' || | ||
| methodValue?.type !== 'JSFunction' || | ||
| !methodValue?.value.startsWith('function') | ||
| ) { | ||
| methods[methodName] = { | ||
| type: 'JSFunction', | ||
| value: 'function ' + methodName + '() {\n console.log("' + methodName + '");\n}' | ||
| } | ||
| logger.log('autofix method to empty function:', methodName, methods[methodName]) | ||
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against non-string value before calling startsWith.
Line 43 calls .startsWith('function') on methodValue?.value without verifying it's a string, which could throw if value is not a string type.
Apply this diff:
if (
typeof methodValue !== 'object' ||
methodValue?.type !== 'JSFunction' ||
- !methodValue?.value.startsWith('function')
+ typeof methodValue?.value !== 'string' ||
+ !methodValue.value.startsWith('function')
) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const fixMethods = (methods: Record<string, any>) => { | |
| if (methods && Object.keys(methods).length) { | |
| Object.entries(methods).forEach(([methodName, methodValue]: [string, any]) => { | |
| if ( | |
| typeof methodValue !== 'object' || | |
| methodValue?.type !== 'JSFunction' || | |
| !methodValue?.value.startsWith('function') | |
| ) { | |
| methods[methodName] = { | |
| type: 'JSFunction', | |
| value: 'function ' + methodName + '() {\n console.log("' + methodName + '");\n}' | |
| } | |
| logger.log('autofix method to empty function:', methodName, methods[methodName]) | |
| } | |
| }) | |
| } | |
| } | |
| const fixMethods = (methods: Record<string, any>) => { | |
| if (methods && Object.keys(methods).length) { | |
| Object.entries(methods).forEach(([methodName, methodValue]: [string, any]) => { | |
| if ( | |
| typeof methodValue !== 'object' || | |
| methodValue?.type !== 'JSFunction' || | |
| typeof methodValue?.value !== 'string' || | |
| !methodValue.value.startsWith('function') | |
| ) { | |
| methods[methodName] = { | |
| type: 'JSFunction', | |
| value: 'function ' + methodName + '() {\n console.log("' + methodName + '");\n}' | |
| } | |
| logger.log('autofix method to empty function:', methodName, methods[methodName]) | |
| } | |
| }) | |
| } | |
| } |
🤖 Prompt for AI Agents
In packages/plugins/robot/src/composables/useAgent.ts around lines 37 to 53, the
conditional at line 43 calls methodValue?.value.startsWith('function') without
ensuring value is a string; update the check to first verify typeof
methodValue?.value === 'string' (e.g. replace the startsWith clause with typeof
methodValue?.value === 'string' && methodValue.value.startsWith('function')) so
non-string values won't throw, keeping the rest of the autofix behavior the
same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
packages/plugins/robot/src/Main.vue (1)
180-198: Handle upload failures and notify users.
getMetaApi(...).postreturns a promise, so network failures bypass thistry/catch, leaving the attachment stuck in “uploading” with no user feedback. Please await the request (or add a.catch) and surface the error viaTinyNotify, soupdateAttachment('')always runs and the user sees the failure.-const handleFileSelected = (formData: unknown, updateAttachment: (resourceUrl: string) => void) => { - try { - getMetaApi(META_SERVICE.Http) - .post('/material-center/api/resource/upload', formData, { - headers: { - 'Content-Type': 'multipart/form-data' - } - }) - .then((res: any) => { - updateAttachment(res?.resourceUrl) - if (!inputMessage.value) { - inputMessage.value = '生成图片中UI效果' - } - }) - } catch (error) { - // eslint-disable-next-line no-console - console.error('上传失败', error) - updateAttachment('') - } -} +const handleFileSelected = async ( + formData: FormData, + updateAttachment: (resourceUrl: string) => void +) => { + try { + const res = await getMetaApi(META_SERVICE.Http).post('/material-center/api/resource/upload', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + updateAttachment(res?.resourceUrl) + if (!inputMessage.value) { + inputMessage.value = '生成图片中UI效果' + } + } catch (error) { + updateAttachment('') + TinyNotify({ + type: 'error', + message: '文件上传失败,请重试', + position: 'top-right', + duration: 5000 + }) + } +}Based on learnings
packages/plugins/robot/src/components/chat/RobotChat.vue (2)
166-214: Fix retry upload type handling and null guards.
filesis declared asFileList | null, yet retry passes a singleFile, and the logic assumes.lengthexists. This breaks TypeScript expectations and can sendundefinedtoFormData, leaving uploads in limbo. Guardnull, normalize toFile[], and use the storedrawFileon retry.-const handleSingleFilesSelected = (files: FileList | null, retry = false) => { +const handleSingleFilesSelected = (files: FileList | File | null, retry = false) => { if (retry) { singleAttachmentItems.value[0].status = 'uploading' singleAttachmentItems.value[0].isUploading = true singleAttachmentItems.value[0].messageType = 'uploading' } else { - if (!files.length) return - - if (files && files.length > 1) { + if (!files) return + const selectedFiles = files instanceof FileList ? Array.from(files) : [files] + if (!selectedFiles.length) return + if (selectedFiles.length > 1) { Notify({ type: 'error', message: '当前仅支持上传一张图片', position: 'top-right', duration: 5000 }) return } - if (files && files.length > 0) { - // 将选中的文件转换为 Attachment 格式并添加到附件列表 - const newAttachments = Array.from(files).map((file) => ({ - size: file.size, - rawFile: file - })) - singleAttachmentItems.value.push(...newAttachments) - } + const newAttachments = selectedFiles.map((file) => ({ + size: file.size, + rawFile: file + })) + singleAttachmentItems.value.push(...newAttachments) } // 开始上传 const formData = new FormData() - const fileData = retry ? files : files[0] - formData.append('file', fileData) + const fileData = + retry && files instanceof File + ? files + : files instanceof FileList + ? files[0] + : files instanceof File + ? files + : null + if (!fileData) { + return + } + formData.append('file', fileData) @@ -const handleSingleFileRetry = (file: any) => { - handleSingleFilesSelected(file.file, true) +const handleSingleFileRetry = (file: any) => { + handleSingleFilesSelected(file.rawFile, true) }
358-360: Guard abort on empty message list.
messages.value.at(-1)!throws if the list is empty (e.g., aborting before any assistant response exists). Add a null check before settingaborted.const handleAbortRequest = () => { abortRequest() - messages.value.at(-1)!.aborted = true + const lastMessage = messages.value.at(-1) + if (lastMessage) { + lastMessage.aborted = true + } }packages/plugins/robot/src/composables/useChat.ts (1)
86-93: Guard reasoningextraBodymerge.Some providers return
extraBody.disable = null; passing that intoObject.assignthrowsTypeError: Cannot convert undefined or null to object, breaking agent flows when thinking is off. Select the branch and only merge when it’s truthy.- if (modelCapabilities?.reasoning?.extraBody) { - Object.assign( - requestParams, - robotSettingState.enableThinking - ? modelCapabilities.reasoning.extraBody.enable - : modelCapabilities.reasoning.extraBody.disable - ) - } + if (modelCapabilities?.reasoning?.extraBody) { + const extraBody = robotSettingState.enableThinking + ? modelCapabilities.reasoning.extraBody.enable + : modelCapabilities.reasoning.extraBody.disable + if (extraBody) { + Object.assign(requestParams, extraBody) + } + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
packages/plugins/robot/src/Main.vue(2 hunks)packages/plugins/robot/src/components/chat/RobotChat.vue(1 hunks)packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue(11 hunks)packages/plugins/robot/src/composables/useChat.ts(1 hunks)packages/plugins/robot/src/constants/model-config.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/plugins/robot/src/constants/model-config.ts
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.
Applied to files:
packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.
Applied to files:
packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-15T02:19:06.755Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 940
File: packages/canvas/DesignCanvas/src/DesignCanvas.vue:0-0
Timestamp: 2025-01-15T02:19:06.755Z
Learning: In Vue components using message subscriptions from opentiny/tiny-engine-meta-register, always clean up subscriptions in the onUnmounted hook using useMessage().unsubscribe() to prevent memory leaks.
Applied to files:
packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.
Applied to files:
packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.
Applied to files:
packages/plugins/robot/src/composables/useChat.ts
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。
Applied to files:
packages/plugins/robot/src/Main.vue
🧬 Code graph analysis (1)
packages/plugins/robot/src/composables/useChat.ts (9)
packages/plugins/robot/src/types/mcp.types.ts (1)
ResponseToolCall(1-7)packages/register/src/common.ts (2)
getMetaApi(20-30)getOptions(32-34)packages/register/src/constants.ts (1)
META_SERVICE(1-24)packages/plugins/robot/src/types/chat.types.ts (2)
LLMMessage(32-36)RobotMessage(38-43)packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/plugins/robot/src/composables/useMcp.ts (1)
useMcpServer(126-137)packages/plugins/robot/src/composables/useAgent.ts (4)
search(195-211)fetchAssets(213-225)updatePageSchema(193-193)isValidJsonPatchObjectString(123-138)packages/plugins/robot/src/prompts/index.ts (2)
getAgentSystemPrompt(30-52)getJsonFixPrompt(56-87)packages/plugins/robot/src/utils/chat.utils.ts (3)
mergeStringFields(37-56)formatMessages(7-17)serializeError(19-29)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/plugins/robot/src/components/footer-extension/McpServer.vue (1)
36-48: IncorrectwithDefaultsstructure.The
withDefaultscall has structural issues:
- Line 41 sets
type: 'fixed'buttypeis not a defined prop- Line 42-46 provides
positionas a flat object but it should be a factory function for object defaults in Vue 3const props = withDefaults( defineProps<{ position: PopupConfig }>(), { - type: 'fixed', - position: { + position: () => ({ top: 'var(--base-top-panel-height)', bottom: 0, right: 'var(--tr-container-width)' - } + }) } )
♻️ Duplicate comments (6)
packages/plugins/robot/src/constants/prompts/data/examples.json (1)
26-120: Component IDs still do not comply with specification requirements.As previously noted, the specification requires component IDs to be unique 8-character random IDs containing at least one uppercase letter, one lowercase letter, and one digit. The IDs in this file (lines 26, 37, 43, 75, 83, 104, 117, 120) are either purely numeric or lack uppercase letters.
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
63-102: Error type detection relies on fragile string matching.As previously noted, the method infers error types by searching for substrings in lowercased error messages, which is brittle across different browsers and network stacks.
packages/plugins/robot/src/Main.vue (1)
252-265: Add user notification for file upload failures.The catch block logs the error but provides no visible feedback to the user. Additionally, the
try-catchwon't catch errors from the Promise chain sinceapiService.uploadFileis not awaited.Apply this diff to fix error handling and add user notification:
-const handleFileSelected = (formData: FormData, updateAttachment: (resourceUrl: string) => void) => { - try { - apiService.uploadFile(formData).then((res: any) => { +const handleFileSelected = async (formData: FormData, updateAttachment: (resourceUrl: string) => void) => { + try { + const res: any = await apiService.uploadFile(formData) + updateAttachment(res?.resourceUrl) + if (!inputMessage.value) { + inputMessage.value = '生成图片中UI效果' + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('上传失败', error) + updateAttachment('') + TinyNotify({ + type: 'error', + title: '文件上传失败', + message: '请重试', + position: 'top-right', + duration: 5000 + }) + } +} - updateAttachment(res?.resourceUrl) - if (!inputMessage.value) { - inputMessage.value = '生成图片中UI效果' - } - }) - } catch (error) { - // eslint-disable-next-line no-console - console.error('上传失败', error) - updateAttachment('') - } -}packages/plugins/robot/src/constants/model-config.ts (1)
1-9: Previous review notedreasoningExtraBodyshould be exported.A past review comment already flagged that
reasoningExtraBodyis not exported but should be for reusability.packages/plugins/robot/src/utils/chat.utils.ts (1)
57-86: SSE parsing robustness concerns already flagged.The issues with
lines.pop()assuming trailing newline, regex matching, and error handling were previously identified.packages/plugins/robot/src/components/chat/RobotChat.vue (1)
121-152: Type inconsistency in file upload retry logic persists.The
filesparameter type mismatch from previous review remains unaddressed. Whenretryistrue(Line 175 calls withfile.file), the function receives aFileobject, notFileList. However:
- Line 127 calls
files.lengthwithout null check whenretryis false- Line 151 uses
filesdirectly when retry is true, butfiles[0]otherwiseApply this diff to fix the type handling:
-const handleSingleFilesSelected = (files: FileList | null, retry = false) => { +const handleSingleFilesSelected = (files: FileList | File | null, retry = false) => { if (retry) { singleAttachmentItems.value[0].status = 'uploading' singleAttachmentItems.value[0].isUploading = true singleAttachmentItems.value[0].messageType = 'uploading' } else { - if (!files.length) return + if (!files || !('length' in files) || !files.length) return if (files && files.length > 1) {And update the retry call at Line 174-176:
const handleSingleFileRetry = (file: any) => { - handleSingleFilesSelected(file.file, true) + handleSingleFilesSelected(file.rawFile, true) }
🟡 Minor comments (11)
packages/plugins/robot/src/utils/schema.utils.ts-139-146 (1)
139-146: Fix parameter naming typo.The parameter name
isFinialshould beisFinalthroughout this function and wherever it's called.Apply this diff:
-export const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => { +export const jsonPatchAutoFix = (jsonPatches: any[], isFinal: boolean) => { // 流式渲染过程中,画布只渲染完整的字段或流式的children字段,避免不完整的methods/states/css等字段导致解析报错 const childrenFilter = (patch: any, index: number, arr: any[]) => - isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children')) + isFinal || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children')) const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch) return validJsonPatches }Also update all call sites of this function (e.g., in
parseAndRepairJsonat line 180 if it uses this parameter).packages/plugins/robot/src/Main.vue-173-176 (1)
173-176: Potential null access when aborting an empty message list.If
messages.valueis empty whenhandleAbortRequestis called,messages.value.at(-1)returnsundefined, causing a runtime error when accessing.aborted.Apply this diff to add a guard:
const handleAbortRequest = () => { abortRequest() - messages.value.at(-1)!.aborted = true + const lastMessage = messages.value.at(-1) + if (lastMessage) { + lastMessage.aborted = true + } }packages/plugins/robot/src/constants/prompts/data/components.json-529-542 (1)
529-542: Trailing space in event name.Line 532 has a trailing space in
"onCurrentChange "and"onPrevClick "which could cause issues when matching event names programmatically.- "events": ["onCurrentChange ", "onPrevClick ", "onNextClick"], + "events": ["onCurrentChange", "onPrevClick", "onNextClick"],packages/plugins/robot/src/composables/modes/useAgentMode.ts-150-158 (1)
150-158: Potential null reference inonRequestEnd.Lines 153-156 access
messages.at(-1).renderContent.at(-1)without checking ifmessagesorrenderContentare non-empty. If called with an empty messages array, this will throw.if (finishReason === 'aborted' || finishReason === 'error') { removeLoading(messages) const errorInfo = { content: extraData?.error || '请求失败', status: 'failed' } - if (messages.at(-1).renderContent.at(-1)) { - Object.assign(messages.at(-1).renderContent.at(-1), errorInfo) + const lastMessage = messages.at(-1) + const lastRenderContent = lastMessage?.renderContent?.at(-1) + if (lastRenderContent) { + Object.assign(lastRenderContent, errorInfo) } else { - messages.at(-1).renderContent = [{ type: getContentType(), ...errorInfo }] + if (lastMessage) { + lastMessage.renderContent = [{ type: getContentType(), ...errorInfo }] + } } }packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue-163-168 (1)
163-168: Props destructuring loses reactivity.Destructuring
fullscreendirectly fromdefinePropsloses reactivity. If the parent changesfullscreen, the component won't react to the change.-const { fullscreen } = defineProps({ +const props = defineProps({ fullscreen: { type: Boolean, default: false } })Then use
props.fullscreenin the template, or usetoRefsif local destructuring is preferred.Committable suggestion skipped: line range outside the PR's diff.
packages/plugins/robot/src/constants/prompts/index.ts-181-186 (1)
181-186: Incorrect JSON Patch specification in prompt.Line 185 states
"value" is required for "add", "replace", "move", "copy", "test" operations, but per RFC 6902:
moveandcopyrequirefrom, notvaluetestrequiresvalueremoverequires neitherThis could cause the LLM to generate invalid patches.
-- "value" is required for "add", "replace", "move", "copy", "test" operations -- "from" is required for "move", "copy" operations +- "value" is required for "add", "replace", "test" operations +- "from" is required for "move", "copy" operationspackages/plugins/robot/src/constants/prompts/index.ts-163-168 (1)
163-168: Template substitution could break if dynamic content contains placeholder patterns.The
replace()calls on lines 164-168 use simple string replacement. IfreferenceContextorcurrentPageSchemaStrcontains a string like{{COMPONENTS_LIST}}, it would be incorrectly substituted in subsequent replacements.Consider using a single-pass replacement or ensuring dynamic content is processed after all static placeholders:
- const prompt = agentPrompt - .replace('{{COMPONENTS_LIST}}', componentsList) - .replace('{{EXAMPLES_SECTION}}', examplesSection) - .replace('{{CURRENT_PAGE_SCHEMA}}', currentPageSchemaStr) - .replace('{{REFERENCE_KNOWLEDGE}}', referenceContext || '') - .replace('{{IMAGE_ASSETS}}', imageAssets.map((item) => `- `).join('\n')) + const replacements: Record<string, string> = { + '{{COMPONENTS_LIST}}': componentsList, + '{{EXAMPLES_SECTION}}': examplesSection, + '{{CURRENT_PAGE_SCHEMA}}': currentPageSchemaStr, + '{{REFERENCE_KNOWLEDGE}}': referenceContext || '', + '{{IMAGE_ASSETS}}': imageAssets.map((item) => `- `).join('\n') + } + const prompt = Object.entries(replacements).reduce( + (acc, [key, value]) => acc.replace(key, value), + agentPrompt + )packages/plugins/robot/src/composables/core/useMessageStream.ts-53-68 (1)
53-68: Sparse array risk with index-based tool call assignment.When
chunk.indexis used directly for array assignment (line 64), non-contiguous indices could create sparse arrays (e.g.,[toolCall0, undefined, undefined, toolCall3]). This may cause issues when iterating or serializing downstream.Consider using a Map or object keyed by index, then converting to array, or ensuring indices are always contiguous from the provider:
+// Alternative: Use object-based accumulation +if (!lastMessage._toolCallsMap) { + lastMessage._toolCallsMap = {} +} for (const chunk of toolCallChunks) { const { index, ...chunkWithoutIndex } = chunk - if (lastMessage.tool_calls[index]) { - mergeStringFields(lastMessage.tool_calls[index], chunkWithoutIndex) + if (lastMessage._toolCallsMap[index]) { + mergeStringFields(lastMessage._toolCallsMap[index], chunkWithoutIndex) } else { - lastMessage.tool_calls[index] = chunkWithoutIndex + lastMessage._toolCallsMap[index] = chunkWithoutIndex } } +lastMessage.tool_calls = Object.values(lastMessage._toolCallsMap)packages/plugins/robot/src/composables/features/useToolCalls.ts-115-117 (1)
115-117: Silent error swallowing hides failures.When
callToolsthrows (including on abort), the error is caught and the function silently returns without notifying the caller or logging. This makes debugging difficult.Consider at minimum logging the error or calling an error handler:
} catch (error) { + // eslint-disable-next-line no-console + console.error('Tool call execution failed:', error) return }Or propagate to
streamHandlers.onErrorif appropriate.packages/plugins/robot/src/composables/core/useConversation.ts-35-38 (1)
35-38: Context parameter mismatch withModeHooks.onMessageProcessed.Line 37 passes an empty object
{}as the context, but according tomode.types.ts(line 77),onMessageProcessedexpectscontext: { abortControllerMap: Record<string, AbortController> }. This type mismatch could cause runtime errors in hook implementations.Ensure the context matches the expected interface:
if (lastMessage) { - await onMessageProcessed(finishReason ?? 'unknown', lastMessage.content ?? '', messages.value, {}) + await onMessageProcessed(finishReason ?? 'unknown', lastMessage.content ?? '', messages.value, { + abortControllerMap: {} // Or pass the actual map if available + }) }packages/plugins/robot/src/composables/features/useToolCalls.ts-34-54 (1)
34-54: Abort signal checked too late and tool objects mutated.Two concerns:
Abort timing: The abort check (lines 49-51) occurs after each tool call completes. For long-running tools, this means an aborted request won't stop the current tool, only prevent subsequent ones.
Object mutation: Lines 39-40 mutate the original
toolobjects by addingparsedArgsandnameproperties, which could cause issues if the caller expects the original objects unchanged.Consider checking abort before each tool call and avoiding mutation:
export const callTools = async (tool_calls: any, hooks: CallToolHooks, signal: AbortController['signal']) => { const result = [] for (const tool of tool_calls) { + if (signal?.aborted) { + return Promise.reject('aborted') + } + const { name, arguments: args } = tool.function const parsedArgs = parseArgs(args) - tool.parsedArgs = parsedArgs - tool.name = name + const enrichedTool = { ...tool, parsedArgs, name } - hooks.onBeforeCallTool(tool) + hooks.onBeforeCallTool(enrichedTool) const { toolCallResult, toolCallStatus } = await callTool(name, parsedArgs) - result.push({ toolCallResult, toolCallStatus, ...tool }) + result.push({ toolCallResult, toolCallStatus, ...enrichedTool }) - hooks.onPostCallTool(tool, toolCallResult, toolCallStatus) - - if (signal?.aborted) { - return Promise.reject('aborted') - } + hooks.onPostCallTool(enrichedTool, toolCallResult, toolCallStatus) } return result }
🧹 Nitpick comments (32)
packages/layout/src/composable/useLayout.ts (1)
136-139: Defaulttoolbars.rendersentinel seems reasonable; ensure consumers handle empty stringInitializing
toolbars.renderto''matches the “no active panel” convention already used forplugins.render/settings.render. Just make sure any components readinglayoutState.toolbars.rendertreat''as “no toolbar selected” and don’t assume a non‑empty ID.packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (3)
145-205: Prefer typed error objects overany.Line 163 uses
anytype for the custom error object, which weakens type safety. Consider defining a proper error interface with aresponseproperty.Apply this approach:
+interface HttpError extends Error { + response?: Response +} + private createFetchAdapter(requestData: ChatRequestData, isStream = false) { return async (config: AxiosRequestConfig) => { // ... if (!fetchResponse.ok) { const errorText = await fetchResponse.text() - const customError: any = new Error( + const customError = new Error( `HTTP error! status: ${fetchResponse.status}${errorText ? ', details: ' + errorText : ''}` - ) + ) as HttpError customError.response = fetchResponse throw customError }
211-234: Consider extracting shared error handling logic.The error handling in
sendFetchRequest(lines 223-231) duplicates logic fromcreateFetchAdapter(lines 161-168). Consider extracting this into a shared helper method to improve maintainability.Example:
private createHttpError(response: Response, errorText: string): Error { const error = new Error( `HTTP error! status: ${response.status}${errorText ? ', details: ' + errorText : ''}` ) as HttpError error.response = response return error }Then use it in both methods:
if (!fetchResponse.ok) { const errorText = await fetchResponse.text() throw this.createHttpError(fetchResponse, errorText) }
268-287: Inconsistent error handling loses type information.The
chatmethod wraps all errors in a generic error message (line 285), losing the structured error information thattoAIAdapterErrorprovides. Consider using the adapter's error handling consistently.Apply this diff:
async chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse> { try { const requestData = await this.prepareRequestData(request, false) const headers = this.buildHeaders(false) if (this.httpClientType === 'axios' && this.axiosClient) { const response = await this.sendAxiosRequest(requestData, headers, false) return (response as { data: ChatCompletionResponse }).data || response } else { const response = await this.sendFetchRequest(requestData, headers) return await response.json() } - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error) - throw new Error(`Error in chat request: ${errorMessage}`) + } catch (error) { + throw this.toAIAdapterError(error) } }packages/plugins/robot/src/Main.vue (1)
267-272: Consider removing the artificial delay for teleport.The 1-second delay before showing the teleport container may cause a noticeable lag in the UI. If this is to ensure the target element exists, consider using a more deterministic approach.
onMounted(async () => { initChatClient() - setTimeout(() => { - showTeleport.value = true - }, 1000) + // Consider using nextTick or checking if target element exists + await nextTick() + showTeleport.value = true })packages/plugins/robot/src/services/agentServices.ts (2)
29-34: Preferfor...ofwithbreakfor early termination.Using
forEachwithreturnonly exits the current iteration callback; the loop continues iterating over remaining items even after the limit is reached. This is inefficient for large result sets.- res.forEach((item: { content: string }) => { - if (result.length + item.content.length > MAX_SEARCH_LENGTH) { - return - } - result += item.content - }) + for (const item of res as { content: string }[]) { + if (result.length + item.content.length > MAX_SEARCH_LENGTH) { + break + } + result += item.content + }
47-59: Consider adding type definitions for resource items.The function uses
anytype for resource items. Adding an interface would improve type safety and documentation.interface ResourceItem { resourceUrl: string description?: string } export const fetchAssets = async (): Promise<{ url: string; describe: string }[]> => { try { const res = (await apiService.getResourceList('1')) as ResourceItem[] || [] return res .filter((item) => item.description) .map((item) => ({ url: item.resourceUrl, describe: item.description! })) } catch (error) { logger.warn('Fetch assets failed:', error) return [] } }packages/plugins/robot/src/components/footer-extension/McpServer.vue (1)
34-34: InitialactiveCountmay be misleading.
activeCountis initialized to1before the picker component populates its actual value viav-model:activeCount. Consider initializing to0and letting the picker set the correct value.-const activeCount = ref(1) +const activeCount = ref(0)packages/plugins/robot/src/components/header-extension/robot-setting/ServiceEditDialog.vue (1)
59-65: Non-null assertion oncapabilitiesmay cause runtime errors.The template uses
model.capabilities!without null checking. WhilecreateEmptyModel()initializescapabilities, if a model fromservice.modelsis missing this field, this will throw.Consider using optional chaining with defaults or ensure capabilities exist:
<tiny-form-item label="模型能力"> <div class="capabilities-group"> - <tiny-checkbox v-model="model.capabilities!.toolCalling">工具调用</tiny-checkbox> - <tiny-checkbox v-model="model.capabilities!.vision">视觉理解</tiny-checkbox> - <tiny-checkbox v-model="model.capabilities!.compact">快速模型</tiny-checkbox> + <tiny-checkbox v-model="model.capabilities.toolCalling">工具调用</tiny-checkbox> + <tiny-checkbox v-model="model.capabilities.vision">视觉理解</tiny-checkbox> + <tiny-checkbox v-model="model.capabilities.compact">快速模型</tiny-checkbox> </div> </tiny-form-item>And ensure capabilities are always initialized when cloning existing models in
updateForm:- formData.models = service.isBuiltIn ? [] : JSON.parse(JSON.stringify(service.models)) + formData.models = service.isBuiltIn ? [] : JSON.parse(JSON.stringify(service.models)).map((m: ModelConfig) => ({ + ...m, + capabilities: m.capabilities ?? { toolCalling: false, vision: false, reasoning: false, compact: false } + }))packages/plugins/robot/src/composables/core/pageUpdater.ts (1)
20-24: Inconsistent return type when not in Agent mode.The function returns
undefinedwhen not in Agent mode but returns{ schema, isError }or{ isError, error }in other paths. This inconsistency may confuse callers.const { getSelectedModelInfo } = useModelConfig() if (getSelectedModelInfo().config?.chatMode !== ChatMode.Agent) { - return + return { isError: false } }packages/plugins/robot/src/components/header-extension/History.vue (1)
116-132: Hardcoded background color may break theming.The
background-color: whitedoesn't use a CSS variable, which could cause issues with dark mode or custom themes..tr-history-container { position: absolute; right: 100%; top: 100%; z-index: var(--tr-z-index-popover); width: 300px; height: 400px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04); - background-color: white; + background-color: var(--tr-color-bg-container, white); padding: 16px; border-radius: 16px;packages/plugins/robot/src/composables/modes/useChatMode.ts (1)
19-39: Non-null assertions ontool.functionmay cause runtime errors.Lines 26, 30, and 33 use
tool.function!but if the tool object doesn't have afunctionproperty (e.g., newer OpenAI tool formats), this will throw.Consider defensive access:
- currentToolCallContent.content.params = tool.parsedArgs || tool.function!.arguments || {} + currentToolCallContent.content.params = tool.parsedArgs || tool.function?.arguments || {} } else { renderContent.push({ type: 'tool', - name: tool.name || tool.function!.name, + name: (tool.name || tool.function?.name) as string, status: 'running', content: { - params: tool.parsedArgs || tool.function!.arguments || {} + params: tool.parsedArgs || tool.function?.arguments || {} },packages/plugins/robot/src/composables/core/useMessageStream.ts (2)
6-17: Consider using stricter type definitions for hooks.The
hooksinterface usesanytypes extensively (messages: any[],data: any,content: any,tools: any[],currentMessage: any). This reduces type safety and makes it harder to catch integration errors at compile time.export interface StreamDataHandlerOptions { getContentType: () => string hooks: { - onStreamStart: (messages: any[]) => void - onStreamData: (data: any, content: any, messages: any[]) => void - onStreamTools: (tools: any[], context: { currentMessage: any }) => void + onStreamStart: (messages: Message[]) => void + onStreamData: (data: ChatCompletionStreamResponse, content: string, messages: Message[]) => void + onStreamTools: (tools: ResponseToolCall[], context: { currentMessage: Message }) => void } statusManager: { isStreaming: () => boolean setStreaming: () => void } }
21-31: Non-null assertion after.at(-1)is safe here but consider i18n for the hardcoded string.The non-null assertion on line 31 is guarded by the check on line 21 which ensures the last item exists. However, the hardcoded Chinese string
'深度思考'should be externalized for internationalization support.packages/plugins/robot/src/composables/features/useMcp.ts (1)
120-124: Consider reusinglistToolsto avoid duplicate API call patterns.
getLLMToolsduplicates the MCP client call fromlistTools. This could lead to maintenance issues if the API call logic changes.const getLLMTools = async () => { - const mcpTools = await getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.listTools() - llmTools = convertMCPToOpenAITools(mcpTools?.tools || []) - return llmTools + const mcpToolsResponse = await getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.listTools() + return convertMCPToOpenAITools(mcpToolsResponse?.tools || []) }Also, the module-level
llmToolsvariable (line 110) appears unused ifgetLLMToolsalways fetches fresh data. Consider removing it if caching isn't needed.packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (3)
233-248: Model value parsing assumes::won't appear in serviceId or modelName.The split on
'::'(lines 234, 260) assumes neitherserviceIdnormodelNamecontain this delimiter. If a model name likeprovider::v2::largeexists, parsing would break.Consider using a more robust delimiter or limiting the split:
-const [defaultServiceId, defaultModelName] = state.modelSelection.defaultModel.split('::') +const [defaultServiceId, ...rest] = state.modelSelection.defaultModel.split('::') +const defaultModelName = rest.join('::')
284-292: Avoidas anytype assertion; use proper typing.The
as anyon line 290 bypasses type checking. SinceaddCustomServiceexpects a specific type, ensure the partial data conforms to required fields or use a proper type guard.const handleServiceConfirm = (serviceData: Partial<ModelService>) => { if (serviceData.id) { // 更新现有服务 updateService(serviceData.id, serviceData) } else { // 添加新服务 - addCustomService(serviceData as any) + addCustomService(serviceData as Omit<ModelService, 'id' | 'isBuiltIn'>) } }
183-183: Unused ref:modelSelectionForm.The
modelSelectionFormref is declared but not used anywhere in the component. If form validation is planned, keep it; otherwise, consider removing.packages/plugins/robot/src/constants/prompts/index.ts (1)
72-86: Hardcoded ignore lists may need maintenance.The
ignoreGroups,ignoreComponents, andignorePropertiesarrays are hardcoded. Consider externalizing these to a configuration file for easier maintenance, especially the comment notingTinyNumericis ignored due to an error.packages/plugins/robot/src/composables/modes/useAgentMode.ts (1)
169-180: Non-null assertions assume correct lifecycle ordering.Lines 175-179 use
.at(-1)!which assumesrenderContenthas items. This should be safe ifonBeforeCallToolis always called beforeonPostCallTool, but adding a defensive check would improve robustness.packages/plugins/robot/src/types/mode.types.ts (1)
25-82: Consider replacinganytypes with more specific interfaces.The
ModeHooksinterface usesanyextensively for parameters likeconversationState,messages,apis,requestParams, andcontext. This reduces type safety and IDE support.Consider defining specific interfaces for these parameters:
+export interface ConversationState { + currentId: string + conversations: Conversation[] + // ... other properties +} + +export interface ModeApis { + sendMessage: (content: string) => Promise<void> + // ... other methods +} + export interface ModeHooks { // ... - onConversationStart: (conversationState: any, messages: any[], apis: any) => void + onConversationStart: (conversationState: ConversationState, messages: LLMMessage[], apis: ModeApis) => void // ... }This would improve maintainability and catch type errors at compile time.
packages/plugins/robot/src/services/api.ts (2)
39-46: Inconsistent handling ofoptions.urlcompared tochatCompletions.
chatCompletions(line 26) usesoptions?.urlto allow customizable endpoints, butagentChatignores theoptions.urlparameter entirely. This inconsistency may confuse consumers who expect uniform behavior.Consider aligning the behavior:
agentChat: (body: LLMRequestBody, options: RequestOptions = {}) => { - return getMetaApi(META_SERVICE.Http).post('/app-center/api/ai/chat', body, { + return getMetaApi(META_SERVICE.Http).post(options?.url || '/app-center/api/ai/chat', body, { headers: { 'Content-Type': 'application/json', ...options?.headers } }) },
85-92:getHttpClientmay returnundefinedwithout explicit typing.The optional chaining
getMetaApi(META_SERVICE.Http)?.getHttp()can returnundefinedifgetMetaApireturns a falsy value. The return type should be explicitly annotated to reflect this.export const httpApi = { - getHttpClient: () => { + getHttpClient: (): ReturnType<typeof getMetaApi> | undefined => { return getMetaApi(META_SERVICE.Http)?.getHttp() } }Alternatively, document or handle the undefined case explicitly.
packages/plugins/robot/src/utils/chat.utils.ts (2)
36-55:mergeStringFieldsdoesn't handle arrays.When
targetValueorvalueis an array, the function will attempt to iterate it as an object, which may produce unexpected results. Arrays with numeric indices will be converted to object-like structures.export const mergeStringFields = (target: Record<string, any>, source: Record<string, any>) => { for (const [key, value] of Object.entries(source)) { const targetValue = target[key] if (targetValue) { if (typeof targetValue === 'string' && typeof value === 'string') { target[key] = targetValue + value - } else if (targetValue && typeof targetValue === 'object' && value && typeof value === 'object') { + } else if ( + targetValue && + typeof targetValue === 'object' && + !Array.isArray(targetValue) && + value && + typeof value === 'object' && + !Array.isArray(value) + ) { target[key] = mergeStringFields(targetValue, value) + } else if (Array.isArray(targetValue) && Array.isArray(value)) { + // Handle array concatenation if needed + target[key] = [...targetValue, ...value] } } else { target[key] = value } } return target }
99-105:addSystemPromptmutates the input array.The function directly mutates
messagesviaunshiftand property assignment. This side-effect pattern can be error-prone and harder to debug. Consider documenting this behavior clearly or returning a new array.If mutation is intentional for performance, add a JSDoc note:
+/** + * Ensures the first message is a system prompt. + * @param messages - Message array (mutated in place) + * @param prompt - System prompt content + */ export const addSystemPrompt = (messages: LLMMessage[], prompt: string = '') => {packages/plugins/robot/src/composables/features/useToolCalls.ts (1)
119-119: MutatingcurrentMessageby deletingtool_calls.Directly deleting a property from
currentMessage(which comes from the messages array) creates a side effect that may not be expected by callers. This could interfere with message history or debugging.Consider tracking tool_calls state separately rather than mutating the message object, or document this behavior clearly.
packages/plugins/robot/src/composables/core/useConversation.ts (2)
66-78: Silent failure when conversation not found.
switchConversationreturns early without any indication when the conversation ID doesn't exist (line 68). This could make debugging difficult for callers.Consider returning a result indicating success/failure or throwing:
const switchConversation = (conversationId: string, onStart?: (state: any, messages: any, methods: any) => void) => { const conversation = conversationState.conversations.find((c) => c.id === conversationId) - if (!conversation) return + if (!conversation) { + console.warn(`Conversation not found: ${conversationId}`) + return false + } const result = conversationMethods.switchConversation(conversationId) if (onStart) { onStart(conversationState, messageManager.messages.value, conversationMethods) } - return result + return result ?? true }
90-94:substring(0, 20)may split multi-byte Unicode characters.Using
substringon a string with multi-byte characters (e.g., emoji, CJK characters) can result in corrupted display when cutting mid-character.Consider using spread or
Array.fromfor safe Unicode handling:- conversationMethods.updateTitle(currentId, contentStr.substring(0, 20)) + const chars = [...contentStr] + conversationMethods.updateTitle(currentId, chars.slice(0, 20).join(''))packages/plugins/robot/src/composables/useChat.ts (3)
42-44: Module-level mutable state may cause issues in multi-instance scenarios.
chatStatusandabortControllerMapare module-level variables shared across all consumers. This singleton pattern works for a single chat instance but could cause state leakage if multiple chat instances are needed, or issues in SSR environments where state persists across requests.If multiple chat instances are needed in the future, consider moving these into the composable's closure or using a
Mapkeyed by conversation ID:+const createChatState = () => ({ + chatStatus: CHAT_STATUS.IDLE as CHAT_STATUS, + abortControllerMap: {} as Record<string, AbortController> +}) export default function () { + const state = createChatState() // ... use state.chatStatus, state.abortControllerMap }
75-87: Consider making the fallback model configurable.The hardcoded fallback
'deepseek-v3'at Line 81 may not be appropriate for all deployments. Consider making this configurable via the mode or config system.
193-202: Type assertion may hide undefined content.At Line 201,
messages.value.at(-1)?.content as stringwill beundefinedcast tostringif the array is empty or content is undefined. Consider using nullish coalescing for safety.- onRequestEnd('aborted', messageManager.messages.value.at(-1)?.content as string, messageManager.messages.value) + onRequestEnd('aborted', messageManager.messages.value.at(-1)?.content ?? '', messageManager.messages.value)packages/plugins/robot/src/composables/core/useConfig.ts (1)
171-178: Read-modify-write pattern may cause data loss on concurrent updates.
saveRobotSettingStatereads the current state at Line 175, then merges and writes. If two updates happen concurrently (e.g., from different tabs or rapid UI interactions), one update may overwrite the other. Consider using a lock or always saving the full state.const saveRobotSettingState = (state: Partial<RobotSettings>, updateState = true) => { if (updateState) { Object.assign(robotSettingState, state) } - const currentState = loadRobotSettingState() || initDefaultSettings() - const newState = { ...currentState, ...state, version: SETTING_VERSION } + // Use in-memory state as source of truth to avoid race conditions + const newState = { ...robotSettingState, version: SETTING_VERSION } localStorage.setItem(SETTING_STORAGE_KEY, JSON.stringify(newState)) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
packages/design-core/assets/back.svgis excluded by!**/*.svgpackages/design-core/assets/intelligent-construction.svgis excluded by!**/*.svg
📒 Files selected for processing (49)
designer-demo/public/mock/bundle.json(3 hunks)packages/canvas/container/src/components/CanvasResize.vue(1 hunks)packages/common/js/completion.js(3 hunks)packages/layout/src/composable/useLayout.ts(2 hunks)packages/plugins/materials/src/composable/useMaterial.ts(1 hunks)packages/plugins/robot/index.ts(1 hunks)packages/plugins/robot/meta.js(1 hunks)packages/plugins/robot/package.json(1 hunks)packages/plugins/robot/src/Main.vue(2 hunks)packages/plugins/robot/src/components/chat/RobotChat.vue(1 hunks)packages/plugins/robot/src/components/footer-extension/McpServer.vue(1 hunks)packages/plugins/robot/src/components/footer-extension/index.ts(1 hunks)packages/plugins/robot/src/components/header-extension/History.vue(1 hunks)packages/plugins/robot/src/components/header-extension/index.ts(1 hunks)packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue(1 hunks)packages/plugins/robot/src/components/header-extension/robot-setting/ServiceEditDialog.vue(1 hunks)packages/plugins/robot/src/components/icons/index.ts(1 hunks)packages/plugins/robot/src/components/renderers/AgentRenderer.vue(1 hunks)packages/plugins/robot/src/components/renderers/index.ts(1 hunks)packages/plugins/robot/src/composables/core/pageUpdater.ts(1 hunks)packages/plugins/robot/src/composables/core/useConfig.ts(1 hunks)packages/plugins/robot/src/composables/core/useConversation.ts(1 hunks)packages/plugins/robot/src/composables/core/useMessageStream.ts(1 hunks)packages/plugins/robot/src/composables/features/useMcp.ts(2 hunks)packages/plugins/robot/src/composables/features/useToolCalls.ts(1 hunks)packages/plugins/robot/src/composables/modes/useAgentMode.ts(1 hunks)packages/plugins/robot/src/composables/modes/useChatMode.ts(1 hunks)packages/plugins/robot/src/composables/modes/useMode.ts(1 hunks)packages/plugins/robot/src/composables/useChat.ts(1 hunks)packages/plugins/robot/src/constants/model-config.ts(1 hunks)packages/plugins/robot/src/constants/prompts/data/components.json(1 hunks)packages/plugins/robot/src/constants/prompts/data/examples.json(1 hunks)packages/plugins/robot/src/constants/prompts/index.ts(1 hunks)packages/plugins/robot/src/constants/prompts/templates/agent-prompt.md(1 hunks)packages/plugins/robot/src/metas/index.ts(1 hunks)packages/plugins/robot/src/services/OpenAICompatibleProvider.ts(1 hunks)packages/plugins/robot/src/services/agentServices.ts(1 hunks)packages/plugins/robot/src/services/aiClient.ts(1 hunks)packages/plugins/robot/src/services/api.ts(1 hunks)packages/plugins/robot/src/types/chat.types.ts(3 hunks)packages/plugins/robot/src/types/index.ts(1 hunks)packages/plugins/robot/src/types/mode.types.ts(1 hunks)packages/plugins/robot/src/types/setting.types.ts(1 hunks)packages/plugins/robot/src/utils/chat.utils.ts(1 hunks)packages/plugins/robot/src/utils/index.ts(1 hunks)packages/plugins/robot/src/utils/meta.utils.ts(1 hunks)packages/plugins/robot/src/utils/schema.utils.ts(1 hunks)packages/register/src/constants.ts(1 hunks)tsconfig.app.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/plugins/robot/index.ts
- packages/plugins/robot/src/metas/index.ts
- packages/common/js/completion.js
- packages/plugins/robot/src/components/renderers/AgentRenderer.vue
🧰 Additional context used
🧠 Learnings (18)
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.
Applied to files:
packages/plugins/robot/src/components/icons/index.tspackages/plugins/robot/package.json
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.
Applied to files:
packages/plugins/robot/src/components/icons/index.tspackages/plugins/robot/package.json
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: The SvgIcon component is globally registered and available throughout the application without requiring explicit imports.
Applied to files:
packages/plugins/robot/src/components/icons/index.ts
📚 Learning: 2025-03-19T03:13:51.520Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1226
File: packages/canvas/container/src/components/CanvasDivider.vue:184-185
Timestamp: 2025-03-19T03:13:51.520Z
Learning: The CSS bug in packages/canvas/container/src/components/CanvasDivider.vue where verLeft already includes "px" but is being appended again in the style object will be fixed in a future update, as confirmed by gene9831.
Applied to files:
packages/canvas/container/src/components/CanvasResize.vue
📚 Learning: 2025-03-20T07:20:12.221Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1233
File: packages/canvas/container/src/components/CanvasDivider.vue:184-185
Timestamp: 2025-03-20T07:20:12.221Z
Learning: In CanvasDivider.vue, even though state.verLeft and state.horizontalTop already include 'px' suffix, the CSS properties in state.dividerStyle still need to append 'px' again according to gene9831, suggesting that these state variables might be processed differently than expected when used in style binding.
Applied to files:
packages/canvas/container/src/components/CanvasResize.vue
📚 Learning: 2025-01-14T06:55:14.457Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/canvas-function/design-mode.ts:6-13
Timestamp: 2025-01-14T06:55:14.457Z
Learning: The code in `packages/canvas/render/src/canvas-function/design-mode.ts` is migrated code that should be preserved in its current form during the migration process. Refactoring suggestions for type safety and state management improvements should be considered in future PRs.
Applied to files:
packages/canvas/container/src/components/CanvasResize.vue
📚 Learning: 2025-01-14T08:45:57.032Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/application-function/global-state.ts:12-25
Timestamp: 2025-01-14T08:45:57.032Z
Learning: The code in `packages/canvas/render/src/application-function/global-state.ts` is migrated from an existing codebase and should be handled with care when making modifications.
Applied to files:
packages/canvas/container/src/components/CanvasResize.vue
📚 Learning: 2025-01-14T04:22:02.404Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/builtin/builtin.json:645-850
Timestamp: 2025-01-14T04:22:02.404Z
Learning: In TinyEngine, components must use inline styles instead of CSS classes because components cannot carry class styles when dragged into the canvas.
Applied to files:
packages/plugins/robot/src/constants/prompts/data/components.json
📚 Learning: 2025-07-03T09:22:59.512Z
Learnt from: hexqi
Repo: opentiny/tiny-engine PR: 1501
File: mockServer/src/tool/Common.js:79-82
Timestamp: 2025-07-03T09:22:59.512Z
Learning: In the tiny-engine project, the mockServer code uses ES6 import syntax but is compiled to CommonJS output. This means CommonJS globals like `__dirname` are available at runtime, while ES6 module-specific features like `import.meta` would cause runtime errors.
Applied to files:
packages/plugins/robot/src/utils/index.tspackages/plugins/robot/package.json
📚 Learning: 2025-01-14T04:25:08.323Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/material-function/material-getter.ts:55-88
Timestamp: 2025-01-14T04:25:08.323Z
Learning: The BlockLoadError component in packages/canvas/render/src/material-function/material-getter.ts requires a `name` prop to display which block failed to load.
Applied to files:
packages/plugins/materials/src/composable/useMaterial.ts
📚 Learning: 2024-10-15T02:45:17.168Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 830
File: packages/common/component/MetaChildItem.vue:50-56
Timestamp: 2024-10-15T02:45:17.168Z
Learning: In `packages/common/component/MetaChildItem.vue`, when checking if `text` is an object in the computed property `title`, ensure that `text` is not `null` because `typeof null === 'object'` in JavaScript. Use checks like `text && typeof text === 'object'` to safely handle `null` values.
Applied to files:
packages/plugins/materials/src/composable/useMaterial.ts
📚 Learning: 2025-01-14T06:59:02.999Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/material-function/support-collection.ts:3-15
Timestamp: 2025-01-14T06:59:02.999Z
Learning: The code in `packages/canvas/render/src/material-function/support-collection.ts` is migrated code that should not be modified at this time to maintain stability during the migration process.
Applied to files:
packages/plugins/materials/src/composable/useMaterial.ts
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。
Applied to files:
packages/plugins/robot/src/Main.vue
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
Repo: opentiny/tiny-engine PR: 837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
Applied to files:
packages/plugins/robot/package.json
📚 Learning: 2025-01-14T04:25:46.281Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/material-function/material-getter.ts:66-80
Timestamp: 2025-01-14T04:25:46.281Z
Learning: In the tiny-engine project, styles from block components are processed through Vite's CSS compilation pipeline, and additional style sanitization libraries should be avoided to maintain consistency with this approach.
Applied to files:
packages/plugins/robot/package.json
📚 Learning: 2024-12-14T05:53:28.501Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 917
File: docs/开始/快速上手.md:31-31
Timestamp: 2024-12-14T05:53:28.501Z
Learning: The latest stable version of `opentiny/tiny-engine-cli` is `2.0.0`, and documentation should reference this version instead of any release candidates.
Applied to files:
packages/plugins/robot/package.json
📚 Learning: 2025-01-14T08:42:18.574Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1038
File: packages/plugins/block/index.js:24-24
Timestamp: 2025-01-14T08:42:18.574Z
Learning: In the tiny-engine project, breaking changes are documented in the changelog rather than in JSDoc comments or separate migration guides.
Applied to files:
packages/plugins/robot/package.json
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.
Applied to files:
packages/plugins/robot/src/composables/useChat.ts
🧬 Code graph analysis (15)
packages/plugins/robot/src/utils/meta.utils.ts (1)
packages/register/src/common.ts (1)
getOptions(32-34)
packages/plugins/robot/src/services/agentServices.ts (1)
packages/plugins/robot/src/services/api.ts (1)
apiService(99-104)
packages/plugins/robot/src/composables/modes/useChatMode.ts (3)
packages/plugins/robot/src/types/mode.types.ts (1)
ModeHooks(25-82)packages/plugins/robot/src/utils/chat.utils.ts (2)
removeLoading(88-97)serializeError(18-28)packages/plugins/robot/src/composables/features/useMcp.ts (1)
useMcpServer(126-137)
packages/plugins/robot/src/types/chat.types.ts (1)
packages/plugins/robot/src/types/mcp.types.ts (1)
ResponseToolCall(1-7)
packages/plugins/robot/src/composables/modes/useMode.ts (2)
packages/plugins/robot/src/types/mode.types.ts (1)
ModeHooks(25-82)packages/plugins/robot/src/utils/meta.utils.ts (1)
getRobotServiceOptions(4-6)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)
packages/plugins/robot/src/utils/chat.utils.ts (1)
formatMessages(6-16)packages/plugins/datasource/src/js/datasource.ts (1)
response(101-103)
packages/plugins/robot/src/types/mode.types.ts (1)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
ModeHooks(83-83)
packages/plugins/robot/src/utils/schema.utils.ts (1)
packages/plugins/robot/src/utils/chat.utils.ts (1)
serializeError(18-28)
packages/plugins/robot/src/composables/features/useToolCalls.ts (4)
packages/plugins/robot/src/composables/features/useMcp.ts (1)
useMcpServer(126-137)packages/plugins/robot/src/utils/chat.utils.ts (2)
serializeError(18-28)formatMessages(6-16)packages/plugins/robot/src/types/chat.types.ts (2)
LLMMessage(33-37)RobotMessage(44-49)packages/plugins/robot/src/types/mcp.types.ts (1)
ResponseToolCall(1-7)
packages/plugins/robot/src/composables/core/pageUpdater.ts (3)
packages/plugins/robot/src/utils/schema.utils.ts (5)
getJsonObjectString(151-155)isValidFastJsonPatch(127-134)jsonPatchAutoFix(139-146)fixMethods(48-64)schemaAutoFix(69-82)packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/register/src/hooks.ts (1)
useHistory(82-82)
packages/plugins/robot/src/composables/modes/useAgentMode.ts (9)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
ModeHooks(83-83)packages/utils/src/utils/index.ts (1)
deepClone(328-330)packages/common/js/completion.js (3)
useCanvas(178-178)referenceContext(180-180)referenceContext(195-195)packages/plugins/robot/src/services/agentServices.ts (2)
search(22-41)fetchAssets(47-60)packages/plugins/robot/src/constants/prompts/index.ts (2)
getAgentSystemPrompt(151-171)getJsonFixPrompt(175-206)packages/plugins/robot/src/utils/chat.utils.ts (2)
addSystemPrompt(99-105)removeLoading(88-97)packages/plugins/robot/src/composables/core/pageUpdater.ts (1)
updatePageSchema(73-73)packages/plugins/robot/src/utils/schema.utils.ts (1)
isValidJsonPatchObjectString(160-175)packages/plugins/robot/src/services/aiClient.ts (1)
client(21-21)
packages/plugins/robot/src/utils/chat.utils.ts (1)
packages/plugins/robot/src/types/chat.types.ts (2)
LLMMessage(33-37)RobotMessage(44-49)
packages/plugins/robot/src/composables/core/useMessageStream.ts (3)
packages/plugins/robot/src/types/chat.types.ts (1)
Message(39-42)packages/plugins/robot/src/types/mcp.types.ts (1)
ResponseToolCall(1-7)packages/plugins/robot/src/utils/chat.utils.ts (1)
mergeStringFields(36-55)
packages/plugins/robot/src/services/api.ts (2)
packages/plugins/robot/src/types/chat.types.ts (2)
LLMRequestBody(51-57)RequestOptions(5-10)packages/register/src/constants.ts (1)
META_SERVICE(1-25)
packages/plugins/robot/src/composables/features/useMcp.ts (2)
packages/plugins/robot/src/types/chat.types.ts (1)
RequestTool(12-31)packages/register/src/constants.ts (1)
META_SERVICE(1-25)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/constants/prompts/templates/agent-prompt.md
46-46: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
47-47: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
48-48: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
49-49: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
50-50: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
51-51: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
52-52: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
53-53: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
54-54: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
55-55: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
56-56: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
57-57: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
58-58: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
59-59: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
60-60: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
61-61: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
62-62: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
63-63: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
64-64: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
65-65: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
66-66: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
67-67: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
68-68: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
69-69: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
70-70: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
71-71: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
72-72: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
73-73: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
74-74: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
75-75: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
76-76: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
77-77: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
78-78: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
79-79: Unordered list indentation
Expected: 4; Actual: 10
(MD007, ul-indent)
80-80: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
81-81: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
82-82: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
83-83: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
84-84: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
85-85: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
86-86: Unordered list indentation
Expected: 2; Actual: 6
(MD007, ul-indent)
93-93: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
98-98: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
103-103: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
110-110: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
120-120: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
125-125: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
130-130: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
| const getCurrentMode = (): ModeHooks => { | ||
| const { getSelectedModelInfo } = useModelConfig() | ||
| return getModeInstance(getSelectedModelInfo().config!.chatMode) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-null assertion on config may throw.
Line 51 uses config!.chatMode which assumes config is always defined. If getSelectedModelInfo() returns an object with undefined config, this will throw a runtime error.
const getCurrentMode = (): ModeHooks => {
const { getSelectedModelInfo } = useModelConfig()
- return getModeInstance(getSelectedModelInfo().config!.chatMode)
+ const modelInfo = getSelectedModelInfo()
+ const chatMode = modelInfo.config?.chatMode ?? ChatMode.Chat
+ return getModeInstance(chatMode)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const getCurrentMode = (): ModeHooks => { | |
| const { getSelectedModelInfo } = useModelConfig() | |
| return getModeInstance(getSelectedModelInfo().config!.chatMode) | |
| } | |
| const getCurrentMode = (): ModeHooks => { | |
| const { getSelectedModelInfo } = useModelConfig() | |
| const modelInfo = getSelectedModelInfo() | |
| const chatMode = modelInfo.config?.chatMode ?? ChatMode.Chat | |
| return getModeInstance(chatMode) | |
| } |
🤖 Prompt for AI Agents
In packages/plugins/robot/src/composables/modes/useMode.ts around lines 49 to
52, the code uses a non-null assertion config!.chatMode which will throw if
getSelectedModelInfo().config is undefined; change this to safely handle a
missing config by first retrieving selected = getSelectedModelInfo(), then if
selected.config is undefined provide a sensible fallback (e.g., a default
chatMode or default ModeHooks) or throw a clear, descriptive error; finally pass
the resolved chatMode into getModeInstance (use optional chaining and nullish
coalescing or an explicit guard) so runtime exceptions are avoided and behavior
is deterministic.
| const sendUserMessage = async () => { | ||
| nextTick(() => { | ||
| const assistantMessage: ChatMessage = { | ||
| role: 'assistant', | ||
| content: '', | ||
| renderContent: [{ type: getLoadingType() }] | ||
| } | ||
| messageManager.messages.value.push(assistantMessage) | ||
| }) | ||
| await messageManager.send() | ||
| if (messageManager.messageState.status === STATUS.ERROR) { | ||
| removeLoading(messageManager.messages.value) | ||
| await handleRequestError( | ||
| messageManager.messageState.errorMsg, | ||
| messageManager.messages.value, | ||
| messageManager.messageState | ||
| ) | ||
| } | ||
| onMessageSent() | ||
| autoSetTitle() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nextTick is not awaited, causing potential race condition.
The nextTick at Line 172 is called but not awaited, meaning messageManager.send() at Line 180 may execute before the assistant message is pushed to the array. This could cause the loading indicator to not appear or the message order to be incorrect.
const sendUserMessage = async () => {
- nextTick(() => {
+ await nextTick()
+ {
const assistantMessage: ChatMessage = {
role: 'assistant',
content: '',
renderContent: [{ type: getLoadingType() }]
}
messageManager.messages.value.push(assistantMessage)
- })
+ }
await messageManager.send()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const sendUserMessage = async () => { | |
| nextTick(() => { | |
| const assistantMessage: ChatMessage = { | |
| role: 'assistant', | |
| content: '', | |
| renderContent: [{ type: getLoadingType() }] | |
| } | |
| messageManager.messages.value.push(assistantMessage) | |
| }) | |
| await messageManager.send() | |
| if (messageManager.messageState.status === STATUS.ERROR) { | |
| removeLoading(messageManager.messages.value) | |
| await handleRequestError( | |
| messageManager.messageState.errorMsg, | |
| messageManager.messages.value, | |
| messageManager.messageState | |
| ) | |
| } | |
| onMessageSent() | |
| autoSetTitle() | |
| } | |
| const sendUserMessage = async () => { | |
| await nextTick() | |
| const assistantMessage: ChatMessage = { | |
| role: 'assistant', | |
| content: '', | |
| renderContent: [{ type: getLoadingType() }] | |
| } | |
| messageManager.messages.value.push(assistantMessage) | |
| await messageManager.send() | |
| if (messageManager.messageState.status === STATUS.ERROR) { | |
| removeLoading(messageManager.messages.value) | |
| await handleRequestError( | |
| messageManager.messageState.errorMsg, | |
| messageManager.messages.value, | |
| messageManager.messageState | |
| ) | |
| } | |
| onMessageSent() | |
| autoSetTitle() | |
| } |
🤖 Prompt for AI Agents
In packages/plugins/robot/src/composables/useChat.ts around lines 171 to 191,
nextTick() is invoked but not awaited which can let messageManager.send() run
before the assistant message is actually pushed; update the flow to await
nextTick() (or chain a .then) so the assistant message is guaranteed to be in
messageManager.messages before calling await messageManager.send(), preserving
correct message order and ensuring the loading indicator is present.
c03b8bf to
299a48c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (8)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
63-102: Error type detection relies on fragile string matching.The method infers error types by searching for substrings like "http error", "network", "fetch", and "timeout" in lowercased error messages. This approach is brittle—error messages can vary across browsers, fetch implementations, and network stacks.
Consider enhancing error detection by checking for specific error types first (TypeError for fetch failures, DOMException for aborts) before falling back to string matching, or preserve structured error info (status codes, error names) from the source.
packages/plugins/robot/src/composables/useChat.ts (4)
62-73: Guard against undefined service inbeforeRequest.
getSelectedModelInfo().servicecan benull(as seen in useConfig.ts Line 401), but Lines 66-67 use non-null assertionservice!.apiKey. This will throw a runtime error if no service is configured.Add a guard:
const { service } = getSelectedModelInfo() - if (getConfig().apiKey !== service!.apiKey) { - updateConfig({ apiKey: service!.apiKey }) + if (service && getConfig().apiKey !== service.apiKey) { + updateConfig({ apiKey: service.apiKey }) }
89-106: Add null check forlastMessage.
messages.at(-1)at Line 91 can returnundefinedif the messages array is empty. The subsequent access tolastMessage.content(Line 93) andlastMessage.tool_calls(Line 95) would throw a runtime error.Add a guard:
const lastMessage = messages.at(-1) + if (!lastMessage) { + chatStatus = CHAT_STATUS.FINISHED + return + } await onRequestEnd(finishReason, lastMessage.content, messages)
108-112: Add null check for last message in error handler.Similar to
handleFinishRequest,messages.at(-1).contentat Line 110 will throw if the messages array is empty.Apply this diff:
chatStatus = CHAT_STATUS.FINISHED - await onRequestEnd('error', messages.at(-1).content, messages, { error }) + const lastMessage = messages.at(-1) + await onRequestEnd('error', lastMessage?.content ?? '', messages, { error }) messageState.status = STATUS.ERROR
171-191:nextTickis not awaited, causing potential race condition.The
nextTickat Line 172 is called but not awaited, meaningmessageManager.send()at Line 180 may execute before the assistant message is pushed to the array. This could cause the loading indicator to not appear or the message order to be incorrect.Apply this diff:
const sendUserMessage = async () => { - nextTick(() => { + await nextTick() + { const assistantMessage: ChatMessage = { role: 'assistant', content: '', renderContent: [{ type: getLoadingType() }] } messageManager.messages.value.push(assistantMessage) - }) + } await messageManager.send()packages/plugins/robot/src/composables/modes/useChatMode.ts (1)
107-117: Potential TypeError: accessingextraData.errorwithout null check.The
extraDataparameter is marked as optional (extraData?: Record<string, unknown>), but line 115 accessesextraData.errordirectly without checking ifextraDataexists. This will throw aTypeErrorwhen the request is aborted or errors withoutextraDatabeing passed.Apply this diff:
if (finishReason === 'aborted' || finishReason === 'error') { removeLoading(messages) - messages.at(-1)!.renderContent.push({ type: 'text', content: serializeError(extraData.error) }) + messages.at(-1)!.renderContent.push({ type: 'text', content: serializeError(extraData?.error) }) }packages/plugins/robot/src/composables/modes/useAgentMode.ts (2)
93-94: Variable shadowing:pageSchemais redeclared.Line 94 declares
const pageSchemawhich shadows the function-scopedlet pageSchemafrom line 59. This appears unintentional since line 137 also reassigns the outerpageSchema.Remove the
constto use the outer variable:const onBeforeRequest = async (requestParams: any) => { - const pageSchema = deepClone(useCanvas().pageState.pageSchema) + pageSchema = deepClone(useCanvas().pageState.pageSchema)
207-220: Global config mutation during JSON fix could cause race conditions.Lines 207 and 220 temporarily change the global
apiUrlviaupdateConfig. If another request fires concurrently, it would use the wrong URL.Consider passing the URL directly to the
client.chatcall via a per-request option or use a dedicated client instance for fix requests:- updateConfig({ apiUrl: '/app-center/api/chat/completions' }) messages.at(-1).renderContent.at(-1).status = 'fix' const fixedResponse = await client.chat({ messages: [{ role: 'user', content: getJsonFixPrompt(content, jsonValidResult.error) }], - options: { signal: abortControllerMap.errorFix?.signal, beforeRequest: beforeRequest as any } + options: { + signal: abortControllerMap.errorFix?.signal, + beforeRequest: (params) => { + params.apiUrl = '/app-center/api/chat/completions' + return beforeRequest(params) + } + } }) - updateConfig({ apiUrl: getApiUrl() })
🧹 Nitpick comments (3)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
268-287: Consider using a consistent return type from axios pathway.Line 277 attempts to unwrap the response with
(response as { data: ChatCompletionResponse }).data || response, which suggests uncertainty about whether the axios client returns the raw data or a wrapper. This fallback pattern can mask bugs if the axios client changes.Clarify the axios client contract: either always expect
{ data: ... }or always expect the raw response, and document this assumption. If the axios client is user-provided, validate its response shape at runtime.packages/plugins/robot/src/components/renderers/AgentRenderer.vue (2)
33-59: HoiststatusDataMapout ofsetupto avoid per-instance reallocation.
statusDataMapis entirely static and doesn’t depend on reactive state, but it’s recreated on every component instance. Consider moving it to module scope (and optionally freezing/typing it) so it’s allocated once and reused, which also makes it easier to share types and keep status definitions centralized.
35-56: Consider driving titles/content from config or i18n instead of hard-coding.All user-visible copy and status variants are hard-coded here (Chinese strings + fixed icons). Given the PR’s focus on making the AI plugin more extensible, it might be worth exposing these strings/icons via the robot config or an i18n layer so downstream consumers can localize or customize the agent statuses without touching this renderer.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
packages/plugins/robot/src/components/renderers/AgentRenderer.vue(1 hunks)packages/plugins/robot/src/composables/modes/useAgentMode.ts(1 hunks)packages/plugins/robot/src/composables/modes/useChatMode.ts(1 hunks)packages/plugins/robot/src/composables/useChat.ts(1 hunks)packages/plugins/robot/src/services/OpenAICompatibleProvider.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.
Applied to files:
packages/plugins/robot/src/composables/modes/useAgentMode.tspackages/plugins/robot/src/composables/useChat.ts
📚 Learning: 2025-05-28T03:58:31.212Z
Learnt from: chilingling
Repo: opentiny/tiny-engine PR: 1440
File: packages/plugins/materials/src/composable/useResource.ts:82-84
Timestamp: 2025-05-28T03:58:31.212Z
Learning: In the TinyEngine codebase, there are two different data structures for page information:
1. App schema components tree (appSchemaState.pageTree) uses nested meta structure with page.meta?.id
2. API responses from pagePluginApi.getPageById() return flattened structure with pageInfo.id and pageInfo.occupier directly
The code should use page.meta?.id when working with pageTree data and pageInfo.id when working with API response data.
Applied to files:
packages/plugins/robot/src/composables/modes/useAgentMode.ts
📚 Learning: 2024-10-30T02:19:37.775Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 886
File: packages/plugins/state/src/js/http.js:19-19
Timestamp: 2024-10-30T02:19:37.775Z
Learning: In the `packages/plugins/state/src/js/http.js` file, errors for the `requestGlobalState` function are handled by the user, so additional error handling is unnecessary.
Applied to files:
packages/plugins/robot/src/composables/useChat.ts
🧬 Code graph analysis (3)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)
packages/plugins/robot/src/utils/chat.utils.ts (1)
formatMessages(6-16)packages/plugins/datasource/src/js/datasource.ts (1)
response(101-103)
packages/plugins/robot/src/composables/useChat.ts (6)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
useMode(59-80)packages/plugins/robot/src/composables/core/useMessageStream.ts (1)
createStreamDataHandler(76-102)packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (3)
ChatRequestData(27-32)updateConfig(372-374)ProviderConfig(34-39)packages/plugins/robot/src/services/api.ts (1)
apiService(99-104)packages/plugins/robot/src/composables/features/useToolCalls.ts (1)
createToolCallHandler(83-132)packages/plugins/robot/src/utils/chat.utils.ts (1)
removeLoading(88-97)
packages/plugins/robot/src/composables/modes/useChatMode.ts (4)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
ModeHooks(83-83)packages/plugins/robot/src/types/mode.types.ts (1)
ModeHooks(25-82)packages/plugins/robot/src/utils/chat.utils.ts (2)
removeLoading(88-97)serializeError(18-28)packages/plugins/robot/src/composables/features/useMcp.ts (1)
useMcpServer(126-137)
🔇 Additional comments (5)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
323-357: LGTM: Config validation enforces axios client requirement.The validation at Lines 354-356 correctly ensures that when
httpClientTypeis 'axios', anaxiosClientmust be provided. This prevents runtime errors downstream.packages/plugins/robot/src/composables/modes/useChatMode.ts (1)
77-97: LGTM: MCP tool integration follows best practices.The
onBeforeRequesthook correctly fetches MCP tools, checks model capabilities, and conditionally injects them into the request. The logic properly respects thetoolCallingcapability flag.packages/plugins/robot/src/composables/modes/useAgentMode.ts (1)
93-133: LGTM: Agent mode request preparation is well-structured.The
onBeforeRequesthook properly builds the RAG context, formats components, injects system prompts, and configures JSON response format. The conditional logic for thinking mode and resource context is appropriate.packages/plugins/robot/src/components/renderers/AgentRenderer.vue (2)
2-8: Clarify reasoning visibility logic (hasReasoningFinishedvsstatusData).For
contentType === 'reasoning',statusDataalways uses thereasoningentry, buthasReasoningFinishedflips totrueas soon asstatus !== 'reasoning', which hides this block entirely. That means the “页面生成中” and other non‑reasoning titles will never show for reasoning content, and the component disappears instead. Please double‑check that this matches the intended lifecycle for reasoning → loading → success/failed states.Also applies to: 61-73
84-108: Styles and layout look clean and appropriate for a compact status bubble.The flex layout, typography, and ellipsis constraints are straightforward and match the component’s purpose as a small helper/agent-status renderer. No issues from a structure/readability perspective.
| private createFetchAdapter(requestData: ChatRequestData, isStream = false) { | ||
| return async (config: AxiosRequestConfig) => { | ||
| // 构建完整URL | ||
| let url = config.url | ||
| if (!url.startsWith('http') && config.apiUrl) { | ||
| url = new URL(url, config.apiUrl).href | ||
| } | ||
|
|
||
| try { | ||
| const fetchResponse = await fetch(url, { | ||
| method: config.method.toUpperCase(), | ||
| headers: config.headers, | ||
| body: JSON.stringify(requestData), | ||
| signal: config.signal | ||
| }) | ||
|
|
||
| if (!fetchResponse.ok) { | ||
| const errorText = await fetchResponse.text() | ||
| const customError: any = new Error( | ||
| `HTTP error! status: ${fetchResponse.status}${errorText ? ', details: ' + errorText : ''}` | ||
| ) | ||
| customError.response = fetchResponse | ||
| throw customError | ||
| } | ||
|
|
||
| if (isStream) { | ||
| // 流式响应处理 | ||
| return { | ||
| data: { response: fetchResponse }, | ||
| status: fetchResponse.status, | ||
| statusText: fetchResponse.statusText, | ||
| headers: fetchResponse.headers, | ||
| config | ||
| } | ||
| } | ||
|
|
||
| // 非流式响应处理 | ||
| let responseData: unknown | ||
| try { | ||
| responseData = await fetchResponse.json() | ||
| } catch (error) { | ||
| const errorMessage = error instanceof Error ? error.message : String(error) | ||
| throw new Error(`Failed to parse response JSON: ${errorMessage}`) | ||
| } | ||
|
|
||
| return { | ||
| data: responseData, | ||
| status: fetchResponse.status, | ||
| statusText: fetchResponse.statusText, | ||
| headers: fetchResponse.headers, | ||
| config | ||
| } | ||
| } catch (error) { | ||
| // 增强错误信息 | ||
| if (error instanceof Error) { | ||
| throw error | ||
| } | ||
| throw new Error(`Request failed: ${String(error)}`) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
createFetchAdapter always uses requestData from closure instead of config.data.
The adapter function at Line 145 closes over requestData but ignores config.data passed by axios. This means any transformations axios applies to the request body are silently discarded, breaking interceptors or request transforms.
Apply this diff to use the data from the axios config:
- private createFetchAdapter(requestData: ChatRequestData, isStream = false) {
+ private createFetchAdapter(isStream = false) {
return async (config: AxiosRequestConfig) => {
+ const requestData = config.data as ChatRequestData
// 构建完整URL
let url = config.urlThen update the call sites at Lines 256 and 304:
- adapter: this.createFetchAdapter(requestData, isStream)
+ adapter: this.createFetchAdapter(isStream)Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/plugins/robot/src/services/OpenAICompatibleProvider.ts around lines
145 to 205, the fetch adapter closes over the outer requestData and ignores
config.data so any axios-transformed request body or interceptors are discarded;
change the adapter to prefer config.data (falling back to the closed-over
requestData if config.data is undefined), serialize appropriately
(JSON.stringify for objects/arrays, pass through strings/Blob/FormData as-is),
and ensure headers like Content-Type are respected; after that, update the
adapter call sites at lines ~256 and ~304 to stop passing the outer requestData
and instead let axios config.data flow through (or explicitly set config.data
where needed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (2)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)
63-102: Error type detection via string matching is fragile.This concern was already raised in a previous review. The approach of parsing lowercased error messages for substrings like "http error", "network", "fetch", and "timeout" is brittle across different browsers and fetch implementations.
145-157: Adapter ignoresconfig.datain favor of closed-overrequestData.This concern was raised in a previous review. The adapter should use
config.datato respect any axios interceptor transformations, rather than always using therequestDatacaptured in the closure.
🧹 Nitpick comments (6)
docs/advanced-features/new-ai-plugin-usage.md (3)
320-320: Add language specifications to fenced code blocks.Several code blocks lack language identifiers, which prevents syntax highlighting and violates Markdown best practices:
- Line 320: empty code block (should be
text)- Line 342, 350, 358, 370, 377, 388: missing language spec (should be appropriate language like
text,bash, etc.)Example fix:
-``` +```text 用户:帮我查看当前项目有哪些页面Also applies to: 342-342, 350-350, 358-358, 370-370, 377-377, 388-388
85-86: Clarify Chinese grammar for model selection guidance.Per LanguageTool, the phrasing around "不全" may benefit from clarification. Current: "模型列表会标注能力标签(工具调用、视觉理解)" suggests the labels are incomplete. Consider: "模型列表会标注模型的能力标签(如工具调用、视觉理解)" for clarity.
101-101: Minor grammar improvement for API Key configuration note.The phrase "切换AI模型会开启新的会话" can be improved to "切换AI模型会开启一个新的会话" or "切换AI模型会开启新建会话" for better grammatical correctness in Chinese.
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (3)
128-129: RedundanttoRawcall.
formatMessagesalready callstoRaw(messages)internally (perchat.utils.ts), so wrapping it again here is unnecessary.- const messages = formatMessages(toRaw(request.messages)) + const messages = formatMessages(request.messages)
283-286: Consider preserving the original error for debugging.Wrapping the error loses the original stack trace. Using the
causeoption preserves the error chain.} catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error) - throw new Error(`Error in chat request: ${errorMessage}`) + throw new Error(`Error in chat request: ${errorMessage}`, { cause: error }) }
305-307: Complex and fragile response extraction logic.The nested type casting and fallback chain is hard to follow and may fail silently if the response structure doesn't match expectations. Consider adding explicit validation.
- const fetchResponse = ( - (response as { data: { response: Response } }).data || (response as { response: Response }) - ).response + const axiosResponse = response as { data?: { response?: Response }; response?: Response } + const fetchResponse = axiosResponse.data?.response ?? axiosResponse.response + if (!fetchResponse) { + throw new Error('Failed to extract Response from axios result') + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
docs/advanced-features/new-ai-plugin-usage.md(2 hunks)packages/plugins/robot/index.ts(1 hunks)packages/plugins/robot/src/services/OpenAICompatibleProvider.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/plugins/robot/index.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)
packages/plugins/robot/src/utils/chat.utils.ts (1)
formatMessages(6-16)packages/plugins/datasource/src/js/datasource.ts (1)
response(101-103)
🪛 LanguageTool
docs/advanced-features/new-ai-plugin-usage.md
[uncategorized] ~85-~85: 您的意思是“"不"全”?
Context: ...选。模型列表会标注能力标签(工具调用、视觉理解) - 快速模型:用于代码补全、话题命名等场景,建议选择轻量模型(如flash类型或8b/14b模型)以获得...
(BU)
[uncategorized] ~101-~101: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:新"地"会话
Context: ...sistant-settings.png) 注意:切换AI模型会开启新的会话;若选择的模型未配置API Key,会提示前往模型服务页签进行配置。 ##...
(wb4)
🪛 markdownlint-cli2 (0.18.1)
docs/advanced-features/new-ai-plugin-usage.md
11-11: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
17-17: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
320-320: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
342-342: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
350-350: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
358-358: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
370-370: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
377-377: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
388-388: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
507-507: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
513-513: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
518-518: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
535-535: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
541-541: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
🔇 Additional comments (10)
docs/advanced-features/new-ai-plugin-usage.md (2)
1-547: Documentation comprehensiveness aligns well with PR objectives.The guide effectively documents the refactored AI plugin architecture with clear coverage of:
- Agent/Chat dual-mode design and mode switching
- MCP tool integration and usage
- Multi-model support and custom service configuration
- Session management and conversation history
- Troubleshooting and performance guidance
The examples, especially the custom AI model configuration (lines 156-241) and provider setup, provide practical value to users. Consider adding cross-references to the code modules mentioned in PR objectives (e.g.,
useAgentMode.ts,useChatMode.ts,useConfig.ts) if applicable for developers who may extend the plugin.
34-34: Verify UI positioning inconsistency across documentation.Line 34 describes the AI assistant plugin bar as "右上角插件栏" (top-right), while Line 117 refers to "左下角插件栏" (bottom-left). Confirm the actual UI location and ensure consistent positioning terminology throughout the document.
Also applies to: 117-117
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (8)
1-39: LGTM!The imports are well-organized, and the interface definitions provide good flexibility. Defining local
AxiosRequestConfigandAxiosInstanceinterfaces rather than importing from axios keeps the dependency optional, which aligns well with the configurable HTTP client approach.
41-47: LGTM!Class properties are well-typed with sensible defaults. The identity function for
beforeRequestand'fetch'as default HTTP client are appropriate choices.
49-57: LGTM!The constructor correctly initializes the base class and delegates configuration to
setConfig. The destructuring at line 54 extracts fields that shouldn't be passed to the parent, thoughsetConfighandles the full config again—this is fine for ensuring consistent initialization.
104-122: LGTM!Header construction is clean and handles optional authentication correctly. The conditional
Accept: text/event-streamfor streaming requests is appropriate for SSE.
211-234: LGTM!Clean fetch implementation with proper error handling. Returning the raw
Responseobject allows flexible handling for both JSON and streaming scenarios.
240-261: LGTM with noted caveat.The method is well-structured. Note that
config.data(line 254) is effectively unused due to the fetch adapter closure issue mentioned in prior review comments.
359-366: LGTM!The method returns serializable configuration values. Omitting
beforeRequest(function) andaxiosClient(instance/function) is appropriate for config inspection and persistence use cases.
368-375: LGTM!Simple delegation pattern provides a consistent API that matches the parent class naming convention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
designer-demo/public/mock/bundle.json(3 hunks)packages/design-core/src/preview/src/Toolbar.vue(1 hunks)packages/layout/src/Main.vue(1 hunks)packages/register/src/constants.ts(1 hunks)tsconfig.app.json(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/design-core/src/preview/src/Toolbar.vue
🚧 Files skipped from review as they are similar to previous changes (3)
- designer-demo/public/mock/bundle.json
- packages/register/src/constants.ts
- packages/layout/src/Main.vue
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: push-check
🔇 Additional comments (1)
tsconfig.app.json (1)
13-13: Verifylibsettings do not conflict with base tsconfig.The addition of
"lib": ["ES2023", "DOM", "DOM.Iterable"]may override settings intsconfig.base.json. Confirm that this does not introduce unintended library-level changes or conflicts, and that DOM/DOM.Iterable are necessary additions (rather than already included via the extended config).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
是否可以考虑做成 mcp 工具,然后存放到物料中?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
当前已经是从物料api获取components了,默认不会用这个json,这个文件我去掉吧
| * 创建Axios适配器,使用fetch实现 | ||
| * @private | ||
| */ | ||
| private createFetchAdapter(requestData: ChatRequestData, isStream = false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里 requestData 和 isStream 参数是不是可以从 config 中传进来?这样的话,就不需要每次都创建一个闭包函数了?
比如:
private async fetchAdapter(config: AxiosRequestConfig) {
// 直接从 config 获取,没有闭包
const isStream = config.isStream;
const requestData = JSON.parse(config.data);
// ...
}
| if (statusCode === 401 || statusCode === 403) { | ||
| type = ErrorType.AUTHENTICATION_ERROR | ||
| } else if (statusCode === 429) { | ||
| type = ErrorType.RATE_LIMIT_ERROR | ||
| } else if (statusCode >= 500) { | ||
| type = ErrorType.SERVER_ERROR | ||
| } else { | ||
| type = ErrorType.NETWORK_ERROR | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里看下要不要用 map 结构,这样后续继续扩展
const errorTypeMap = {
'401': ErrorType.AUTHENTICATION_ERROR,
'403': ErrorType.AUTHENTICATION_ERROR,
'429': ErrorType.RATE_LIMIT_ERROR,
....
}
type = ERROR_CODE_MAP[statusCode]
|| (statusCode >= 500 ? ErrorType.SERVER_ERROR : ErrorType.NETWORK_ERROR)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
后续建议优化:
- 颜色使用 css 变量
- 支持亮色暗色模式
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Robot插件当前还没做暗色支持,后续支持
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
后续优化建议:
tr-welcome提示词支持配置Notify组件使用公共的useNotify
| singleAttachmentItems.value[0].status = 'done' | ||
| singleAttachmentItems.value[0].isUploading = false | ||
| singleAttachmentItems.value[0].messageType = 'success' | ||
| singleAttachmentItems.value[0].url = resourceUrl | ||
| } else { | ||
| singleAttachmentItems.value[0].status = 'error' | ||
| singleAttachmentItems.value[0].isUploading = false | ||
| singleAttachmentItems.value[0].messageType = 'error' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
建议一次性变更完成(原子操作):
Object.assign(singleAttachmentItems.value[0], {
status: 'done',
isUploading: false,
messageType: 'success',
url: resourceUrl
})
| <div><h3 style="padding-left: 12px">历史对话</h3></div> | ||
| <tr-icon-button | ||
| :icon="IconClose" | ||
| size="28" | ||
| svgSize="20" | ||
| @click="showHistory = false" | ||
| style="position: absolute; right: 14px; top: 14px" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
建议不要直接写行内样式
| const convertFlatToGrouped = (flatData: Conversation[]): Array<{ group: string; items: Conversation[] }> => { | ||
| const now = new Date() | ||
| const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) | ||
| const yesterday = new Date(today) | ||
| yesterday.setDate(yesterday.getDate() - 1) | ||
| const threeDaysAgo = new Date(today) | ||
| threeDaysAgo.setDate(threeDaysAgo.getDate() - 3) | ||
| const sevenDaysAgo = new Date(today) | ||
| sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7) | ||
| const groups: Record<string, { group: string; items: Conversation[] }> = { | ||
| today: { group: '今天', items: [] }, | ||
| yesterday: { group: '昨天', items: [] }, | ||
| lastThreeDays: { group: '近3天', items: [] }, | ||
| lastSevenDays: { group: '近7天', items: [] }, | ||
| earlier: { group: '更早', items: [] } | ||
| } | ||
| flatData.forEach((item) => { | ||
| const itemDate = new Date(item.createdAt) | ||
| if (itemDate >= today) { | ||
| groups.today.items.push(item) | ||
| } else if (itemDate >= yesterday) { | ||
| groups.yesterday.items.push(item) | ||
| } else if (itemDate >= threeDaysAgo) { | ||
| groups.lastThreeDays.items.push(item) | ||
| } else if (itemDate >= sevenDaysAgo) { | ||
| groups.lastSevenDays.items.push(item) | ||
| } else { | ||
| groups.earlier.items.push(item) | ||
| } | ||
| }) | ||
| Object.values(groups).forEach((group) => { | ||
| group.items.sort((a, b) => b.createdAt - a.createdAt) | ||
| }) | ||
| return Object.values(groups).filter((group) => group.items.length > 0) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
可以试试 AI 建议的重构方法。
优点:
- 配置化驱动:将时间分组逻辑提取到 groupConfigs 数组中,替代了冗长的 if-else 判断链。这使得添加或修改时间范围变得非常简单。
- 逻辑复用:通过 getStartOfDay 辅助函数统一处理日期计算,消除了重复代码。
- 声明式编程:使用 find 方法查找匹配的分组,逻辑更加清晰直观。
- 健壮性增强:在排序时统一使用 new Date(...).getTime(),确保无论 createdAt 是时间戳还是字符串都能正确排序。
const convertFlatToGrouped = (flatData: Conversation[]): Array<{ group: string; items: Conversation[] }> => {
const now = new Date()
// 辅助函数:获取指定天数前的 00:00:00 时间戳
const getStartOfDay = (offset: number) => {
const d = new Date(now)
d.setDate(d.getDate() - offset)
return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime()
}
// 分组配置:按时间倒序排列,确保 find 方法能匹配到最近的时间段
const groupConfigs = [
{ label: '今天', minTime: getStartOfDay(0) },
{ label: '昨天', minTime: getStartOfDay(1) },
{ label: '近3天', minTime: getStartOfDay(3) },
{ label: '近7天', minTime: getStartOfDay(7) },
{ label: '更早', minTime: 0 }
]
// 初始化分组结构
const groups = groupConfigs.map((config) => ({
...config,
items: [] as Conversation[]
}))
// 遍历数据并分配到对应分组
flatData.forEach((item) => {
const itemTime = new Date(item.createdAt).getTime()
// 查找第一个满足条件的分组(因为配置是按时间倒序的,所以第一个满足的就是最近的范围)
const targetGroup = groups.find((group) => itemTime >= group.minTime)
if (targetGroup) {
targetGroup.items.push(item)
}
})
// 过滤空分组并对组内数据按时间倒序排序
return groups
.filter((group) => group.items.length > 0)
.map(({ label, items }) => ({
group: label,
items: items.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
}))
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
新增的接口,建议补充接口返回的数据类型
| */ | ||
| export const fetchAssets = async () => { | ||
| try { | ||
| const res = (await apiService.getResourceList('1')) || [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里为什么写死?
| setSaved(false) | ||
| } | ||
|
|
||
| const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
应该是 isFinal ?
| updateConfig({ apiUrl: '/app-center/api/chat/completions' }) | ||
| messages.at(-1).renderContent.at(-1).status = 'fix' | ||
| const fixedResponse = await client.chat({ | ||
| messages: [{ role: 'user', content: getJsonFixPrompt(content, jsonValidResult.error) }], | ||
| options: { signal: abortControllerMap.errorFix?.signal, beforeRequest: beforeRequest as any } | ||
| }) | ||
| if (!isValidJsonPatchObjectString(fixedResponse.choices[0].message.content).isError) { | ||
| lastMessage.originContent = lastMessage.content | ||
| lastMessage.content = fixedResponse.choices[0].message.content | ||
| } | ||
| } catch (error) { | ||
| logger.error('json fix failed', error) | ||
| } | ||
| updateConfig({ apiUrl: getApiUrl() }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里为什么 207 行使用 updateConfig({ apiUrl: '/app-center/api/chat/completions' }),然后 220 行又 updateConfig({ apiUrl: getApiUrl() }) 呢?

English | 简体中文
PR
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
Background and solution
TinyEngine AI功能增强
部署环境:https://hexqi.github.io/tiny-engine/?type=app&id=1&tenant=1
Agent模式:
重构:
新增历史列表功能:
新增模型设置功能:
UI/体验优化:
bugfix:
二次开发:
文档:
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.