feat: ai plugin refactor#1657
Conversation
WalkthroughRestructures the robot plugin into a composable dual-mode AI system (Agent & Chat), adds streaming/chat/tool-call pipelines and an OpenAI-compatible provider, introduces model/service configuration, JSON-Patch schema streaming/repair, new UI components, and removes legacy JS utilities and prompts. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as Main / RobotChat
participant ChatFlow as useChat
participant ModeMgr as useMode
participant ModeImpl as Agent/Chat Mode
participant API as apiService / OpenAICompatibleProvider
participant MCP as MCP / useMcp
User->>UI: Submit message / upload image / click prompt
UI->>ChatFlow: sendUserMessage(payload)
ChatFlow->>ModeMgr: getCurrentMode()
ModeMgr-->>ChatFlow: ModeHooks instance
ChatFlow->>ModeImpl: onBeforeRequest(requestParams)
ModeImpl->>API: chatStream(requestData)
API-->>ModeImpl: stream chunks (content, tool_calls)
ModeImpl->>ChatFlow: onStreamData(chunk)
ChatFlow->>UI: update renderContent / messages
alt tool_calls present
ModeImpl->>MCP: callTools(tool_calls)
MCP-->>ModeImpl: tool results
ModeImpl->>API: stream tool-derived content (chatStream)
API-->>ModeImpl: tool stream chunks
ModeImpl->>ChatFlow: onStreamTools / onPostCallTools
ChatFlow->>UI: render tool results
end
ModeImpl->>ChatFlow: onRequestEnd(finishReason)
ChatFlow->>UI: finalize message state
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.
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.
There was a problem hiding this comment.
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.
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.
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)
c03b8bf to
299a48c
Compare
There was a problem hiding this comment.
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.
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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.
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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).
packages/plugins/robot/src/constants/prompts/data/components.json
Outdated
Show resolved
Hide resolved
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts
Outdated
Show resolved
Hide resolved
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts
Outdated
Show resolved
Hide resolved
packages/plugins/robot/src/components/header-extension/History.vue
Outdated
Show resolved
Hide resolved
已支持 |
Already supported |
There was a problem hiding this comment.
Actionable comments posted: 12
♻️ Duplicate comments (13)
packages/plugins/robot/src/components/chat/FooterButton.vue (1)
44-61: Replace hardcoded colors with CSS variables for theme support.Multiple hardcoded color values prevent dark mode support and theming. This was flagged in previous reviews but remains unresolved.
Hardcoded colors to replace:
- Line 44:
rgb(194, 194, 194)(border)- Line 50:
rgba(0, 0, 0, 0.08)(hover background)- Lines 54-56, 59:
rgb(20, 118, 255)variations (active state)Apply this diff to use CSS variables:
width: fit-content; border-radius: 32px; height: 32px; padding: 0px 8px; - border: 1px solid rgb(194, 194, 194); + border: 1px solid var(--te-base-border-color); cursor: pointer; box-sizing: border-box; background-color: var(--te-base-gray-10); &:hover { - background-color: rgba(0, 0, 0, 0.08); + background-color: var(--te-base-hover-bg); } &.active { - border: 1px solid rgb(20, 118, 255); - background: rgba(20, 118, 255, 0.08); - color: rgb(20, 118, 255); + border: 1px solid var(--te-base-primary-color); + background: var(--te-base-primary-bg-light); + color: var(--te-base-primary-color); &:hover { - background: rgba(20, 118, 255, 0.12); + background: var(--te-base-primary-bg-lighter); } }packages/plugins/robot/src/components/header-extension/History.vue (1)
2-2: Inline style remains on wrapper element.A past review requested avoiding inline styles. This wrapper span still uses
style="display: inline-flex; line-height: 0; position: relative". Consider moving these styles to the scoped CSS block.<template> - <span style="display: inline-flex; line-height: 0; position: relative"> + <span class="history-trigger">Add to scoped styles:
.history-trigger { display: inline-flex; line-height: 0; position: relative; }packages/plugins/robot/src/composables/modes/useMode.ts (1)
49-52: Non-null assertion onconfigmay throw at runtime.This was flagged in a previous review. If
getSelectedModelInfo().configisundefined, line 51 will throw. The suggested fix was to use optional chaining with a fallback.const getCurrentMode = (): ModeHooks => { const { getSelectedModelInfo } = useModelConfig() - return getModeInstance(getSelectedModelInfo().config!.chatMode ?? ChatMode.Agent) + const modelInfo = getSelectedModelInfo() + const chatMode = modelInfo.config?.chatMode ?? ChatMode.Agent + return getModeInstance(chatMode) }packages/plugins/robot/src/composables/modes/useAgentMode.ts (1)
28-48: Non-null assertions ontool.functionmay cause runtime errors.Similar to
useChatMode.ts, lines 35, 39, and 42 use non-null assertions (tool.function!) when accessing properties. If malformed tool calls arrive, these will throw.Apply the same fix as suggested for
useChatMode.ts:- currentToolCallContent.content.params = tool.parsedArgs || tool.function!.arguments || {} + currentToolCallContent.content.params = tool.parsedArgs || tool.function?.arguments || {}And:
- name: tool.name || tool.function!.name, + name: tool.name || tool.function?.name || 'unknown',packages/plugins/robot/src/utils/schema.utils.ts (1)
139-146: Typo:isFinialshould beisFinal.The parameter name at line 139 and its usage at line 142 have the same spelling error that was corrected in
pageUpdater.ts. This affects searchability and consistency across the codebase.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 the call site in
pageUpdater.tsline 45 to match:- const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinal) + const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinal)packages/plugins/robot/src/composables/useChat.ts (2)
89-108: Missing null check forlastMessage.Line 96 retrieves
lastMessagewith.at(-1)which returnsundefinedif themessagesarray is empty. Line 99 then accesseslastMessage.contentwithout checking iflastMessageexists, which will throw aTypeError.Add a guard:
const handleFinishRequest = async ( finishReason: string, messages: ChatMessage[], contextMessages: ChatMessage[], messageState: MessageState ) => { chatStatus = CHAT_STATUS.PROCESSING const lastMessage = messages.at(-1) + if (!lastMessage) { + console.warn('handleFinishRequest called with empty messages array') + return + } delete abortControllerMap.main await onRequestEnd(finishReason, lastMessage.content, messages)
110-115: Missing null check in error handler.Similar issue: line 113 accesses
messages.at(-1).contentwithout checking if the array is empty.Apply the same fix:
const handleRequestError = async (error: Error, messages: ChatMessage[], messageState: MessageState) => { chatStatus = CHAT_STATUS.FINISHED delete abortControllerMap.main - 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 }packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (3)
161-221:createFetchAdapterignores transformed request body from axios config.The adapter at line 162 uses the
datafrom its closure (from the outer scope whencreateFetchAdapteris called), but axios may have transformed the request body via interceptors or transforms. Line 173 castsconfig.data as string, but this ignores any transformations axios applied and assumes the data is always a string.This breaks axios interceptors and any custom request transformations. Consider reading from
config.datainstead:private createFetchAdapter(isStream = false) { return async (config: AxiosRequestConfig) => { + const requestBody = typeof config.data === 'string' + ? config.data + : JSON.stringify(config.data) + // 构建完整URL let url = config.url if (!url.startsWith('http') && config.baseURL) { url = new URL(url, config.baseURL).href } try { const fetchResponse = await fetch(url, { method: config.method.toUpperCase(), headers: config.headers, - body: config.data as string, + body: requestBody, signal: config.signal })
297-323: Response extraction treats falsy values as missing data.Line 313 uses
(response as { data: ChatCompletionResponse }).data || response, which will incorrectly return the entire response object ifdatais any falsy value (null,0,'',false). If the API returns a valid but falsy response, this breaks.Use explicit undefined/null checks:
- return (response as { data: ChatCompletionResponse }).data || response + const axiosResponse = response as { data?: ChatCompletionResponse } + return axiosResponse.data !== undefined ? axiosResponse.data : (response as ChatCompletionResponse)
385-395: Silent fallback from axios to fetch may confuse users.When
httpClientType: 'axios'is provided withoutaxiosClient, the code at line 389 silently falls back to'fetch'rather than throwing an error. The validation at lines 393-395 only catches cases wherethis.httpClientTypewas already'axios'from a previous call, missing the initial misconfiguration.Fail explicitly when axios is requested without a client:
if (httpClientType === 'axios' && axiosClient) { this.httpClientType = 'axios' this.axiosClient = axiosClient - } else if (httpClientType) { + } else if (httpClientType === 'axios' && !axiosClient) { + throw new Error('axiosClient is required when httpClientType is axios') + } else if (httpClientType === 'fetch') { this.httpClientType = 'fetch' } - - // 验证配置 - if (this.httpClientType === 'axios' && !this.axiosClient) { - throw new Error('axiosClient is required when httpClientType is axios') - }packages/plugins/robot/src/composables/core/useConfig.ts (3)
362-371:updateServicenow avoids wiping API keys; logic looks good.The new pattern only re-encrypts the key when
apiKeyis explicitly present inupdates, preserving the existing key otherwise. This resolves the earlier concern about accidentally clearing API keys.
385-415:getSelectedModelInfonow safely handles missing service/model.Using
currentService ?? ({} as Partial<ModelService>)avoids the previous non-null assertion issue while still returning a stableSelectedModelInfoshape. This aligns with prior review feedback.
417-441:getSelectedQuickModelInfomirrorsgetSelectedModelInfosafety improvements.Same pattern here: guarded destructuring and null service when not found makes the consumer side much more robust without throwing.
🧹 Nitpick comments (32)
packages/plugins/robot/src/components/chat/FooterButton.vue (1)
78-113: Remove unused CSS classes.The
.plugin-commonand.plugin-activeclasses are not referenced in the template or mentioned in the component's usage context.Apply this diff to remove dead code:
.button { display: flex; align-items: center; justify-content: center; gap: 4px; } - - .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; - } - } }packages/plugins/robot/src/components/chat/RobotChat.vue (4)
87-108: Strengthen type safety for function props.The
promptClickHandler,beforeSubmit, andstatusprops lack precise type constraints, which could lead to runtime errors if consumers pass incompatible values.Apply this diff to add type safety:
+import type { ChatStatus } from '@opentiny/tiny-robot-kit' + const { promptItems, status, promptClickHandler, allowFiles, bubbleRenderers, beforeSubmit } = defineProps({ promptItems: { type: Array as PropType<PromptProps[]>, default: () => [] }, promptClickHandler: { - type: Function + type: Function as PropType<(item: PromptProps) => void> }, - status: { type: String }, + status: { + type: String as PropType<ChatStatus>, + required: true + }, allowFiles: { type: Boolean, default: false }, bubbleRenderers: { type: Object as PropType<Record<string, Component>>, default: () => ({}) }, beforeSubmit: { - type: Function, + type: Function as PropType<(content: string) => boolean | Promise<boolean>>, default: () => true } })
276-282: Add guard for undefined description in prompt click fallback.Line 280 passes
item.descriptionwhich may beundefined. WhilehandleSendMessagevalidates empty content (Lines 219-221), it's clearer to check this explicitly before calling.Apply this diff:
const handlePromptItemClick = (ev: unknown, item: { description?: string }) => { if (promptClickHandler && typeof promptClickHandler === 'function') { promptClickHandler(item) } else { - handleSendMessage(item.description) + if (item.description) { + handleSendMessage(item.description) + } } }
190-195: Clarify renderer override behavior.The spread operator on Line 194 allows
bubbleRenderersprop to override built-in renderers (markdown,loading,img). While this may be intentional for customization, it could lead to unexpected behavior if consumers accidentally use the same keys.Consider either:
- Documenting this override behavior in component docs/comments, or
- Reversing the spread order to prioritize built-ins:
const contentRenderers = computed(() => ({ + ...bubbleRenderers, markdown: MarkdownRenderer, loading: LoadingRenderer, - img: ImgRenderer, - ...bubbleRenderers + img: ImgRenderer }))Choose based on whether you want consumers to be able to override built-in renderers.
285-483: Consider reducing reliance on deep selectors.The styles contain many
:deep()selectors (Lines 296, 311, 323, 326, 331, 335, 373, 379, 396, 442, 449, 454, 463, 467, 471) which tightly couple this component to the internal DOM structure of child components. While functional, this makes the code fragile if child component internals change.Consider working with child component maintainers to expose CSS custom properties or explicit style props instead of piercing encapsulation.
packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (3)
217-226: Potential runtime error when splitting empty string.When
state.modelSelection.defaultModelis empty (e.g., initial state or cleared),split('::')returns[''], causinggetServiceById('')to be called. While this likely returnsnullsafely, it's worth defensive handling.const selectedDefaultModelInfo = computed(() => { - const [serviceId] = state.modelSelection.defaultModel.split('::') + if (!state.modelSelection.defaultModel) return null + const [serviceId] = state.modelSelection.defaultModel.split('::') const service = getServiceById(serviceId) if (!service) return null
282-290: Type assertion bypasses type safety.The
as anycast on line 288 hides potential type mismatches betweenPartial<ModelService>and the expected parameter type foraddCustomService.Consider defining a proper type for new service creation (e.g.,
NewModelService) that matches the required fields foraddCustomService, rather than usingas any.
273-276: Consider usingstructuredClonefor deep cloning.
JSON.parse(JSON.stringify(...))works but loses undefined values, Date objects, and other non-JSON-serializable types. If theModelServicetype contains such values, this could cause subtle bugs.const editService = (service: ModelService) => { - state.editingService = JSON.parse(JSON.stringify(service)) + state.editingService = structuredClone(service) state.showServiceDialog = true }packages/plugins/robot/src/components/header-extension/History.vue (1)
79-79: Ensure type-safe date comparison in sort.Direct subtraction
b.createdAt - a.createdAtassumescreatedAtis a numeric timestamp. IfcreatedAtcould be aDateobject or ISO string, this would yieldNaN.- items: group.items.sort((a, b) => b.createdAt - a.createdAt) + items: group.items.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())packages/plugins/robot/src/Main.vue (1)
267-272: Consider documenting or extracting magic timeout values.The
setTimeoutwith 1000ms delay is used without explanation. If this is to avoid layout thrashing or race conditions, consider adding a comment or extracting to a named constant.+// Delay teleport mounting to avoid initial layout thrashing +const TELEPORT_MOUNT_DELAY = 1000 + onMounted(async () => { initChatClient() setTimeout(() => { showTeleport.value = true - }, 1000) + }, TELEPORT_MOUNT_DELAY) })packages/plugins/robot/src/services/agentServices.ts (2)
29-34:returninsideforEachonly skips current item, not the entire iteration.The intent seems to be stopping when the limit is reached, but
returninsideforEachonly skips to the next item. All remaining items will still be processed (though not appended).Use
for...ofwithbreak, orreduce, orsometo stop early:- 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-61: Type safety improvement for resource processing.The chain uses
anytypes extensively. Consider defining proper interfaces for the API response structure to improve maintainability and catch errors at compile time.interface ResourceGroup { resources: Array<{ resourceUrl: string description?: string }> } export const fetchAssets = async (appId: string): Promise<{ url: string; describe: string }[]> => { try { const res = (await apiService.getResourceList(appId)) as ResourceGroup[] || [] return res .flatMap((group) => group.resources) .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/composables/modes/useMode.ts (1)
29-30: Module-level cache may cause issues with HMR or testing.
modeInstanceCacheis a module-level object that persists across component instances. This could cause stale state during hot module replacement or make unit testing difficult. Consider providing a cache-clearing mechanism or making the cache instance-scoped if needed.// Add a function to clear cache for testing/HMR scenarios export const clearModeCache = () => { Object.keys(modeInstanceCache).forEach(key => delete modeInstanceCache[key]) }packages/plugins/robot/src/services/api.ts (5)
19-55: Consider typingchatCompletions/agentChatresponses explicitly.Right now both methods rely on the HTTP client’s implicit return type. Defining concrete response types (or at least
Promise<unknown>/Promise<any>) will make the public surface clearer and help catch mismatches if the backend shape changes.
39-46: AlignagentChatwithchatCompletionsoptions behavior.
chatCompletionssupports overridingurland merging headers fromoptions, butagentChathardcodes the URL and only merges headers. If callers may need to customize the endpoint (e.g., proxying / multi-env), it’s worth adding optionaloptions.urlhandling here for symmetry.
57-75: Export resource-related types if they are part of the public surface.
Resource,ResourceGroup, andResourceGroupListare currently file-local. If other modules consumeresourceApiand rely on these shapes, exporting them will avoid duplicated type definitions and keep contracts in sync with the backend.
112-119: NarrowgetHttpClientreturn type if possible.Currently the return type is inferred from
getMetaApi(META_SERVICE.Http)?.getHttp(), which is effectivelyanyto most consumers. If the HTTP client has a stable interface (axios-like, fetch-like), consider declaring a small interface and using it as the return type for better IntelliSense and safer usage.
121-124: Clarify error contract forencryptKey.
encryptKeyis assumed to always resolve to{ token: string }. Downstream code (e.g.,encryptServiceApiKey) should be robust to HTTP failures / unexpected payloads; if the HTTP layer can throw or return non-2xx, you may want to document that here or wrap with a safer helper.packages/plugins/robot/src/composables/core/useConfig.ts (14)
39-45: TightengetAIModelOptionstyping to reduceanydownstream.
getAIModelOptionscurrently returnsany[](inferred frommergeAIModelOptions(DEFAULT_LLM_MODELS, customAIModels)). SinceDEFAULT_LLM_MODELShas a known provider/model shape, consider adding an explicit return type (e.g.,ProviderConfig[]) so code using this helper doesn’t lose type safety when accessingprovider,models, etc.
48-59: AvoidanyininitBuiltInServicesmapping if possible.
service: anyis only used to access known fields (provider,label,baseUrl,allowEmptyApiKey,models). If you introduce a sharedProviderConfigtype for DEFAULT_LLM_MODELS/custom providers, you can replaceanyhere and let TS catch config mistakes at compile time.
62-80: Guard against emptybuiltInServicesedge case.
initDefaultSettingsassumesbuiltInServices[0]exists. In case DEFAULT_LLM_MODELS or merged options are empty/misconfigured, this will still behave (falling back to empty IDs) but the resulting settings won’t have any usable model. Consider explicitly handling the “no services” case (e.g., returning services: [], defaultModel/quickModel with empty IDs) and logging for easier debugging.
84-148: Consider makingmigrateOldSettingsid generation more stable.Using
Date.now()forcustom_${Date.now()}works but makes tests / snapshots harder to reason about. If old settings already imply a unique key (e.g., baseUrl + model), deriving IDs from those values or a small hash would be more deterministic while still unique enough for this context.
150-169:loadRobotSettingStatesilently discards invalid JSON; consider minimal logging.On JSON parse errors the function just returns
null. Adding a smallconsole.warn(or shared logger) would help diagnose corrupted localStorage without impacting UX.
171-178:saveRobotSettingStatereads from storage again; consider merging in-memory state instead.
saveRobotSettingStatecallsloadRobotSettingState()andinitDefaultSettings()to buildcurrentState, even thoughrobotSettingStateis already up-to-date whenupdateStateis true. This can introduce subtle divergence if a caller mutatesrobotSettingStatedirectly. UsingrobotSettingStateas the base source of truth (and only falling back to defaults when it’s uninitialized) would simplify the mental model and avoid extra JSON parse.
194-258:mergeAIModelOptionsworks but is heavy onany; a shared config type would help.The deep-merge logic and
_removehandling look correct, but pervasiveany(providers, models) weakens static guarantees and makes refactors risky. Introducing explicit interfaces (e.g.,CompatibleProviderConfig,CompatibleModelConfigwith an optional_removeflag) would let TS enforce field names and catch typos in provider/model configs early.
261-285:mergeServicesdrops all non-API fields from cached built-ins; verify that’s desired.For built-in services with a cached entry, you only preserve
apiKeyand overwrite everything else with the latest built-ins (...builtIn, apiKey: cached.apiKey || ''). If future fields onModelServicebecome user-editable per built-in (e.g., custom label), this will discard them on init. If that’s not intended, consider merging fromcachedwhile letting built-in defaults win only for specific fields (e.g., models).
287-295: Confirminitshould always re-save to localStorage on every call.
initcallssaveRobotSettingState(settingState)even when it just loaded a valid state. Ifinitis invoked frequently (e.g., on plugin mount), this will repeatedly write to localStorage. Not a big issue, but you might add a cheap equality check or only save when migration/merge actually changed something.
297-304:getModelCapabilitiesreturnsnullorundefined; consider normalizing.Currently this can return
null(when ids are missing) orundefined(when service/model not found). Normalizing tonull(or a consistent empty object) makes consumer code simpler and avoids double checks.
307-319:getAllAvailableModelsshape might deserve an exported type.The flattened model objects (
serviceId,serviceName,modelName,modelLabel,capabilities,displayLabel,value) are a semi-public contract used by UI. Defining a named interface (e.g.,AvailableModel) will help keep UI and core consistent as fields evolve.
326-334: Side-effectful setters look good; optional: debounce persistence.
updateThinkingState/updateChatModeStateboth immediately write to localStorage. If these toggles can change rapidly (e.g., user dragging a slider or rapidly switching modes), consider debouncing saves to avoid excessive writes; otherwise this is fine.
336-347:encryptServiceApiKeybehavior is reasonable; consider surfacing failure to callers.On failure it logs and returns the raw
apiKey. For security-sensitive scenarios, callers might want to know encryption failed (e.g., to show a warning badge). Returning a tuple or throwing on failure would be more explicit; alternatively, document that “failure falls back to cleartext” so consumers can choose whether to accept it.
349-360:addCustomServiceID generation and encryption look correct.The function correctly encrypts
service.apiKey, marks the service as non-built-in, and persists state once. Only optional improvement would be the same determinism comment aboutcustom_${Date.now()}as in migration, if snapshot tests ever need stable IDs.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
packages/plugins/robot/src/Main.vue(2 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/header-extension/History.vue(1 hunks)packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue(1 hunks)packages/plugins/robot/src/components/renderers/AgentRenderer.vue(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/useMessageStream.ts(1 hunks)packages/plugins/robot/src/composables/features/useMcp.ts(2 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/prompts/data/examples.json(1 hunks)packages/plugins/robot/src/constants/prompts/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/utils/schema.utils.ts(1 hunks)tsconfig.app.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- packages/plugins/robot/src/constants/prompts/data/examples.json
- tsconfig.app.json
- packages/plugins/robot/src/components/renderers/AgentRenderer.vue
- packages/plugins/robot/src/composables/features/useMcp.ts
- packages/plugins/robot/src/constants/prompts/index.ts
🧰 Additional context used
🧠 Learnings (5)
📚 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-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.vuepackages/plugins/robot/src/components/chat/RobotChat.vue
📚 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
📚 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/robot/src/composables/modes/useChatMode.ts
🧬 Code graph analysis (9)
packages/plugins/robot/src/services/aiClient.ts (1)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)
ProviderConfig(42-47)OpenAICompatibleProvider(49-414)
packages/plugins/robot/src/composables/modes/useMode.ts (4)
packages/plugins/robot/src/types/mode.types.ts (1)
ModeHooks(25-82)packages/plugins/robot/src/composables/modes/useAgentMode.ts (1)
useAgentMode(59-279)packages/plugins/robot/src/composables/modes/useChatMode.ts (1)
useChatMode(53-179)packages/plugins/robot/src/utils/meta.utils.ts (1)
getRobotServiceOptions(4-6)
packages/plugins/robot/src/composables/useChat.ts (8)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
useMode(59-80)packages/plugins/robot/src/composables/core/useMessageStream.ts (1)
createStreamDataHandler(76-106)packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
ChatRequestData(35-40)packages/plugins/robot/src/services/api.ts (1)
apiService(126-131)packages/plugins/robot/src/composables/core/useConversation.ts (1)
useConversationAdapter(22-108)packages/plugins/robot/src/composables/features/useToolCalls.ts (1)
createToolCallHandler(83-132)packages/plugins/robot/src/composables/core/useConfig.ts (1)
robotSettingState(443-468)packages/plugins/robot/src/utils/chat.utils.ts (1)
removeLoading(88-97)
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/agentServices.ts (1)
packages/plugins/robot/src/services/api.ts (1)
apiService(126-131)
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/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/utils/schema.utils.ts (1)
packages/plugins/robot/src/utils/chat.utils.ts (1)
serializeError(18-28)
packages/plugins/robot/src/composables/core/useConfig.ts (4)
packages/plugins/robot/src/types/setting.types.ts (4)
RobotSettings(52-59)ModelService(30-39)SelectedModelInfo(61-76)ModelConfig(16-25)packages/plugins/robot/src/utils/meta.utils.ts (1)
getRobotServiceOptions(4-6)packages/plugins/robot/src/constants/model-config.ts (1)
DEFAULT_LLM_MODELS(11-94)packages/plugins/robot/src/services/api.ts (1)
apiService(126-131)
⏰ 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 (13)
packages/plugins/robot/src/components/chat/FooterButton.vue (1)
116-125: @container queries have widespread browser support—clarify if legacy browser support is a project requirement.@container queries are now baseline across all major browsers (Chrome 105+, Edge 105+, Safari 16+, Firefox 110+) and have been since 2022–2023. This support exceeds the browser matrix already accepted by your project (which supports :has() on Safari 15.4+ and Edge 105+). If your project targets only modern browsers, no action is needed. If legacy browser support is required, consider using a Container Query Polyfill or @supports feature detection.
packages/plugins/robot/src/components/chat/RobotChat.vue (1)
272-274: Previous abort handling issue has been resolved.The abort logic has been simplified to only emit the event, delegating the actual abort handling (including setting the
abortedflag) to the parent component. This eliminates the bounds-check issue that was previously flagged.packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (1)
1-142: Well-structured settings UI with good UX patterns.The template uses appropriate form validation flow, capability tags for model selection, and confirmation dialogs for destructive actions. The tab-based organization and API key status indicators provide clear user feedback.
packages/plugins/robot/src/components/header-extension/History.vue (1)
50-81: Clean refactored grouping logic.The configuration-driven approach with
groupConfigsis much cleaner than the previous if-else chain. The implementation correctly handles threshold-based grouping and filters empty groups.packages/plugins/robot/src/Main.vue (2)
256-264: Good error feedback implementation.The file upload error handling now includes user notification via
useNotify, addressing the previous review concern about silent failures.
199-215: API key validation flow is well-implemented.The
checkApiKeyfunction properly validates bothbaseUrlandapiKeypresence, respectsallowEmptyApiKeyconfiguration, provides user notification, and auto-opens settings panel. This is a good UX pattern.packages/plugins/robot/src/composables/modes/useMode.ts (1)
59-79: Clean proxy pattern for runtime mode switching.The delegation pattern allows seamless mode switching at runtime without requiring consumers to track mode changes. Each method call dynamically resolves to the current mode's implementation.
packages/plugins/robot/src/composables/core/pageUpdater.ts (1)
73-73: LGTM!The throttled wrapper with a 200ms interval is appropriate for rate-limiting streaming schema updates, preventing excessive DOM updates while maintaining responsiveness.
packages/plugins/robot/src/composables/useChat.ts (1)
75-87: LGTM! Well-structured initialization.The
initChatClientfunction properly initializes the provider configuration with all necessary parameters and uses a factory-provided axios client. The conditionalservice?.apiKeyhandles the case where no service is configured.packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
141-155: LGTM! Clean request preparation with proper hooks.The
prepareRequestDatamethod correctly formats messages, merges model selection from multiple sources, and applies thebeforeRequesthook at the appropriate time. The deep clone viaJSON.parse(JSON.stringify(...))ensures no mutation of the original request.packages/plugins/robot/src/services/api.ts (1)
84-90: Verify backend really returns a fullResourceon upload.
uploadFileis typed asPromise<Resource>, but many upload endpoints return only IDs or minimal metadata. If the actual API returns a different shape (e.g.,{ id: string }), this typing could be misleading and leak through to consumers.packages/plugins/robot/src/composables/core/useConfig.ts (2)
373-379:deleteServicecorrectly protects built-ins.Guarding on
!isBuiltInbefore deletion is a good safety measure; behavior is clear and consistent with expectations for built-in providers.
443-468: Default export surface is cohesive and clear.Exposing read-only state plus focused utilities (init is separate) gives consumers a clean API for reading config and managing services/models. No changes needed here.
3c329b0 to
53501ed
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (13)
packages/plugins/robot/src/constants/prompts/data/examples.json (1)
75-75: Component ID still violates specification requirements.The ID
"f2525253"contains only lowercase letters and digits. Per the specification requirement mentioned in the previous review, component IDs must contain at least one uppercase letter, one lowercase letter, and one digit.Replace with a compliant ID:
- "id": "f2525253", + "id": "fL2m5x3R",Ensure any references to this ID elsewhere in the file are also updated.
packages/plugins/robot/src/utils/schema.utils.ts (1)
69-82: Missing circular reference protection inschemaAutoFix.The recursive function traverses
data.childrenwithout tracking visited nodes. If the schema contains circular references, this will cause infinite recursion and stack overflow.This was flagged in a previous review. Consider adding a
WeakSetto track visited nodes.packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)
385-395: Silent fallback from axios to fetch whenaxiosClientis missing.When
httpClientType: 'axios'is passed withoutaxiosClient, line 388-390 silently setshttpClientTypeto'fetch'. The validation at lines 392-395 never triggers becausethis.httpClientTypewas already changed to'fetch'.This was flagged in a previous review. Consider failing explicitly when axios is requested but client is not provided.
306-318: Response extraction may fail for falsy but valid responses.Line 313 uses
|| responsewhich would return the entire axios response object ifdatais a valid but falsy value. While unlikely for chat completions, consider using nullish coalescing for correctness.This was noted in a previous review. Consider using
?? responseinstead of|| response.packages/plugins/robot/src/composables/modes/useMode.ts (1)
49-52: Non-null assertion onconfigmay throw at runtime.Line 51 uses
config!.chatModewhich assumesconfigis always defined. IfgetSelectedModelInfo()returns an object withundefinedconfig, this will cause a runtime error.Apply this diff to add a fallback:
const getCurrentMode = (): ModeHooks => { const { getSelectedModelInfo } = useModelConfig() - return getModeInstance(getSelectedModelInfo().config!.chatMode ?? ChatMode.Agent) + const modelInfo = getSelectedModelInfo() + const chatMode = modelInfo.config?.chatMode ?? ChatMode.Agent + return getModeInstance(chatMode) }packages/plugins/robot/src/composables/modes/useChatMode.ts (2)
19-43: Non-null assertions ontool.functionmay cause runtime errors.Lines 26, 33, 36 use non-null assertions (
tool.function!) when accessingtool.function.nameandtool.function.arguments. If malformed tool calls arrive from the LLM, these assertions will throw runtime errors.Apply optional chaining with fallbacks:
- currentToolCallContent.content.params = tool.parsedArgs || tool.function!.arguments || {} + currentToolCallContent.content.params = tool.parsedArgs || tool.function?.arguments || {} ... - name: tool.name || tool.function!.name, + name: tool.name || tool.function?.name || 'unknown', ... - params: tool.parsedArgs || tool.function!.arguments || {}, + params: tool.parsedArgs || tool.function?.arguments || {},
81-103: Missing error handling for asyncgetLLMTools()call.Line 82 awaits
useMcpServer().getLLMTools()without a try-catch block. If the MCP server is unavailable, this will throw an unhandled exception that aborts request preparation.const onBeforeRequest = async (requestParams: any) => { - const tools = await useMcpServer().getLLMTools() + let tools + try { + tools = await useMcpServer().getLLMTools() + } catch (error) { + console.warn('Failed to load MCP tools:', error) + tools = [] + } const { model, baseUrl, config, capabilities } = getSelectedModelInfo()packages/plugins/robot/src/composables/modes/useAgentMode.ts (2)
139-141: Non-null assertion onpageSchemamay cause runtime error.Line 140 uses
pageSchema!assuming it's always defined during streaming. However, ifonStreamDatais called beforeonMessageSent(which initializespageSchemaat line 89), this will throw.const onStreamData = (data: object, content: string | object, _messages: any[]) => { + if (!pageSchema) { + console.warn('pageSchema not initialized, skipping stream update') + return + } - updatePageSchema(content, pageSchema!) + updatePageSchema(content, pageSchema) }
239-246: Awaiting throttled function may returnundefined.
updatePageSchemais created usinguseThrottleFnwhich typically doesn't preserve the return value. Theawaitat line 240 expects a result with.schemaproperty, but the throttled wrapper may returnvoid/undefined.Verify whether
updatePageSchemareturns a Promise with the expected shape or consider using the non-throttled version for final updates:#!/bin/bash # Check the implementation of updatePageSchema in pageUpdater.ts ast-grep --pattern $'export const updatePageSchema = $_' rg -n "useThrottleFn" --type=ts -A 3 packages/plugins/robot/src/composables/core/pageUpdater.tspackages/plugins/robot/src/Main.vue (1)
172-177: Potential null reference when aborting.The non-null assertion on line 175 (
messages.value.at(-1)!) could throw if the check on line 174 passes but the array is modified concurrently (unlikely but possible). Using a local variable is safer.const handleAbortRequest = () => { abortRequest() - if (messages.value.at(-1)) { - messages.value.at(-1)!.aborted = true - } + const lastMessage = messages.value.at(-1) + if (lastMessage) { + lastMessage.aborted = true + } }packages/plugins/robot/src/components/chat/RobotChat.vue (1)
128-170: Add bounds checks to prevent runtime errors.The retry path (Line 131) and the
updateAttachmentcallback (Lines 158, 163) accessselectedAttachments.value[0]without verifying the array has elements. If the user removes the attachment before retry completes or before the upload callback fires, this will throw a runtime error.Apply this diff:
const handleSingleFilesSelected = (files: File[] | null, retry = false) => { if (!files?.length) return if (retry) { + if (selectedAttachments.value.length === 0) { + useNotify({ + type: 'error', + message: '附件已被移除,无法重试' + }) + return + } Object.assign(selectedAttachments.value[0], { status: 'uploading' }) } else { // ... existing code } // ... existing upload code const updateAttachment = (resourceUrl: string) => { + if (selectedAttachments.value.length === 0) return + if (resourceUrl) { Object.assign(selectedAttachments.value[0], { status: 'success', url: resourceUrl }) } else { Object.assign(selectedAttachments.value[0], { status: 'error' }) } } emit('fileSelected', formData, updateAttachment) }packages/plugins/robot/src/composables/useChat.ts (2)
110-115: Add null check for last message in error handler.Line 113 accesses
messages.at(-1).contentwithout verifying the array has elements. If an error occurs with an empty messages array, this will throw a runtime error.Apply this diff:
const handleRequestError = async (error: Error, messages: ChatMessage[], messageState: MessageState) => { chatStatus = CHAT_STATUS.FINISHED delete abortControllerMap.main - 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 }
96-99: Add null check forlastMessagebefore accessing properties.
messages.at(-1)at Line 96 can returnundefinedif the messages array is empty. Line 99 accesseslastMessage.contentwithout a guard, which would throw a runtime error.Apply this diff to add a null check:
const handleFinishRequest = async ( finishReason: string, messages: ChatMessage[], contextMessages: ChatMessage[], messageState: MessageState ) => { chatStatus = CHAT_STATUS.PROCESSING const lastMessage = messages.at(-1) + if (!lastMessage) { + chatStatus = CHAT_STATUS.FINISHED + delete abortControllerMap.main + return + } delete abortControllerMap.main await onRequestEnd(finishReason, lastMessage.content, messages) // 本次请求结束
🧹 Nitpick comments (12)
packages/plugins/robot/src/constants/prompts/index.ts (2)
45-51: Function name doesn't reflect conditional behavior.The function name
toPascalCaseimplies it always converts strings to PascalCase, but it only transforms strings starting with "tiny" and returns others unchanged. Consider renaming to reflect this conditional behavior.Apply this diff:
-const toPascalCase = (str: string): string => { +const convertTinyComponentName = (str: string): string => { if (!str.toLowerCase().startsWith('tiny')) return strThen update the two usages at lines 93 and 138.
83-83: TODO: Address the TinyNumeric component error.The Chinese comment indicates this component causes an error and is temporarily ignored. This technical debt should be tracked and resolved.
Would you like me to open a new issue to track resolving the TinyNumeric component error?
packages/plugins/robot/src/composables/features/useMcp.ts (1)
110-124: Redundant MCP client call ingetLLMTools.
getLLMToolsdirectly callsgetMcpClient()?.listTools()instead of reusing thelistToolsfunction defined on lines 112-115. This duplicates logic and could lead to inconsistencies iflistToolsis modified.const getLLMTools = async () => { - const mcpTools = await getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.listTools() - llmTools = convertMCPToOpenAITools(mcpTools?.tools || []) + const mcpTools = await listTools() + llmTools = convertMCPToOpenAITools(mcpTools) return llmTools }packages/plugins/robot/src/utils/schema.utils.ts (1)
180-189: Inconsistent error handling inparseAndRepairJson.When
isFinialistrue, the function skipsjsonrepairand directly parsescontent. If parsing fails, the error object is returned raw in theerrorfield. However, whenisFinialisfalseandjsonrepairorJSON.parsefails, the same pattern is used. Consider usingserializeErrorfor consistency withisValidJsonPatchObjectString.export const parseAndRepairJson = (content: string, isFinial: boolean) => { try { let repairedContent = content if (!isFinial) { repairedContent = jsonrepair(content) } return { data: JSON.parse(repairedContent), isError: false } } catch (error) { - return { isError: true, error } + return { isError: true, error: serializeError(error) } } }packages/plugins/robot/src/composables/core/useConfig.ts (1)
171-178: Potential re-entrancy during migration insaveRobotSettingState.When
loadRobotSettingStatetriggers migration (line 161), it recursively callssaveRobotSettingState. Then on line 175,saveRobotSettingStatecallsloadRobotSettingStateagain. While this doesn't cause infinite recursion (migration only happens once), the nested read-write pattern is confusing.Consider extracting the localStorage write to a private helper to make the flow clearer:
+const writeToStorage = (state: RobotSettings) => { + localStorage.setItem(SETTING_STORAGE_KEY, JSON.stringify(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 } - localStorage.setItem(SETTING_STORAGE_KEY, JSON.stringify(newState)) + writeToStorage(newState) }packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (1)
282-290: Type assertionas anybypasses validation.The
addCustomService(serviceData as any)on line 288 bypasses TypeScript's type checking. Consider properly typing theServiceEditDialogoutput to match the expectedOmit<ModelService, 'id' | 'isBuiltIn'>signature.packages/plugins/robot/src/services/api.ts (1)
25-32: Consider adding explicit return type for API methods.The
chatCompletionsandagentChatmethods return whatevergetMetaApi(META_SERVICE.Http).post()returns, but the type is implicit. For better type safety and API documentation, consider adding explicit return types.- chatCompletions: (body: LLMRequestBody, options: RequestOptions = {}) => { + chatCompletions: (body: LLMRequestBody, options: RequestOptions = {}): Promise<any> => { return getMetaApi(META_SERVICE.Http).post(options?.url || '/app-center/api/chat/completions', body, {packages/plugins/robot/src/composables/modes/useMode.ts (1)
59-80: Consider using a JavaScript Proxy for cleaner delegation.The manual delegation of all methods works but requires maintenance when
ModeHooksinterface changes. AProxywould auto-forward all method calls.export default function useMode(): ModeHooks { return new Proxy({} as ModeHooks, { get(_target, prop) { const mode = getCurrentMode() const value = mode[prop as keyof ModeHooks] return typeof value === 'function' ? value.bind(mode) : value } }) }packages/plugins/robot/src/Main.vue (1)
123-144: Consider extracting prompt items to a separate configuration.The
promptItemsarray is hardcoded in the component. For better maintainability and potential i18n support, consider extracting this to a configuration file or composable.packages/plugins/robot/src/composables/useChat.ts (2)
225-236: Clarify the conversation reuse logic.The condition at Line 229 (
usedConversationId === newConversationId) appears to check if a conversation was reused rather than created. However, the logic is unclear:
createConversationtypically returns a new ID- If IDs match, why would we need to update metadata since it was just passed to
createConversation?Consider adding a comment explaining when this equality condition occurs, or refactor to make the intent explicit (e.g., separate
reuseConversationvscreateNewConversationpaths).
42-44: Consider instance isolation for module-level state.
chatStatusandabortControllerMapare module-level variables shared across all consumers. If multiple chat instances are used simultaneously (e.g., different panels or test scenarios), they will interfere with each other.If this is intentionally a singleton, consider adding a comment documenting this constraint. Otherwise, move these into the returned object or use a factory pattern.
packages/plugins/robot/src/components/chat/RobotChat.vue (1)
111-111: Add type annotation forselectedAttachments.The
selectedAttachmentsref is untyped, which reduces type safety and IDE support. Consider adding a proper type.+interface Attachment { + size?: number + rawFile?: File + status?: 'uploading' | 'success' | 'error' + url?: string +} + -const selectedAttachments = ref([]) +const selectedAttachments = ref<Attachment[]>([])
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (23)
packages/plugins/robot/src/Main.vue(2 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/header-extension/History.vue(1 hunks)packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue(1 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/composables/core/pageUpdater.ts(1 hunks)packages/plugins/robot/src/composables/core/useConfig.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/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/prompts/data/examples.json(1 hunks)packages/plugins/robot/src/constants/prompts/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/utils/schema.utils.ts(1 hunks)tsconfig.app.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
- packages/plugins/robot/src/composables/core/pageUpdater.ts
- tsconfig.app.json
- packages/plugins/robot/src/services/agentServices.ts
- packages/plugins/robot/src/services/aiClient.ts
- packages/plugins/robot/src/components/header-extension/History.vue
- packages/plugins/robot/src/components/renderers/AgentRenderer.vue
- packages/plugins/robot/src/components/chat/FooterButton.vue
- packages/plugins/robot/src/components/renderers/ImgRenderer.vue
- packages/plugins/robot/src/composables/core/useMessageStream.ts
🧰 Additional context used
🧠 Learnings (9)
📚 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.tspackages/plugins/robot/src/composables/modes/useChatMode.tspackages/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
📚 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.vuepackages/plugins/robot/src/components/chat/RobotChat.vue
📚 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/modes/useChatMode.ts
📚 Learning: 2025-01-14T08:44:09.485Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/canvas-function/controller.ts:1-7
Timestamp: 2025-01-14T08:44:09.485Z
Learning: Type safety improvements for the controller in `packages/canvas/render/src/canvas-function/controller.ts` should be deferred until the data structure is finalized.
Applied to files:
packages/plugins/robot/src/composables/modes/useChatMode.ts
📚 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/plugins/robot/src/composables/modes/useChatMode.tspackages/plugins/robot/src/composables/modes/useAgentMode.ts
📚 Learning: 2025-01-14T07:11:58.019Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/accessor-map.ts:13-13
Timestamp: 2025-01-14T07:11:58.019Z
Learning: The user prefers to keep the `Function` type in `packages/canvas/render/src/page-block-function/accessor-map.ts` for now, despite static analysis warnings.
Applied to files:
packages/plugins/robot/src/composables/modes/useChatMode.ts
📚 Learning: 2024-09-25T11:18:00.771Z
Learnt from: chilingling
Repo: opentiny/tiny-engine PR: 817
File: packages/vue-generator/src/plugins/appendElePlusStylePlugin.js:46-50
Timestamp: 2024-09-25T11:18:00.771Z
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/modes/useChatMode.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
🧬 Code graph analysis (7)
packages/plugins/robot/src/composables/modes/useMode.ts (4)
packages/plugins/robot/src/types/mode.types.ts (1)
ModeHooks(25-82)packages/plugins/robot/src/composables/modes/useAgentMode.ts (1)
useAgentMode(59-279)packages/plugins/robot/src/composables/modes/useChatMode.ts (1)
useChatMode(53-179)packages/plugins/robot/src/utils/meta.utils.ts (1)
getRobotServiceOptions(4-6)
packages/plugins/robot/src/constants/prompts/index.ts (1)
packages/canvas/render/src/material-function/material-getter.ts (1)
getComponent(116-118)
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/useChat.ts (7)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
useMode(59-80)packages/plugins/robot/src/composables/core/useMessageStream.ts (1)
createStreamDataHandler(76-106)packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
ChatRequestData(35-40)packages/plugins/robot/src/services/api.ts (1)
apiService(126-131)packages/plugins/robot/src/composables/core/useConversation.ts (1)
useConversationAdapter(22-108)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/services/api.ts (1)
packages/plugins/robot/src/types/chat.types.ts (2)
LLMRequestBody(51-57)RequestOptions(5-10)
packages/plugins/robot/src/composables/features/useMcp.ts (2)
packages/plugins/robot/src/types/chat.types.ts (1)
RequestTool(12-31)packages/plugins/robot/src/types/mcp.types.ts (1)
McpTool(9-25)
packages/plugins/robot/src/composables/core/useConfig.ts (4)
packages/plugins/robot/src/types/setting.types.ts (4)
RobotSettings(52-59)ModelService(30-39)SelectedModelInfo(61-76)ModelConfig(16-25)packages/plugins/robot/src/utils/meta.utils.ts (1)
getRobotServiceOptions(4-6)packages/plugins/robot/src/constants/model-config.ts (1)
DEFAULT_LLM_MODELS(11-94)packages/plugins/robot/src/services/api.ts (1)
apiService(126-131)
⏰ 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 (25)
packages/plugins/robot/src/constants/prompts/index.ts (1)
167-167: Ensure imageAssets items have required properties.The code assumes each item in
imageAssetshasdescribeandurlproperties. If these are missing, the markdown output will containundefinedvalues. Consider adding defensive checks:- .replace('{{IMAGE_ASSETS}}', imageAssets.map((item) => `- `).join('\n')) + .replace('{{IMAGE_ASSETS}}', imageAssets + .filter((item) => item?.describe && item?.url) + .map((item) => `- `) + .join('\n'))packages/plugins/robot/src/composables/features/useMcp.ts (1)
36-51: LGTM!The
convertMCPToOpenAIToolsfunction correctly transforms MCP tools to OpenAI-compatible format with proper null handling via optional chaining.packages/plugins/robot/src/utils/schema.utils.ts (2)
87-122: LGTM with a note on non-standard operation.The validation logic is thorough. Note that
_geton line 88 is not a standard RFC 6902 operation but is used by some libraries likefast-json-patchfor internal purposes. This appears intentional for compatibility.
151-155: LGTM!The regex extraction correctly handles markdown code blocks with optional
jsonorschemalanguage identifiers, with a sensible fallback to the raw content.packages/plugins/robot/src/composables/core/useConfig.ts (3)
362-371: LGTM!The
updateServicefunction now correctly preserves the existing API key whenapiKeyis not included in theupdatesobject, addressing the previous review concern.
350-360: LGTM!The
addCustomServicefunction correctly encrypts the API key before storing and generates a unique ID. TheDate.now()approach is acceptable for client-side IDs in this context.
287-295: LGTM!The
initfunction properly handles both fresh initialization and merging existing cached services with built-in services, preserving user-configured API keys.packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (3)
161-221: LGTM!The fetch adapter correctly uses
config.datafrom the axios config rather than a closed-over value, addressing the previous review concern about axios interceptors being bypassed.
71-115: LGTM!The error handling now uses a status code map for cleaner HTTP error classification, as suggested in a previous review. The keyword-based fallback for network/timeout errors is a reasonable approach given browser variations.
11-23: LGTM!The minimal interface definitions for
AxiosRequestConfigandAxiosInstanceappropriately avoid a hard dependency on axios types while providing sufficient type safety for the provider's needs.packages/plugins/robot/src/services/api.ts (2)
57-74: LGTM! Well-defined resource types.The
Resource,ResourceGroup, andResourceGroupListtypes are properly defined with appropriate fields for the resource management API.
121-124: Verify API key encryption endpoint security.The
encryptKeyfunction sends the raw API key to the server for encryption via the HTTP service. Confirm that:
- The endpoint enforces HTTPS to prevent key exposure during transit
- Proper authentication/authorization is implemented to restrict access to this endpoint
- The HTTP client is configured with secure defaults (certificate validation, etc.)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
24-27: LGTM! Clean registry pattern for mode management.The mode registry pattern allows for easy extension of new modes while keeping the mapping centralized and type-safe.
packages/plugins/robot/src/composables/modes/useChatMode.ts (2)
113-123: LGTM! Error handling for request end properly implemented.The
onRequestEndhandler correctly uses optional chaining forextraData?.errorand properly handles abort/error scenarios with loading removal.
93-100: LGTM! Guard for extraBody assignment properly implemented.The code now properly checks if
extraBodyis truthy before callingObject.assign, preventing potentialTypeErrorwhen the value isnull.packages/plugins/robot/src/composables/modes/useAgentMode.ts (2)
123-130: LGTM! Guard for extraBody assignment properly implemented.The code correctly checks if
extraBodyis truthy before callingObject.assign, addressing the previous review concern about passingnulltoObject.assign.
201-218: JSON fix mechanism is well-designed.The JSON repair workflow properly creates a new AbortController, uses a
beforeRequesthook to configure the fix request, and handles both success and failure cases appropriately. The per-request URL approach via theapiUrloption avoids the global config mutation issue from the previous review.packages/plugins/robot/src/Main.vue (4)
79-101: LGTM! Clean composition API setup.The script setup block properly imports dependencies and defines props with appropriate defaults. The migration from Options API to Composition API is well-executed.
199-215: Well-implemented API key validation with user feedback.The
checkApiKeyfunction properly validates provider settings and provides clear user feedback via notification, followed by opening the settings panel. This is a good UX pattern.
249-265: LGTM! Error handling for file upload properly implemented.The
handleFileSelectedfunction includes proper try-catch with user notification viauseNotify, addressing the previous review concern about silent failures.
267-272: Verify necessity of setTimeout for showTeleport.The 1-second delay before showing teleport content may cause a noticeable UI delay. Consider whether this delay is necessary or if a smaller value or
nextTickwould suffice.packages/plugins/robot/src/composables/useChat.ts (2)
199-214: LGTM!The message sending flow correctly:
- Awaits
nextTick()before proceeding (addressing the previous race condition)- Sets up abort controller before starting
- Adds loading indicator
- Handles errors by removing loading and calling error handler
- Auto-sets title after completion
216-223: LGTM!The abort flow properly cleans up all abort controllers and uses optional chaining for safe access to the last message content.
packages/plugins/robot/src/components/chat/RobotChat.vue (2)
264-266: LGTM!The abort handler correctly delegates to the parent component via emit, following good component composition patterns.
268-274: LGTM!The prompt click handler properly delegates to either a custom handler or the default send mechanism. The undefined
descriptioncase is safely handled byhandleSendMessage's validation.
packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue
Show resolved
Hide resolved
packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
packages/plugins/robot/src/components/chat/RobotChat.vue (1)
128-170: Add bounds checks in retry path and upload callback.Both the retry path (line 131) and the
updateAttachmentcallback (lines 158, 163) accessselectedAttachments.value[0]without verifying the array has elements. If a user removes the attachment before retry completes or before the upload finishes, this will cause a runtime error.Apply this diff to add bounds checks:
const handleSingleFilesSelected = (files: File[] | null, retry = false) => { if (!files?.length) return if (retry) { + if (selectedAttachments.value.length === 0) { + useNotify({ + type: 'error', + message: '附件已被移除,无法重试' + }) + return + } Object.assign(selectedAttachments.value[0], { status: 'uploading' }) } else { if (files.length > 1) { useNotify({ type: 'error', message: '当前仅支持上传一张图片' }) return } // 将选中的文件转换为 Attachment 格式并添加到附件列表 const newAttachments = Array.from(files).map((file) => ({ size: file.size, rawFile: file })) selectedAttachments.value.push(...newAttachments) } // 开始上传 const formData = new FormData() const fileData = files[0] formData.append('file', fileData) const updateAttachment = (resourceUrl: string) => { + if (selectedAttachments.value.length === 0) return + if (resourceUrl) { Object.assign(selectedAttachments.value[0], { status: 'success', url: resourceUrl }) } else { Object.assign(selectedAttachments.value[0], { status: 'error' }) } } emit('fileSelected', formData, updateAttachment) }Based on learnings, use
useNotifyfor error messages rather thanconsole.packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (1)
3-11: Make back/close SVG icons keyboard-accessible and screen-reader friendly.These icons are clickable but not focusable via keyboard and lack ARIA metadata, which blocks keyboard-only and screen-reader users from closing the panel.
Consider updating them like this:
- <div class="header-left"> - <svg-icon v-if="!props.fullscreen" name="back" class="back-icon" @click="handleBack"></svg-icon> - </div> + <div class="header-left"> + <svg-icon + v-if="!props.fullscreen" + name="back" + class="back-icon" + tabindex="0" + role="button" + aria-label="返回" + @click="handleBack" + @keydown.enter="handleBack" + @keydown.space.prevent="handleBack" + ></svg-icon> + </div> @@ - <div class="header-right"> - <svg-icon v-if="props.fullscreen" name="cross" class="close-icon" @click="handleBack"></svg-icon> - </div> + <div class="header-right"> + <svg-icon + v-if="props.fullscreen" + name="cross" + class="close-icon" + tabindex="0" + role="button" + aria-label="关闭" + @click="handleBack" + @keydown.enter="handleBack" + @keydown.space.prevent="handleBack" + ></svg-icon> + </div>
🧹 Nitpick comments (1)
packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (1)
185-187: CentralizeserviceId::modelNameparsing (and nice fix on quick model clearing).The
getModelValue/split('::')pattern is used in several places (default model, quick model, andselectedDefaultModelInfo). The newhandleCompactModelChangecorrectly normalizes cleared selections to empty strings, which avoidsundefinedleaking into persisted state—nice improvement.To reduce duplication and harden parsing (including the default model case), you could introduce a small helper:
const getModelValue = (serviceId: string, modelName: string) => { return serviceId && modelName ? `${serviceId}::${modelName}` : '' } + +const parseModelValue = (value?: string) => { + const [serviceId = '', modelName = ''] = (value || '').split('::') + return { serviceId, modelName } +} @@ -const selectedDefaultModelInfo = computed(() => { - const [serviceId] = state.modelSelection.defaultModel.split('::') +const selectedDefaultModelInfo = computed(() => { + const { serviceId } = parseModelValue(state.modelSelection.defaultModel) const service = getServiceById(serviceId) if (!service) return null @@ const handleModelChange = () => { - const [defaultServiceId, defaultModelName] = state.modelSelection.defaultModel.split('::') + const { serviceId: defaultServiceId, modelName: defaultModelName } = parseModelValue( + state.modelSelection.defaultModel + ) + if (!defaultServiceId || !defaultModelName) { + return + } @@ const handleCompactModelChange = () => { - const [quickServiceId = '', quickModelName = ''] = (state.modelSelection.quickModel || '').split('::') + const { serviceId: quickServiceId, modelName: quickModelName } = parseModelValue( + state.modelSelection.quickModel + )This keeps encoding/decoding logic in one place and makes behavior consistent if the stored value is ever empty or malformed.
Also applies to: 199-206, 217-226, 233-266
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/plugins/robot/src/components/chat/RobotChat.vue(1 hunks)packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 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/components/chat/RobotChat.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 (4)
packages/plugins/robot/src/components/chat/RobotChat.vue (4)
1-66: Template structure is well-organized.The conditional rendering logic correctly shows welcome/prompts when no non-system messages exist, and the file upload constraint (
selectedAttachments.length < 1) properly enforces the single-attachment limitation.
209-262: Message sending logic is well-implemented.The beforeSubmit async handling is correct, the file attachment flow properly filters by
status === 'success'(matching the upload callback), and state cleanup is properly sequenced.
264-266: Abort handling is correctly delegated.Emitting the abort event to the parent is the right approach, avoiding direct manipulation of the messages array and the bounds-check issues flagged in previous reviews.
277-475: Styles are well-structured.Good use of container queries for responsive design, CSS custom properties for theming, and scoped styles to avoid conflicts.
…api has duplicate [done] stream data
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/plugins/robot/src/composables/useChat.ts (1)
89-114: Add null guards forlastMessagein finish/error handlers.Both
handleFinishRequestandhandleRequestErrorassumemessages.at(-1)is always present. If the array is empty in any edge case,lastMessage.contentwill throw a runtime error.You can guard and also relax the error type to match actual usage:
const handleFinishRequest = async ( finishReason: string, messages: ChatMessage[], contextMessages: ChatMessage[], messageState: MessageState ) => { - const lastMessage = messages.at(-1) - - delete abortControllerMap.main - await onRequestEnd(finishReason, lastMessage.content, messages) // 本次请求结束 + const lastMessage = messages.at(-1) + + delete abortControllerMap.main + await onRequestEnd(finishReason, lastMessage?.content ?? '', messages) // 本次请求结束 if (finishReason === 'tool_calls' && lastMessage.tool_calls?.length) { await handleToolCall(lastMessage.tool_calls, messages, contextMessages) // eslint-disable-line } @@ -const handleRequestError = async (error: Error, messages: ChatMessage[], messageState: MessageState) => { +const handleRequestError = async (error: unknown, messages: ChatMessage[], messageState: MessageState) => { chatStatus = CHAT_STATUS.FINISHED delete abortControllerMap.main - 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 }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
packages/plugins/robot/src/composables/core/useConversation.ts(1 hunks)packages/plugins/robot/src/composables/useChat.ts(1 hunks)packages/plugins/robot/src/utils/schema.utils.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/plugins/robot/src/utils/schema.utils.ts
🧰 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: 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 (1)
packages/plugins/robot/src/composables/useChat.ts (8)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
useMode(59-80)packages/plugins/robot/src/composables/core/useMessageStream.ts (1)
createStreamDataHandler(76-106)packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
ChatRequestData(35-40)packages/plugins/robot/src/services/api.ts (1)
apiService(126-131)packages/plugins/robot/src/composables/core/useConversation.ts (1)
useConversationAdapter(26-118)packages/plugins/robot/src/composables/features/useToolCalls.ts (1)
createToolCallHandler(83-132)packages/plugins/robot/src/composables/core/useConfig.ts (1)
robotSettingState(443-468)packages/plugins/robot/src/utils/chat.utils.ts (1)
removeLoading(88-97)
⏰ 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 (6)
packages/plugins/robot/src/composables/core/useConversation.ts (1)
26-50: Adapter wiring looks clean and well‑factored.The useConversationAdapter nicely decouples tiny-robot-kit events from business hooks and keeps the integration surface tight (stream, finish, message-processed, status). No issues spotted here.
packages/plugins/robot/src/composables/useChat.ts (5)
191-214: sendUserMessage pipeline looks robust after awaitingnextTick.Awaiting
nextTickbefore adding the loading assistant message and sending ensures correct ordering and avoids the earlier race wheresend()could run before the loading message was appended. Abort controller setup and loading insertion are also cleanly separated.
226-233: ConfirmonRequestEnd('aborted', ...)is not invoked twice for the same abort.
abortRequestmanually callsonRequestEnd('aborted', ...), while the underlying client/adapter is also likely to triggeronFinishwithfinishReason === 'aborted', which then callshandleFinishRequest→onRequestEnd('aborted', ...)again.If the mode’s
onRequestEndhas side effects (logging, metrics, UI transitions), double invocation might be undesirable. Consider either:
- Treating the manual call in
abortRequestas the single source of truth and skippingonRequestEndon adapter finish whenfinishReason === 'aborted', or- Removing the explicit
onRequestEndhere and letting the adapter finish path handle it.Please verify which behavior you want and adjust accordingly.
235-246: changeChatMode logic is clear and aligns with metadata‑driven modes.The flow of creating/rehydrating a conversation, updating metadata when reusing the same conversation, and then syncing state (
updateChatModeState) and provider config (updateConfig({ apiUrl: getApiUrl() })) is coherent and keeps mode/config in sync.
167-183: Avoid non‑null assertions onconversationState.currentIdin lifecycle wrappers.The
conversationState.currentId!assertions suggest the value can be undefined, yet the code passes it directly toonConversationEndwithout guards. If no active conversation exists (first load or corrupted state), this becomes undefined and may cause unexpected behavior.Add simple guards to call
onConversationEndonly when an ID exists:
- In
createConversation: CheckcurrentConversationIdbefore callingonConversationEnd- In
switchConversation: GuardonConversationEnd(conversationState.currentId)with an if checkThe proposed diff removes non-null assertions and wraps calls safely, preserving behavior when valid IDs exist.
35-43:statusManagerforuseConversationAdaptermay not reliably suppress duplicateonFinishcalls.
onFinishin the adapter expectsstatusManager.isProcessing()to befalsefor the first finish andtruethereafter. ReusingchatStatus(PROCESSING/STREAMING/FINISHED) for this flag and resettingchatStatus = CHAT_STATUS.FINISHEDinsideonMessageProcessedmeans a second finish event will again seeisProcessing()asfalse, re-runningonFinishRequestandonMessageProcessed.Introduce a dedicated
hasHandledFinishflag decoupled fromchatStatus, reset only insendUserMessage():+let hasHandledFinish = false statusManager: { - isProcessing: () => chatStatus === CHAT_STATUS.PROCESSING, - setProcessing: () => { - chatStatus = CHAT_STATUS.PROCESSING - } + isProcessing: () => hasHandledFinish, + setProcessing: () => { + hasHandledFinish = true + } } const sendUserMessage = async () => { + hasHandledFinish = false onMessageSent()This keeps
chatStatusfor UI/streaming state while usinghasHandledFinishpurely to gateonFinish(lines 35-43, 138-143).


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:
二次开发:
文档:
工具调用生命周期(ChatMode):

Agent模式生命周期(AgentMode):

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.