Skip to content

Conversation

@hexqi
Copy link
Collaborator

@hexqi hexqi commented Sep 28, 2025

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • Agent mode with streaming tool-calls, richer AI chat UI (history, sessions, file/image upload, retry/abort), new chat components and renderers for reasoning and images.
    • Persisted model/settings UI and a configurable OpenAI-compatible provider for chat and streaming.
  • Bug Fixes

    • Authorization header now always a string; canceled requests no longer show errors; safer icon selection when missing.
  • Refactor

    • Centralized model/config composables and modernized chat internals for clearer state and extensibility.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 28, 2025

Walkthrough

Migrates the robot plugin to Vue 3 composables, adds an OpenAI-compatible provider (streaming and transport adapters), introduces agent/schema patching and new renderer/chat components, centralizes model configuration/state, replaces legacy utilities/prompts with structured prompts/data, and updates types and MCP/tool integration.

Changes

Cohort / File(s) Summary
Layout & UI
packages/layout/src/DesignSettings.vue, packages/layout/src/Main.vue
Removed top offset from right-panel; added position: relative to .tiny-engine-right-wrap.
Auth & HTTP helpers
packages/common/js/completion.js, designer-demo/src/composable/http/index.js
Authorization header set to `Bearer ${apiKey
LLM Provider & Client
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts, packages/plugins/robot/src/client/index.ts
New OpenAICompatibleProvider with fetch/axios adapters, streaming (SSE) support, error mapping, beforeRequest hook; createClient() wrapper added.
Composables: Chat / MCP / Agent / Config
packages/plugins/robot/src/composables/useChat.ts, packages/plugins/robot/src/composables/useMcp.ts, packages/plugins/robot/src/composables/useAgent.ts, packages/plugins/robot/src/composables/useConfig.ts
Added useChat (streaming, tool-calls, public API), refactored useMcp (internal llmTools, removed mcpServers), added useAgent (schema patching, search, assets), and new useConfig for model merging, persistence, and state.
Prompts & Prompt Data
packages/plugins/robot/src/prompts/index.ts, packages/plugins/robot/src/prompts/templates/agent-prompt.md, packages/plugins/robot/src/prompts/data/components.json, packages/plugins/robot/src/prompts/data/examples.json
New prompt generators (agent/chat/json-fix), agent prompt template, component catalog and example dataset.
Utils & Chat Helpers
packages/plugins/robot/src/utils/chat.utils.ts, packages/plugins/robot/src/utils/index.ts
New chat utilities: formatMessages, serializeError, mergeStringFields, fetchLLM, processSSEStream; added re-export barrel.
Types & Constants
packages/plugins/robot/src/types/mcp.types.ts, packages/plugins/robot/src/types/chat.types.ts, packages/plugins/robot/src/types/index.ts, packages/plugins/robot/src/constants/model-config.ts, packages/plugins/robot/src/constants/index.ts
Added MCP types and response shapes, updated message content typing, re-exported types, and added DEFAULT_LLM_MODELS and reasoningExtraBody.
Main Plugin & Metas
packages/plugins/robot/index.ts, packages/plugins/robot/src/metas/index.ts, packages/plugins/robot/package.json, packages/plugins/robot/meta.js
RobotService import/source adjusted to metas; metas now use useConfig exports; dependency bumps (robot packages, added @vueuse/core); meta icon options extended (customCompatibleAIModels, enableResourceContext, enableRagContext).
UI Components Added
packages/plugins/robot/src/Main.vue, packages/plugins/robot/src/components/chat/RobotChat.vue, packages/plugins/robot/src/components/chat/FooterButton.vue, packages/plugins/robot/src/components/renderers/*.vue
Main migrated to script-setup; added RobotChat, FooterButton, AgentRenderer, ImgRenderer, LoadingRenderer with new props/exports and exposed APIs.
Footer & Header Adjustments
packages/plugins/robot/src/components/footer-extension/McpServer.vue, packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue, packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
Reworked MCP server UI to use FooterButton; RobotTypeSelect prop renamed to chatMode (default 'agent'); RobotSettingPopover integrated with useConfig, model option computations, and persisted settings.
Icon Templates Modified
packages/plugins/robot/src/components/icons/mcp-icon.vue, packages/plugins/robot/src/components/icons/page-icon.vue, packages/plugins/robot/src/components/icons/study-icon.vue
Removed SVG metadata and removed component script exports (templates remain).
Removed Legacy JS Modules & Components
packages/plugins/robot/src/js/useRobot.ts, packages/plugins/robot/src/js/prompts.ts, packages/plugins/robot/src/js/utils.ts, packages/plugins/robot/src/mcp/utils.ts, packages/plugins/robot/src/BuildLoadingRenderer.vue
Deleted legacy useRobot, old prompts, old utils, MCP helpers, and old loading renderer.
Resources & Misc
packages/plugins/resource/src/ResourceList.vue, docs/extension-capabilities-tutorial/ai-plugin-configuration.md, packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue
batchCreateResource includes description fallback; docs path updated; optional chaining used for icon lookup.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant Chat as RobotChat
    participant Composable as useChat
    participant Provider as OpenAICompatibleProvider
    participant MCP as MCP Server
    participant Canvas as Canvas

    U->>Chat: send message / attach file
    Chat->>Composable: handleSendMessage(...)
    Composable->>Provider: chatStream(request, handler)
    Provider-->>Composable: SSE deltas (reasoning / markdown / tool_calls)
    alt tool_calls present
        Composable->>MCP: invoke tool with args
        MCP-->>Composable: streamed tool result
        Composable->>Provider: continue/resume chat with tool result
    end
    Composable->>Canvas: updatePageSchema(patches)  %% agent-mode patches applied
    Composable-->>Chat: finalize message / update UI
    Chat-->>U: render response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas needing extra attention:

  • packages/plugins/robot/src/composables/useChat.ts — streaming orchestration, delta merging, tool-call recursion and abort handling.
  • packages/plugins/robot/src/client/OpenAICompatibleProvider.ts — transport adapters, SSE handling, error mapping between transports.
  • packages/plugins/robot/src/composables/useAgent.ts — JSON Patch validation, patch application, and auto-fix logic.
  • packages/plugins/robot/src/metas/index.ts & useConfig composable — config merging, persistence, and effects on initialization.
  • Removed legacy modules (js/utils, prompts, mcp/utils) — ensure no remaining imports reference deleted APIs.

"🐰
I hopped through code with curious paws,
Streams and patches, clever laws,
Tools answered calls and pages grew,
Composables hummed as changes flew,
Tiny garden blooms anew!"

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat: ai plugin refactor' is vague and generic. It describes the overall category (AI plugin refactor) but lacks specificity about the main changes. Given the extensive refactoring across multiple files including composables, components, utilities, and configuration management, a more descriptive title would better convey the scope. Consider a more specific title that highlights key changes, such as 'feat: refactor ai plugin with composable-based architecture and openai-compatible provider' or 'feat: restructure ai plugin with useChat, useConfig, and streaming support'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hexqi hexqi marked this pull request as draft September 28, 2025 03:22
@hexqi hexqi force-pushed the feat/ai-plugin-refactor branch from 0d7f889 to 57ac995 Compare September 28, 2025 03:24
@hexqi hexqi force-pushed the feat/ai-plugin-refactor branch from 57ac995 to 5eb53e9 Compare September 29, 2025 07:06
@chilingling
Copy link
Member

image

模型配置需要支持配置额外传参,比如配置一个 qwen-plus-thinking 模型,可以配置额外带一个 { enable_thinking: true } 的参数

@chilingling
Copy link
Member

感觉可以增强一下代码结构,增强插件的可拓展性与可维护性,比如:

  1. 增强 useRobot.ts 中的 AIModelOptions。提供默认配置,允许在AI 插件配置层级进行新增与隐藏。

好处:将大模型提供商+模型的静态配置集中放置,容易阅读+可维护;也方便后续集中提供配置进行新增或者隐藏

增强示例结构:

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,
            },
        },
    },
  ]
}
  1. 增强 modelProvider 能力。(clients/index.ts、clients/OpenAICompatibleProvider.ts)
    当前我们提供了基础的 openai compatible 的 modelProvider。
    建议:
  • 集成处理 tool_call 的能力(当前处理 tool_call 放置在了 useChat.ts 中)
  • 将 agent模式/chat 模式的处理,抽离出来。

好处:不同的大模型提供商、甚至不同的大模型的 tool_call 格式、以及传参可能都有细微的差别,我们将通用的处理模式全部内聚到一个 provider 里面,后续如果有定制化的需求,直接开放配置出来,让二开用户传入自己的 provider 即可处理 tool_call 格式、传参的相关差异。

总结:增强扩展性+高内聚

  1. 增加模式处理器(agent Mode、chat Mode、plan Mode 等等)
    不同的模式,可能 system prompt、可以调用的工具、可以调用的大模型不同。原本的一些处理我们在 useChat 和 modelProvider 中都有散落处理,可以考虑抽离一个 chatMode 之类的处理器,内聚处理不同模式的差异,然后再传递给 modelProvider。做到下层无感知。

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically.


It feels like the code structure can be enhanced to enhance the scalability and maintainability of the plug-in, such as:

  1. Enhance AIModelOptions in useRobot.ts. Provides default configuration, allowing adding and hiding at the AI ​​plug-in configuration level.

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,
            },
        },
    },
  ]
}
  1. Enhance modelProvider capabilities. (clients/index.ts, clients/OpenAICompatibleProvider.ts)
    Currently we provide a basic openai compatible modelProvider.
    suggestion:
  • Integrated ability to handle tool_call (currently processing tool_call is placed in useChat.ts)
  • Extract the processing of agent mode/chat mode.

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

  1. Add mode processor (agent Mode, chat Mode, plan Mode, etc.)
    Different modes may have different system prompts, tools that can be called, and large models that can be called. We have scattered some of the original processing in useChat and modelProvider. We can consider extracting a processor such as chatMode to cohesively handle the differences in different modes, and then pass it to modelProvider. Make sure the lower level is insensitive.

@hexqi hexqi force-pushed the feat/ai-plugin-refactor branch from cd3d8c0 to 8005437 Compare October 20, 2025 11:51
@hexqi hexqi force-pushed the feat/ai-plugin-refactor branch from f8a401a to 0d7ca22 Compare October 28, 2025 10:09
@hexqi hexqi changed the title [WIP] feat: ai plugin refactor feat: ai plugin refactor Nov 3, 2025
@github-actions github-actions bot added documentation Improvements or additions to documentation enhancement New feature or request labels Nov 3, 2025
@hexqi hexqi marked this pull request as ready for review November 3, 2025 01:09
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/plugins/robot/src/components/McpServer.vue (1)

34-81: Make activeCount reactive to picker state.

activeCount is initialized with ref(1) and never updated, even though the picker exposes v-model:activeCount. The template now binds that model directly to activeCount, 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: Restore state.modelOptions removal side-effect.

state.modelOptions was removed in favor of computed modelOptions, but changeBaseUrl still assigns to state.modelOptions, leaving state.existFormData.model reading from undefined[0] on the next line. Update the assignment to use modelOptions.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 name is guaranteed to exist in SvgICons (since it's derived from Object.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 showError to 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-patch uses ~3.1.1 (patch-level), jsonrepair uses exact 3.13.0, and markdown-it uses ^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 between url and baseUrl.

RequestOptions now contains both url?: string (line 4) and baseUrl?: string (line 7). Additionally, LLMRequestBody (line 45) also has baseUrl?: string. This creates potential confusion about their roles and precedence. Consider:

  • If baseUrl is the API endpoint root and url is 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, if handleVisibleToggle or any other composition logic directly accesses active, 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 to fixIconComponent.

Line 20 accesses data?.componentName without a type guard, and line 21 assigns to data.props.name assuming the shape is correct. If data doesn'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 to fixComponentName.

Line 30 assigns data.componentName = 'div' without proper type checking. TypeScript will allow this on object type, 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 any type for the child parameter. 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 any types for patch, index, and arr.

 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 in applyPatch.

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 in search function.

The search function (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 messageContent is 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 mergeStringFields function 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 afterToolCallAbortController created at Line 262 is not explicitly cleaned up in all paths. While JavaScript GC will eventually collect it, explicitly setting it to null after 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 if createConversation returned 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 (defer attribute, v-if="showTeleport", and a 1000ms delay) without explanation. While the target element .tiny-engine-right-robot exists in the layout component, the hardcoded delay appears redundant with Vue's defer support and warrants clarification.

Add a comment explaining whether:

  • The delay is needed due to specific component initialization order
  • Both defer and the timeout are intentional or if one can be removed
  • The 1000ms value is based on measured timing requirements or conservative estimation

Consider whether nextTick combined 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 axiosClient is provided when httpClientType is 'axios'. However, the beforeRequest hook lacks documentation about what transformations are safe and whether async operations are fully supported in all code paths.

Consider adding JSDoc examples for the beforeRequest hook:

   /**
    * @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) and sendFetchRequest (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 createFetchAdapter to call executeFetch.


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 the httpClientType option.

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.axiosClient

Alternatively, 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 })
).response

This 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.setItem can throw QuotaExceededError if 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.ts reduces 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

📥 Commits

Reviewing files that changed from the base of the PR and between c27e843 and e9fb7d8.

⛔ Files ignored due to path filters (3)
  • packages/plugins/robot/assets/failed.svg is excluded by !**/*.svg
  • packages/plugins/robot/assets/success.svg is excluded by !**/*.svg
  • packages/plugins/robot/assets/test.png is 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.md
  • docs/extension-capabilities-tutorial/ai-plugin-configuration.md
  • packages/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.vue
  • packages/plugins/robot/src/components/RobotTypeSelect.vue
  • packages/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.vue
  • packages/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.vue
  • packages/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.vue
  • packages/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.vue
  • packages/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.vue
  • packages/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.ts
  • 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
🧬 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: relative to .tiny-engine-right-wrap creates an explicit positioning context for the child <design-settings> component, which pairs with the removal of top-offset absolute positioning adjustments in DesignSettings.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 ensures apiKey is 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/core is properly utilized in packages/plugins/robot/src/composables/agent.ts: the useThrottleFn function throttles the updatePageSchema export (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-register is 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 (RobotService from @opentiny/tiny-engine-plugin-robot in 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 components

No 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.ts to src/composables/useRobot.ts.

packages/plugins/robot/src/components/FooterButton.vue (1)

116-125: Verify parent container has container-type set.

The @container query (line 116) requires the parent element to have container-type: inline-size or container-name set. 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-if ensures 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 of tool_calls and tool_call_id is 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 if dataMatch is null. 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 use lines.slice(0, -1) for clarity.

   const lines = data.split('\n\n')
-  lines.pop()
+  lines.pop() // Remove trailing empty element after final \n\n

Likely an incorrect or invalid review comment.

packages/plugins/robot/src/composables/useChat.ts (1)

119-162: Streaming delta handling is well-structured.

The onReceiveData handler effectively processes streaming responses, handling reasoning content, regular content, and tool_calls. The use of preventDefault() to override default behavior and the status tracking via chatStatus are 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 via formatMessages, and applies a three-tier model fallback. The beforeRequest hook 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 robotSettingState allows components to observe configuration changes.

Comment on lines +4 to +7
interface ClientOptions {
config: Omit<AIModelConfig, 'provider' | 'providerImplementation'>
beforeRequest: () => object
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the beforeRequest type signature.

Line 6 defines beforeRequest: () => object, but the OpenAICompatibleProvider constructor (line 10) expects a function with signature (request: ChatRequestData) => ChatRequestData | Promise<ChatRequestData> (see OpenAICompatibleProvider.ts line 52). This type mismatch will cause runtime errors when the provider invokes beforeRequest.

Apply this diff:

 interface ClientOptions {
   config: Omit<AIModelConfig, 'provider' | 'providerImplementation'>
-  beforeRequest: () => object
+  beforeRequest: (request: ChatRequestData) => ChatRequestData | Promise<ChatRequestData>
 }

You'll also need to import or define ChatRequestData in this file.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/plugins/robot/src/client/index.ts around lines 4 to 7, the
ClientOptions.beforeRequest type is currently declared as () => object but must
match OpenAICompatibleProvider which expects (request: ChatRequestData) =>
ChatRequestData | Promise<ChatRequestData>; update the beforeRequest signature
accordingly and import or define ChatRequestData at the top of the file (from
the module where it's defined or add a local type alias) so the ClientOptions
type becomes beforeRequest: (request: ChatRequestData) => ChatRequestData |
Promise<ChatRequestData>, ensuring types align with OpenAICompatibleProvider.

Comment on lines +87 to +126
private toAIAdapterError(error: unknown): AIAdapterError {
if (error instanceof Error) {
// 根据错误消息判断错误类型
const message = error.message.toLowerCase()
let type = ErrorType.UNKNOWN_ERROR
let statusCode: number | undefined

if (message.includes('http error')) {
const statusMatch = message.match(/status:\s*(\d+)/)
if (statusMatch) {
statusCode = parseInt(statusMatch[1], 10)
if (statusCode === 401 || statusCode === 403) {
type = ErrorType.AUTHENTICATION_ERROR
} else if (statusCode === 429) {
type = ErrorType.RATE_LIMIT_ERROR
} else if (statusCode >= 500) {
type = ErrorType.SERVER_ERROR
} else {
type = ErrorType.NETWORK_ERROR
}
}
} else if (message.includes('network') || message.includes('fetch')) {
type = ErrorType.NETWORK_ERROR
} else if (message.includes('timeout')) {
type = ErrorType.TIMEOUT_ERROR
}

return {
type,
message: error.message,
statusCode,
originalError: error
}
}

return {
type: ErrorType.UNKNOWN_ERROR,
message: String(error)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Error type detection relies on fragile string matching.

The method infers error types by searching for substrings like "http error", "network", "fetch", and "timeout" in lowercased error messages. This approach is brittle—error messages can vary across browsers, fetch implementations, and network stacks.

Consider enhancing error detection:

   private toAIAdapterError(error: unknown): AIAdapterError {
+    // Check for specific error types first
+    if (error instanceof TypeError && error.message.includes('fetch')) {
+      return { type: ErrorType.NETWORK_ERROR, message: error.message, originalError: error }
+    }
+    if (error instanceof DOMException && error.name === 'AbortError') {
+      return { type: ErrorType.TIMEOUT_ERROR, message: error.message, originalError: error }
+    }
+
     if (error instanceof Error) {
       const message = error.message.toLowerCase()

Alternatively, preserve structured error info (status codes, error names) from the source and rely less on message parsing.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private toAIAdapterError(error: unknown): AIAdapterError {
if (error instanceof Error) {
// 根据错误消息判断错误类型
const message = error.message.toLowerCase()
let type = ErrorType.UNKNOWN_ERROR
let statusCode: number | undefined
if (message.includes('http error')) {
const statusMatch = message.match(/status:\s*(\d+)/)
if (statusMatch) {
statusCode = parseInt(statusMatch[1], 10)
if (statusCode === 401 || statusCode === 403) {
type = ErrorType.AUTHENTICATION_ERROR
} else if (statusCode === 429) {
type = ErrorType.RATE_LIMIT_ERROR
} else if (statusCode >= 500) {
type = ErrorType.SERVER_ERROR
} else {
type = ErrorType.NETWORK_ERROR
}
}
} else if (message.includes('network') || message.includes('fetch')) {
type = ErrorType.NETWORK_ERROR
} else if (message.includes('timeout')) {
type = ErrorType.TIMEOUT_ERROR
}
return {
type,
message: error.message,
statusCode,
originalError: error
}
}
return {
type: ErrorType.UNKNOWN_ERROR,
message: String(error)
}
}
private toAIAdapterError(error: unknown): AIAdapterError {
// Check for specific error types first
if (error instanceof TypeError && error.message.includes('fetch')) {
return { type: ErrorType.NETWORK_ERROR, message: error.message, originalError: error }
}
if (error instanceof DOMException && error.name === 'AbortError') {
return { type: ErrorType.TIMEOUT_ERROR, message: error.message, originalError: error }
}
if (error instanceof Error) {
// 根据错误消息判断错误类型
const message = error.message.toLowerCase()
let type = ErrorType.UNKNOWN_ERROR
let statusCode: number | undefined
if (message.includes('http error')) {
const statusMatch = message.match(/status:\s*(\d+)/)
if (statusMatch) {
statusCode = parseInt(statusMatch[1], 10)
if (statusCode === 401 || statusCode === 403) {
type = ErrorType.AUTHENTICATION_ERROR
} else if (statusCode === 429) {
type = ErrorType.RATE_LIMIT_ERROR
} else if (statusCode >= 500) {
type = ErrorType.SERVER_ERROR
} else {
type = ErrorType.NETWORK_ERROR
}
}
} else if (message.includes('network') || message.includes('fetch')) {
type = ErrorType.NETWORK_ERROR
} else if (message.includes('timeout')) {
type = ErrorType.TIMEOUT_ERROR
}
return {
type,
message: error.message,
statusCode,
originalError: error
}
}
return {
type: ErrorType.UNKNOWN_ERROR,
message: String(error)
}
}

Comment on lines 33 to 68
const statusDataMap = {
reasoning: {
title: '深度思考中,请稍等片刻',
icon: 'loading.webp',
content: () => props.content?.slice(-30)
},
loading: {
title: '页面生成中,请稍等片刻',
icon: 'loading.webp',
content: () => props.content?.slice(-30)
},
success: {
title: '已生成新页面效果',
content: '您可以继续问答更新页面效果',
icon: 'success.svg'
},
failed: {
title: '页面生成失败',
content: '页面生成失败',
icon: 'failed.svg'
}
}
const hasReasoningFinished = computed(() => props.contentType === 'reasoning' && props.status !== 'reasoning')
const statusData = computed(() => {
let status = props.status as keyof typeof statusDataMap
if (props.contentType === 'reasoning') {
status = 'reasoning'
}
const data = statusDataMap[status] || statusDataMap.loading
return {
...data,
content: typeof data.content === 'function' ? data.content() : data.content
}
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against missing content before slicing.

Both reasoning and loading statuses call props.content?.slice(-30), but when content is an empty string slice(-30) returns an empty string, rendering a blank helper message; worse, when content is undefined the optional chaining still yields undefined, which Vue renders literally. Default to a meaningful fallback text (e.g., '...') so the user never sees blank or “undefined” status text.

🤖 Prompt for AI Agents
packages/plugins/robot/src/components/AgentRenderer.vue lines 33-68: the
functions for the reasoning and loading statuses use props.content?.slice(-30)
which can produce undefined or an empty string and render as "undefined" or
blank; update those content functions to return a guaranteed string fallback
(e.g., props.content ? String(props.content).slice(-30) : '...') so when content
is undefined or empty the UI shows the fallback; also ensure the computed
statusData preserves that fallback by returning the evaluated content as a
string.

Comment on lines 177 to 196
const handleFileSelected = (formData: unknown, updateAttachment: (resourceUrl: string) => void) => {
try {
getMetaApi(META_SERVICE.Http)
.post('/material-center/api/resource/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
await scrollContent()
await sleep(1000)
messages.value.push(getAiDisplayMessage('好的,正在执行相关操作,请稍等片刻...'))
await scrollContent()
await sendStreamRequest()
}
}
// 根据localstorage初始化AI大模型
const initCurrentModel = (aiSession) => {
robotSettingState.selectedModel = {
...JSON.parse(aiSession)?.foundationModel
}
aiType.value = JSON.parse(aiSession)?.aiType || aiType.value
}
const initChat = () => {
const aiChatSession = localStorage.getItem('aiChat')
if (!aiChatSession) {
setContextSession()
} else {
initCurrentModel(aiChatSession) // 如果当前缓存有值,那么则需要根据缓存里的内容去初始化当前选择的模型
}
sessionProcess = JSON.parse(localStorage.getItem('aiChat'))
messages.value = [...(sessionProcess?.displayMessages || [])]
resetContent()
}
onMounted(async () => {
setTimeout(() => {
showTeleport.value = true
}, 1000)
const loadingInstance = Loading.service({
text: '初始化中,请稍等...',
customClass: 'chat-loading',
background: 'rgba(0, 0, 0, 0.15)',
target: '#bind-chatgpt',
size: 'large'
})
await initBlockList()
loadingInstance.close()
initChat()
})
const endContent = () => {
localStorage.removeItem('aiChat')
sessionProcess = null
inProcesing.value = false
initChat()
}
const changeApiKey = () => {
localStorage.removeItem('aiChat')
sessionProcess = null
setContextSession()
sessionProcess = JSON.parse(localStorage.getItem('aiChat'))
}
const changeModel = (model) => {
if (
robotSettingState.selectedModel.baseUrl !== model.baseUrl ||
robotSettingState.selectedModel.model !== model.model
) {
confirm({
title: '切换AI大模型',
message: '切换AI大模型将导致当前会话被清空,重新开启新会话,是否继续?',
exec() {
robotSettingState.selectedModel = {
label: model.label || model.model,
activeName: model.activeName,
baseUrl: model.baseUrl,
model: model.model,
completeModel: model.completeModel,
apiKey: model.apiKey
}
singleAttachmentItems.value = []
imageUrl.value = ''
endContent()
}
})
}
if (
robotSettingState.selectedModel.apiKey !== model.apiKey &&
robotSettingState.selectedModel.baseUrl === model.baseUrl &&
robotSettingState.selectedModel.model === model.model
) {
robotSettingState.selectedModel.apiKey = model.apiKey
changeApiKey()
}
}
const openAIRobot = () => {
robotVisible.value = !robotVisible.value
}
const closePanel = () => {
showPopover.value = false
}
// 控制全屏切换
const fullscreen = ref(false)
// 欢迎界面提示
const promptItems: PromptProps[] = [
{
label: 'MCP工具',
description: '帮我查询当前的页面列表',
icon: h(McpIconComponent),
badge: 'NEW'
},
{
label: '页面搭建场景',
description: '给当前页面中添加一个问卷调查表单',
icon: h(PageIconComponent)
},
{
label: '学习/知识型场景',
description: 'Vue3 和 React 有什么区别?',
icon: h(StudyIconComponent)
}
]
// 处理提示项点击事件
const handlePromptItemClick = (ev: unknown, item: { description?: string }) => {
sendContent(item.description, true)
}
// Icon
const getSvgIcon = (name: string, style?: CSSProperties) => {
return h(resolveComponent('svg-icon'), { name, style: { fontSize: '32px', ...style } })
}
const aiAvatar = getSvgIcon('AI')
const userAvatar = getSvgIcon('user-head', { color: '#dfe1e6' })
const welcomeIcon = getSvgIcon('AI', { fontSize: '48px' })
// 处理文件选择事件
const handleSingleFilesSelected = (files: FileList | null, retry = false) => {
if (retry) {
singleAttachmentItems.value[0].status = 'uploading'
singleAttachmentItems.value[0].isUploading = true
singleAttachmentItems.value[0].messageType = 'uploading'
} else {
if (!files.length) return
if (files && files.length > 1) {
Notify({
type: 'error',
message: '当前仅支持上传一张图片',
position: 'top-right',
duration: 5000
})
return
.then((res: any) => {
updateAttachment(res?.resourceUrl)
if (!inputMessage.value) {
inputMessage.value = '生成图片中UI效果'
}
if (files && files.length > 0) {
// 将选中的文件转换为 Attachment 格式并添加到附件列表
const newAttachments = Array.from(files).map((file) => ({
size: file.size,
rawFile: file
}))
singleAttachmentItems.value.push(...newAttachments)
}
}
// 开始上传
const formData = new FormData()
const fileData = retry ? files : files[0]
formData.append('file', fileData)
try {
getMetaApi(META_SERVICE.Http)
.post('/material-center/api/resource/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then((res) => {
if (res?.resourceUrl) {
imageUrl.value = res.resourceUrl
singleAttachmentItems.value[0].status = 'done'
singleAttachmentItems.value[0].isUploading = false
singleAttachmentItems.value[0].messageType = 'success'
} else {
imageUrl.value = ''
singleAttachmentItems.value[0].status = 'error'
singleAttachmentItems.value[0].isUploading = false
singleAttachmentItems.value[0].messageType = 'error'
}
})
} catch (error) {
// eslint-disable-next-line no-console
console.error('上传失败', error)
}
}
const handleSingleFileRemove = () => {
imageUrl.value = ''
}
const handleSingleFileRetry = (file: any) => {
handleSingleFilesSelected(file.file, true)
}
const typeChange = (type) => {
aiType.value = type
endContent()
}
const roles: Record<string, BubbleRoleConfig> = {
assistant: {
placement: 'start',
avatar: aiAvatar,
maxWidth: '90%',
contentRenderer: MarkdownRenderer,
customContentField: 'renderContent'
},
user: { placement: 'end', avatar: userAvatar, maxWidth: '90%', contentRenderer: MarkdownRenderer },
system: { hidden: true }
}
watch([() => activeMessages.value.length, () => activeMessages.value.at(-1)?.renderContent?.length ?? 0], () => {
scrollContent()
})
const contentRenderers: Record<string, Component> = {
markdown: MarkdownRenderer,
loading: LoadingRenderer
}
const buildContentRenderers: Record<string, Component> = {
loading: BuildLoadingRenderer
}
const mcpDrawerPosition = computed(() => {
return {
type: 'fixed',
position: {
top: 'var(--base-top-panel-height)',
bottom: 0,
...(fullscreen.value ? { left: 0 } : { right: 'var(--tr-container-width)' })
}
}
})
const isVisualModel = () => {
const platform = AIModelOptions.find((option) => option.value === robotSettingState.selectedModel.baseUrl)
const modelAbility = platform.model.find((item) => item.value === robotSettingState.selectedModel.model)
return modelAbility?.ability?.includes('visual') || false
}
return {
chatContainerRef,
robotVisible,
avatarUrl,
chatWindowOpened,
activeMessages,
inputContent,
connectedFailed,
AIModelOptions,
robotSettingState,
showPopover,
fullscreen,
welcomeIcon,
roles,
currentSchema,
showPreview,
singleAttachmentItems,
VISUAL_MODEL,
promptItems,
MarkdownRenderer,
requestLoading,
aiType,
AI_MODES,
showTeleport,
sendContent,
endContent,
changeApiKey,
resizeChatWindow,
changeModel,
openAIRobot,
closePanel,
handlePromptItemClick,
setSchema,
handleSingleFilesSelected,
handleSingleFileRemove,
handleSingleFileRetry,
typeChange,
isVisualModel,
contentRenderers,
mcpDrawerPosition,
buildContentRenderers
}
})
} catch (error) {
// eslint-disable-next-line no-console
console.error('上传失败', error)
updateAttachment('')
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Improve error feedback for file upload failures.

The catch block (lines 191-195) logs the error and signals failure via updateAttachment(''), but provides no visible feedback to the user. The user might not notice the upload failed, especially if the console is not open.

Apply this diff to add user notification:

   } catch (error) {
     // eslint-disable-next-line no-console
     console.error('上传失败', error)
     updateAttachment('')
+    Notify({
+      type: 'error',
+      message: '文件上传失败,请重试',
+      position: 'top-right',
+      duration: 5000
+    })
   }
 }

Note: You'll need to import Notify from @opentiny/vue (it's already imported at the file level, so this should work).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/plugins/robot/src/Main.vue around lines 177 to 196, the catch block
for handleFileSelected only logs the error and calls updateAttachment('') so the
user sees no visible feedback; update the catch to call Notify.error(...)
(import already present) with a concise message like "文件上传失败,请重试" and include
the error message/details, keep the existing updateAttachment('') behavior, and
optionally reset inputMessage to an empty or failure notice so the UI reflects
the failed upload.

Comment on lines 24 to 123
"value": {
"componentName": "div",
"id": "25153243",
"props": {
"className": "component-base-style"
},
"children": [
{
"componentName": "h1",
"props": {
"className": "component-base-style"
},
"children": "消息列表",
"id": "53222591"
},
{
"componentName": "div",
"props": {
"className": "component-base-style div-uhqto",
"alignItems": "flex-start"
},
"children": [
{
"componentName": "div",
"props": {
"className": "component-base-style div-vinko",
"onClick": {
"type": "JSExpression",
"value": "this.onClickMessage",
"params": ["message", "index"]
},
"key": {
"type": "JSExpression",
"value": "index"
}
},
"children": [
{
"componentName": "Text",
"props": {
"style": "display: inline-block;",
"text": {
"type": "JSExpression",
"value": "message.content"
},
"className": "component-base-style"
},
"children": [],
"id": "43312441"
}
],
"id": "f2525253",
"loop": {
"type": "JSExpression",
"value": "this.state.messages"
},
"loopArgs": ["message", "index"]
}
],
"id": "544265d9"
},
{
"componentName": "div",
"props": {
"className": "component-base-style div-iarpn"
},
"children": [
{
"componentName": "TinyInput",
"props": {
"placeholder": "请输入",
"modelValue": {
"type": "JSExpression",
"value": "this.state.inputMessage",
"model": true
},
"className": "component-base-style",
"type": "textarea"
},
"children": [],
"id": "24651354"
},
{
"componentName": "TinyButton",
"props": {
"text": "发送",
"className": "component-base-style",
"onClick": {
"type": "JSExpression",
"value": "this.sendMessage"
}
},
"children": [],
"id": "46812433"
}
],
"id": "3225416b"
}
]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix example component IDs to satisfy schema rules.

Several component id values in this example ("25153243", "53222591", "f2525253", "3225416b" …) lack the required mix of uppercase letters, lowercase letters, and digits that Section 3.1 of the agent prompt mandates. Because these examples feed directly into the system prompt, they now teach the model an invalid pattern and will cause generated patches to fail schema validation. Please update every new component ID in this file to an 8-character string containing at least one uppercase letter, one lowercase letter, and one digit, and keep them unique (e.g., "a7Kp2sN9").

Comment on lines 9 to 49
const formatComponentsToJsonl = (components: any[]): string => {
return '```jsonl\n' + components.map((comp) => JSON.stringify(comp)).join('\n') + '\n```'
}

/**
* Format examples object to readable text
*/
const formatExamples = (examples: Record<string, any>): string => {
return Object.entries(examples)
.map(([_key, example]) => {
const { name, description, note, patch } = example
const header = `### ${name}\n${description ? `${description}\n` : ''}${note ? `**Note**: ${note}\n` : ''}`
const patchContent = JSON.stringify(patch)
return `${header}\n${patchContent}`
})
.join('\n\n')
}

/**
* Generate agent system prompt with dynamic components and examples
*/
export const getAgentSystemPrompt = (currentPageSchema: object, referenceContext: string) => {
// Format components list
const ignoreComponents = ['TinyNumeric'] // 组件报错,先忽略
const componentsList = formatComponentsToJsonl(
componentsData.filter((component) => !ignoreComponents.includes(component.component))
)

// Format examples section
const examplesSection = formatExamples(examplesData)

// Format current page schema
const currentPageSchemaStr = JSON.stringify(currentPageSchema)

// Replace all placeholders in the prompt template
const prompt = agentPrompt
.replace('{{COMPONENTS_LIST}}', componentsList)
.replace('{{EXAMPLES_SECTION}}', examplesSection)
.replace('{{CURRENT_PAGE_SCHEMA}}', currentPageSchemaStr)
.replace('{{REFERENCE_KNOWLEDGE}}', referenceContext || '')

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Escape braces when injecting JSON into Markdown.

formatExamples stringifies the patch array without escaping braces, so when injected into the Markdown template the raw {/} pairs can be interpreted as Markdown lists or template placeholders, breaking prompt formatting. Wrap the JSON in fenced code (or escape braces) before substitution to keep the agent prompt structurally sound.

🤖 Prompt for AI Agents
In packages/plugins/robot/src/prompts/index.ts around lines 9-49, the code
injects raw JSON into the Markdown prompt which allows unescaped `{`/`}` to be
treated as template/Markdown and break formatting; update formatExamples so the
patch JSON is wrapped in a fenced code block (e.g. ```json\n<JSON>\n```) instead
of inserting raw JSON, and ensure formatComponentsToJsonl already returns a
fenced jsonl block (or change it to do so) so both COMPONENTS_LIST and
EXAMPLES_SECTION are safe when .replace(...) injects them into the agent prompt.

Comment on lines 9 to 27
export interface RequestTool {
type: 'function'
function: {
name: string
description: string
parameters: {
type: 'object'
required?: string[]
properties: Record<
string,
{
type: string
description: string
[prop: string]: unknown
}
>
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Consolidate duplicated RequestTool type definition.

RequestTool is defined in both mcp-types.ts (lines 9-27) and types/types.ts with slight differences:

  • types/types.ts includes an optional title?: string field in the function object
  • mcp-types.ts omits this field

This duplication creates maintenance burden and potential type inconsistencies. The useMcp.ts file imports RequestTool from types/types.ts, while other files might use this version from mcp-types.ts.

Consolidate to a single definition in one shared location:

// In types/types.ts - keep this as the canonical definition
export interface RequestTool {
  type: 'function'
  function: {
    name: string
    description: string
    title?: string  // Keep the optional title field
    parameters: {
      type: 'object'
      required?: string[]
      properties: Record<string, {
        type: string
        description: string
        [prop: string]: unknown
      }>
    }
  }
}

Then in mcp-types.ts:

+export type { RequestTool } from './types'
-export interface RequestTool {
-  type: 'function'
-  ...
-}
🤖 Prompt for AI Agents
In packages/plugins/robot/src/types/mcp-types.ts around lines 9 to 27 you have a
duplicated RequestTool interface that omits the optional title field present in
types/types.ts; replace the local definition by removing the interface here and
importing RequestTool from the canonical types/types.ts, update any local
references to use the imported type, and ensure the shared definition in
types/types.ts includes the optional title?: string in the function object so
all consumers use the same shape.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/plugins/robot/src/prompts/index.ts (1)

16-25: Consider consistent fencing for all JSON injections.

While formatComponentsToJsonl correctly wraps output in a fenced code block, formatExamples (line 21) and the CURRENT_PAGE_SCHEMA injection (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_SCHEMA if 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 description field 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 only null or undefined values, 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

📥 Commits

Reviewing files that changed from the base of the PR and between c3991d9 and 59d1acf.

📒 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 SvgICons and the default componentName assignment 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 jsonrepair for 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 addSystemPrompt logic correctly handles both insertion and updates.


120-163: Event handlers correctly orchestrate the streaming workflow.

The onReceiveData and onFinish handlers 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, and handleDeltaToolCalls cleanly separates concerns. The mergeStringFields recursive merge is an elegant solution for assembling fragmented tool_call data.


253-348: Tool-calling flow is comprehensive and robust.

The handleToolCall function 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 abortRequest override correctly handles both the custom abort controller and the base message manager.

Comment on lines +111 to +127
export const search = async (content: string) => {
let result = ''
const MAX_SEARCH_LENGTH = 8000
try {
const res = await getMetaApi(META_SERVICE.Http).post('/app-center/api/ai/search', { content })

res.forEach((item: { content: string }) => {
if (result.length + item.content.length > MAX_SEARCH_LENGTH) {
return
}
result += item.content
})
} catch (error) {
// error
}
return result
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent error handling may hide issues.

The empty catch block (line 123-125) suppresses all search errors. Consider logging errors to aid debugging, even if the function should gracefully return an empty result to callers.

Apply this diff to add minimal error logging:

   } catch (error) {
-    // error
+    logger.error('Search failed:', error)
   }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/plugins/robot/src/composables/agent.ts around lines 111 to 127 the
catch block is empty which hides errors; update the catch to log the caught
error (e.g., console.error('search error:', error) or use the module's logger if
available) including context like the content being searched or error.stack,
then keep returning the empty result so behavior is unchanged.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (6)
packages/plugins/robot/src/composables/agent.ts (2)

89-100: Rename isFinial typo.

Line 89 and Line 98 still expose the isFinial parameter. This typo leaks into the public API and keeps propagating to call sites. Please rename it to isFinal (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 updates apiUrl, apiKey, and defaultModel; httpClientType and axiosClient are 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 calls updateAttachment(''). Users receive no visible indication that the upload failed. Consider importing and using a notification component (e.g., Message or Notify from @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. If customCompatibleAIModels removes 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 value to baseUrl and name.

🧹 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 text for raw JSON output, or json for 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 and handleChatModeChange has 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

📥 Commits

Reviewing files that changed from the base of the PR and between 59d1acf and ce65137.

📒 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.baseUrl ensures the UI updates correctly when the provider changes.


213-228: LGTM!

The changeBaseUrl handler correctly resets dependent fields (apiKey, model, completeModel) when the provider changes, persists the updated state, and synchronizes with robotSettingState.selectedModel.


241-258: LGTM!

The validation flow has been corrected—saveRobotSettingState is now called inside the valid branch (lines 246-250), ensuring invalid forms are never persisted. The optional chaining on form.value?.validate is also a good safety improvement.


261-283: Review comment is incorrect. Upstream initialization properly guards the concern.

The getAIModelOptions() function in useRobot.ts (lines 96-102) includes a guard: if (!customAIModels.length) return DEFAULT_LLM_MODELS. Since DEFAULT_LLM_MODELS is a non-empty, predefined array with multiple model configurations, getAIModelOptions() always returns a non-empty array. Consequently, robotSettingState.selectedModel is always initialized with complete field values (baseUrl, model, apiKey, completeModel, label) at lines 135-138 of useRobot.ts. The code in RobotSettingPopover.vue lines 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 toggleActive function are well-structured. The optional chaining in fullscreen (line 95) safely handles null refs, and toggleActive correctly persists the state change.


114-132: LGTM!

The promptItems array is well-structured with clear labels, descriptions, and icons. Using h() to render icon components is the correct approach for the composition API.


139-153: LGTM!

The isVisualModel and isToolsModel computed properties correctly derive capabilities from the selected model. The check toolCalling !== false (line 152) appropriately treats both true and undefined as tool-enabled.


198-202: LGTM!

The onMounted hook 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 mergeAIModelOptions clearly document the merge behavior, including provider removal, model removal, merging, and overriding.


96-102: LGTM!

The getAIModelOptions function correctly implements extensibility by allowing custom models via customCompatibleAIModels from 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (5)
packages/plugins/robot/src/composables/useAgent.ts (2)

154-170: Add error logging to aid debugging.

The empty catch block (lines 166-168) suppresses all search errors. Consider logging errors to aid debugging, even if the function gracefully returns an empty result.

Apply this diff:

   } catch (error) {
-    // error
+    logger.error('Search failed:', error)
   }

89-89: Fix typo: isFinialisFinal.

The parameter name isFinial at lines 89 and 98 is a typo and should be isFinal. Update all references within both functions.

Apply this diff:

-const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => {
+const jsonPatchAutoFix = (jsonPatches: any[], isFinal: boolean) => {
   // 流式渲染过程中,画布只渲染children字段,避免不完整的methods/states/css等字段导致解析报错
   const childrenFilter = (patch, index, arr) =>
-    isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
+    isFinal || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
   const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch)
 
   return validJsonPatches
 }
 
-const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => {
+const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinal: boolean = false) => {

And update all other references to isFinial within _updatePageSchema (lines 110, 115, 122, 125, 133, 145).

Also applies to: 98-98

packages/plugins/robot/src/Main.vue (1)

178-197: Add user notification for upload failures.

The catch block (lines 192-196) only logs to console. Users receive no visible feedback when upload fails, potentially causing confusion.

Apply this diff:

+import { TinyPopover, Notify } from '@opentiny/vue'
-import { TinyPopover } from '@opentiny/vue'

And update the catch block:

   } catch (error) {
     // eslint-disable-next-line no-console
     console.error('上传失败', error)
     updateAttachment('')
+    Notify({
+      type: 'error',
+      message: '文件上传失败,请重试',
+      position: 'top-right',
+      duration: 5000
+    })
   }
 }
packages/plugins/robot/src/components/chat/RobotChat.vue (2)

163-218: Fix type inconsistency and property access in file upload.

Two issues:

  1. Type mismatch: The function signature (line 163) accepts FileList | null, but when retry is true (line 217), a single File is passed via file.file. This creates type inconsistency.

  2. Wrong property: Line 217 accesses file.file, but line 185 shows the file is stored as rawFile, not file.

Apply this diff:

-const handleSingleFilesSelected = (files: FileList | null, retry = false) => {
+const handleSingleFilesSelected = (files: FileList | File | null, retry = false) => {
   if (retry) {
     singleAttachmentItems.value[0].status = 'uploading'
     singleAttachmentItems.value[0].isUploading = true
     singleAttachmentItems.value[0].messageType = 'uploading'
   } else {
-    if (!files.length) return
+    if (!files || (files instanceof FileList && !files.length)) return
 
-    if (files && files.length > 1) {
+    if (files instanceof FileList && files.length > 1) {
       Notify({
         type: 'error',
         message: '当前仅支持上传一张图片',
         position: 'top-right',
         duration: 5000
       })
       return
     }
 
-    if (files && files.length > 0) {
+    if (files instanceof FileList && files.length > 0) {
       // 将选中的文件转换为 Attachment 格式并添加到附件列表
       const newAttachments = Array.from(files).map((file) => ({

And fix the retry handler:

 const handleSingleFileRetry = (file: any) => {
-  handleSingleFilesSelected(file.file, true)
+  handleSingleFilesSelected(file.rawFile, true)
 }

345-348: Add bounds check to prevent runtime error.

Line 347 uses a non-null assertion (!) when accessing the last message. If messages.value is empty when the user cancels, this will throw a runtime error.

Apply this diff:

 const handleAbortRequest = () => {
   abortRequest()
-  messages.value.at(-1)!.aborted = true
+  const lastMessage = messages.value.at(-1)
+  if (lastMessage) {
+    lastMessage.aborted = true
+  }
 }
🧹 Nitpick comments (13)
packages/plugins/robot/src/prompts/data/components.json (1)

2-9: Box component demo uses generic div structure.

The Box demo shows "componentName": "div" instead of demonstrating the Box component itself. While this may be intentional (container components often don't have complex internal structure), consider whether a more representative example would better serve AI context generation. For consistency with other container patterns, you might show a Box with child components.

packages/plugins/robot/src/prompts/templates/agent-prompt.md (2)

45-86: Markdown list indentation needs correction (MD007).

The static analysis flagged 22 instances of improper unordered list indentation throughout the constraint rules section. While the content is clear and correct, standardized markdown formatting improves readability and maintainability.

Consider using a consistent indentation level (typically 2 spaces per level) across all nested lists. This will also resolve the MD007 linting errors.


93-133: Code blocks should specify language identifiers (MD040).

The error example code blocks (lines 93, 98, 103, 110, 120, 125, 130) lack language specifiers. Adding ```javascript or ```json will enable proper syntax highlighting and resolve MD040 linting violations.

packages/plugins/robot/src/prompts/data/examples.json (1)

1-146: Example structure is well-formed but mixes styling approaches.

The chatMessageList example demonstrates good JSON Patch structure with proper event binding, two-way binding via model: true, and string concatenation (avoiding template literals per spec). However, the example uses both inline styles (line 64: "style": "display: inline-block;") and className bindings to CSS classes defined in the /css section (lines 42, 49, 88, 128).

Per the TinyEngine best practice (from PR 1011), components should use inline styles instead of CSS classes to avoid losing styles when dragged in the canvas designer. Consider refactoring this example to prioritize inline styles over className bindings for maximum compatibility.

Replace className-based styling with inline styles. For example, line 42-43:

- "className": "component-base-style div-uhqto",
- "alignItems": "flex-start"
+ "style": "margin: 8px; display: flex; flex-direction: column; align-items: flex-start;"

This ensures the example aligns with inline-style best practices and improves component portability within the designer.

packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue (1)

213-228: Consider auto-selecting the first compact model on baseUrl change.

When the base URL changes, completeModel is cleared (line 220) but not automatically set, unlike model which defaults to the first available model (line 219). For better UX, consider auto-selecting the first compact model if available.

Apply this enhancement:

  state.existFormData.label = provider?.label || ''
  state.existFormData.model = models[0]?.name || ''
- state.existFormData.completeModel = ''
+ const compactModels = provider?.models.filter(isCompactModel).map(modelTransformer) || []
+ state.existFormData.completeModel = compactModels[0]?.value || ''
packages/plugins/robot/src/components/renderers/ImgRenderer.vue (1)

22-25: Consider making the width configurable.

The fixed width of 200px may not suit all use cases. Consider exposing a width prop to allow parent components to customize the size.

+const { content, width } = defineProps({
-const { content } = defineProps({
   content: {
     type: String,
     default: ''
+  },
+  width: {
+    type: String,
+    default: '200px'
   }
 })
 .img-renderer-container {
-  width: 200px;
+  width: v-bind(width);
   height: auto;
 }
packages/plugins/robot/src/constants/model-config.ts (2)

15-15: Consider making base URLs configurable.

The base URLs for providers are hardcoded, which reduces flexibility for different deployment environments (development, staging, production) or for users who want to use custom API endpoints.

Consider moving these to environment variables or a separate config file:

export const DEFAULT_LLM_MODELS = [
  {
    provider: 'bailian',
    label: '阿里云百炼',
    baseUrl: import.meta.env.VITE_BAILIAN_BASE_URL || 'https://dashscope.aliyuncs.com/compatible-mode/v1',
    models: [
      // ...
    ]
  },
  {
    provider: 'deepseek',
    label: 'DeepSeek',
    baseUrl: import.meta.env.VITE_DEEPSEEK_BASE_URL || 'https://api.deepseek.com/v1',
    models: [
      // ...
    ]
  }
]

Also applies to: 73-73


11-90: Align with reviewer's recommendation to enhance model configuration schema.

The current model configuration provides a good foundation but could be enhanced based on the reviewer's suggestions to include additional metadata such as:

  • Context window size
  • Max tokens
  • Default max tokens
  • Input/output pricing
  • Default model flag
  • More detailed capability descriptions

This would improve extensibility and allow the UI to display richer information to users when selecting models.

Based on coding guidelines from the PR objectives. Consider expanding the model schema:

{
  label: 'Qwen 通用模型(Plus)',
  name: 'qwen-plus',
  contextWindow: 32768,
  maxTokens: 8192,
  defaultMaxTokens: 2000,
  inputPrice: 0.0004,  // per 1k tokens
  outputPrice: 0.0012,
  isDefault: true,
  description: 'Qwen Plus is a general-purpose model with strong reasoning capabilities',
  capabilities: {
    toolCalling: true,
    reasoning: reasoningExtraBody
  }
}
packages/plugins/robot/src/components/chat/FooterButton.vue (2)

78-113: Remove unused CSS classes or document their purpose.

The .plugin-common and .plugin-active classes are defined but not referenced in the template. If these are intended for future use or for consumers of this component, consider documenting this. Otherwise, remove them to reduce code bloat.

-  .plugin-common {
-    &_text {
-      font-size: 12px;
-      font-weight: 400;
-      line-height: 20px;
-      letter-spacing: 0;
-      text-align: left;
-    }
-
-    &_icon {
-      font-size: 16px;
-    }
-  }
-
-  .plugin-active {
-    &_count {
-      width: 12px;
-      height: 12px;
-      background: #1476ff;
-      // border-radius: 100%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-
-      font-size: 9px;
-      font-weight: 500;
-      line-height: 12px;
-      color: #fff;
-    }
-
-    &:hover {
-      color: #1476ff;
-      background-color: #eaf0f8;
-      border: 1px solid #1476ff;
-    }
-  }

97-97: Remove commented-out code.

Line 97 contains a commented-out border-radius: 100%. Either apply the style or remove the comment.

       width: 12px;
       height: 12px;
       background: #1476ff;
-      // border-radius: 100%;
       display: flex;
packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1)

95-101: Fixed width may cause layout issues on narrow screens.

The .build-loading-renderer-content-body has a fixed width of 160px, which might be too wide for very narrow screens or too narrow for wider layouts. Consider using max-width instead or making it responsive.

   &-body {
     color: var(--te-chat-model-helper-text);
     font-size: 12px;
-    width: 160px;
+    max-width: 160px;
     height: 30px;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
   }
packages/plugins/robot/src/utils/chat.utils.ts (2)

19-29: Enhance error serialization to capture stack traces.

The current error serialization only captures name and message, losing valuable debugging information like stack traces.

 export const serializeError = (err: unknown): string => {
   if (err instanceof Error) {
-    return JSON.stringify({ name: err.name, message: err.message })
+    return JSON.stringify({ 
+      name: err.name, 
+      message: err.message,
+      stack: err.stack 
+    })
   }
   if (typeof err === 'string') return err
   try {
     return JSON.stringify(err)
   } catch {
     return String(err)
   }
 }

58-74: Consider making hardcoded values configurable.

The function has several hardcoded values:

  • Default model: 'deepseek-chat' (line 61)
  • Stream flag: false (line 62)
  • API endpoint: '/app-center/api/chat/completions' (line 68)

While some are provided via options, the defaults reduce flexibility. Consider making these configurable via a config file or environment variables, especially for the API endpoint.

-export const fetchLLM = async (messages: LLMMessage[], tools: RequestTool[], options: RequestOptions = {}) => {
+export const fetchLLM = async (
+  messages: LLMMessage[], 
+  tools: RequestTool[], 
+  options: RequestOptions & { stream?: boolean } = {}
+) => {
   const bodyObj: LLMRequestBody = {
     baseUrl: options.baseUrl,
     model: options?.model || 'deepseek-chat',
-    stream: false,
+    stream: options.stream ?? false,
     messages: toRaw(messages)
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce65137 and 2bcbd32.

📒 Files selected for processing (29)
  • packages/plugins/robot/index.ts (1 hunks)
  • packages/plugins/robot/src/Main.vue (2 hunks)
  • packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1 hunks)
  • packages/plugins/robot/src/components/chat/FooterButton.vue (1 hunks)
  • packages/plugins/robot/src/components/chat/RobotChat.vue (1 hunks)
  • packages/plugins/robot/src/components/footer-extension/McpServer.vue (1 hunks)
  • packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue (3 hunks)
  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue (11 hunks)
  • packages/plugins/robot/src/components/icons/mcp-icon.vue (0 hunks)
  • packages/plugins/robot/src/components/icons/page-icon.vue (0 hunks)
  • packages/plugins/robot/src/components/icons/study-icon.vue (0 hunks)
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1 hunks)
  • packages/plugins/robot/src/components/renderers/ImgRenderer.vue (1 hunks)
  • packages/plugins/robot/src/components/renderers/LoadingRenderer.vue (1 hunks)
  • packages/plugins/robot/src/composables/useAgent.ts (1 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/composables/useConfig.ts (1 hunks)
  • packages/plugins/robot/src/constants/index.ts (1 hunks)
  • packages/plugins/robot/src/constants/model-config.ts (1 hunks)
  • packages/plugins/robot/src/metas/index.ts (1 hunks)
  • packages/plugins/robot/src/prompts/data/components.json (1 hunks)
  • packages/plugins/robot/src/prompts/data/examples.json (1 hunks)
  • packages/plugins/robot/src/prompts/index.ts (1 hunks)
  • packages/plugins/robot/src/prompts/templates/agent-prompt.md (1 hunks)
  • packages/plugins/robot/src/types/chat.types.ts (3 hunks)
  • packages/plugins/robot/src/types/index.ts (1 hunks)
  • packages/plugins/robot/src/types/mcp.types.ts (1 hunks)
  • packages/plugins/robot/src/utils/chat.utils.ts (1 hunks)
  • packages/plugins/robot/src/utils/index.ts (1 hunks)
💤 Files with no reviewable changes (3)
  • packages/plugins/robot/src/components/icons/mcp-icon.vue
  • packages/plugins/robot/src/components/icons/study-icon.vue
  • packages/plugins/robot/src/components/icons/page-icon.vue
✅ Files skipped from review due to trivial changes (1)
  • packages/plugins/robot/src/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/plugins/robot/src/prompts/index.ts
  • packages/plugins/robot/src/composables/useChat.ts
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-01-14T04:22:02.404Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/builtin/builtin.json:645-850
Timestamp: 2025-01-14T04:22:02.404Z
Learning: In TinyEngine, components must use inline styles instead of CSS classes because components cannot carry class styles when dragged into the canvas.

Applied to files:

  • packages/plugins/robot/src/prompts/data/components.json
📚 Learning: 2025-07-03T09:22:59.512Z
Learnt from: hexqi
Repo: opentiny/tiny-engine PR: 1501
File: mockServer/src/tool/Common.js:79-82
Timestamp: 2025-07-03T09:22:59.512Z
Learning: In the tiny-engine project, the mockServer code uses ES6 import syntax but is compiled to CommonJS output. This means CommonJS globals like `__dirname` are available at runtime, while ES6 module-specific features like `import.meta` would cause runtime errors.

Applied to files:

  • packages/plugins/robot/index.ts
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.

Applied to files:

  • packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue
  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue
  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-15T02:19:06.755Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 940
File: packages/canvas/DesignCanvas/src/DesignCanvas.vue:0-0
Timestamp: 2025-01-15T02:19:06.755Z
Learning: In Vue components using message subscriptions from opentiny/tiny-engine-meta-register, always clean up subscriptions in the onUnmounted hook using useMessage().unsubscribe() to prevent memory leaks.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。

Applied to files:

  • packages/plugins/robot/src/Main.vue
🧬 Code graph analysis (5)
packages/plugins/robot/src/utils/chat.utils.ts (3)
packages/plugins/robot/src/types/chat.types.ts (4)
  • LLMMessage (32-36)
  • RequestTool (11-30)
  • RequestOptions (4-9)
  • LLMRequestBody (45-51)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/composables/useAgent.ts (4)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/register/src/hooks.ts (1)
  • useHistory (82-82)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/composables/useConfig.ts (2)
packages/register/src/common.ts (1)
  • getOptions (32-34)
packages/plugins/robot/src/constants/model-config.ts (1)
  • DEFAULT_LLM_MODELS (11-90)
packages/plugins/robot/src/types/chat.types.ts (1)
packages/plugins/robot/src/types/mcp.types.ts (1)
  • ResponseToolCall (1-7)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • formatMessages (7-17)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/prompts/templates/agent-prompt.md

46-46: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


47-47: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


48-48: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


49-49: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


50-50: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


51-51: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


52-52: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


53-53: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


54-54: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


55-55: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


56-56: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


57-57: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


58-58: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


59-59: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


60-60: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


61-61: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


62-62: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


63-63: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


64-64: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


65-65: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


66-66: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


67-67: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


68-68: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


69-69: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


70-70: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


71-71: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


72-72: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


73-73: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


74-74: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


75-75: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


76-76: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


77-77: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


78-78: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


79-79: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


80-80: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


81-81: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


82-82: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


83-83: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


84-84: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


85-85: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


86-86: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


93-93: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


98-98: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


103-103: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


120-120: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


130-130: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (23)
packages/plugins/robot/src/prompts/data/components.json (1)

1-997: Component catalog is well-structured and comprehensive.

The catalog provides solid reference data for AI prompt generation with consistent metadata structure (component name, properties, events, and realistic demos). All samples correctly use inline styles per TinyEngine best practices.

packages/plugins/robot/src/prompts/data/examples.json (1)

125-145: Methods are well-structured with proper string handling.

The sendMessage and onClickMessage methods correctly use string concatenation instead of template literals (per spec), and event parameter passing is correct. The example effectively demonstrates method definition and event binding patterns.

packages/plugins/robot/src/prompts/templates/agent-prompt.md (1)

1-622: System prompt template is comprehensive and correctly integrated with placeholder replacement logic.

The specification provides clear guidance for RFC 6902 JSON Patch generation with strict validation rules, good error examples, and complete PageSchema documentation. Verification confirms that all placeholders ({{COMPONENTS_LIST}}, {{EXAMPLES_SECTION}}, {{CURRENT_PAGE_SCHEMA}}, {{REFERENCE_KNOWLEDGE}}, {{IMAGE_ASSETS}}) in the template are correctly replaced by the generator in packages/plugins/robot/src/prompts/index.ts (lines 45-49).

packages/plugins/robot/src/components/renderers/LoadingRenderer.vue (1)

1-3: LGTM! Simple and functional loading indicator.

The template-only component is straightforward and serves its purpose well. The hardcoded dimensions and relative asset path are acceptable for this use case.

packages/plugins/robot/src/types/mcp.types.ts (1)

1-38: LGTM! Well-structured type definitions for MCP interactions.

The centralized type definitions appropriately separate MCP-related concerns from chat types. The flexible index signatures ([prop: string]: unknown) provide good extensibility while maintaining type safety for known fields.

packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (4)

51-81: Constructor validation is well-implemented.

The constructor properly validates that axiosClient is provided when httpClientType is set to 'axios', preventing runtime errors. The initialization logic with sensible defaults and the extensibility via the beforeRequest hook are good design choices.


132-162: LGTM! Header construction and request preparation are solid.

The buildHeaders method appropriately sets headers based on streaming mode and API key presence. The prepareRequestData method correctly applies message formatting, model selection precedence, and the beforeRequest hook with proper async handling.


168-224: Well-designed fetch adapter for axios compatibility.

The createFetchAdapter method effectively bridges fetch and axios interfaces with proper URL resolution, response structure mapping, and error handling. The JSON parsing error recovery is particularly good.


282-335: Chat methods handle streaming and non-streaming flows correctly.

Both chat and chatStream methods properly branch on httpClientType, handle errors appropriately, and the streaming method correctly honors abort signals by silently returning when aborted (lines 330-332).

packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue (2)

182-199: Well-structured computed properties for dynamic model options.

The computed properties effectively derive model options from the selected provider, with clear separation between regular and compact models. The helper functions (modelTransformer, isCompactModel, getProviderByBaseUrl) improve readability.


48-53: Good security improvement with password input type.

Changing the API key inputs to type="password" prevents accidental exposure of sensitive credentials during screen sharing or over-the-shoulder viewing.

Also applies to: 100-105

packages/plugins/robot/index.ts (1)

17-17: LGTM! Import path updated to match refactored structure.

The updated import path correctly references the new location of RobotService in the refactored codebase structure.

packages/plugins/robot/src/constants/index.ts (1)

1-1: LGTM! Standard barrel export for constants.

The barrel export provides centralized access to model configuration constants, following common JavaScript/TypeScript patterns.

packages/plugins/robot/src/utils/index.ts (1)

1-1: LGTM! Barrel export for chat utilities.

The export provides centralized access to chat-related utilities, aligning with the refactored architecture.

packages/plugins/robot/src/types/chat.types.ts (3)

2-2: Good type organization improvements.

Importing ResponseToolCall from the dedicated mcp.types module improves type organization. The addition of baseUrl to RequestOptions provides necessary flexibility for multi-provider scenarios.

Also applies to: 8-8


40-40: Enhanced content type enables rich message rendering.

The union type string | BubbleContentItem[] maintains backward compatibility while enabling structured content rendering, aligning with the enhanced chat UI capabilities in this refactor.


58-58: Type reference correctly updated.

The tool_calls type now references ResponseToolCall from mcp.types, fixing the previous typo ("ReponseToolCall") and aligning with the centralized MCP type definitions.

packages/plugins/robot/src/components/footer-extension/McpServer.vue (1)

3-32: LGTM! Clean refactoring to use FooterButton component.

The refactoring successfully extracts the button UI into a reusable FooterButton component while maintaining the existing functionality. The active state is correctly derived from activeCount > 0, and the visibility toggle logic is properly wired.

packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1)

33-54: Verify safe handling of content property in statusDataMap.

Lines 37 and 42 use props.content?.slice(-30), which correctly uses optional chaining. However, if content is an empty string, slice(-30) returns an empty string, which may not provide useful feedback to users during the reasoning/loading states.

Consider adding a fallback message:

 reasoning: {
   title: '深度思考中,请稍等片刻',
   icon: 'loading.webp',
-  content: () => props.content?.slice(-30)
+  content: () => props.content?.slice(-30) || '正在处理中...'
 },
 loading: {
   title: '页面生成中,请稍等片刻',
   icon: 'loading.webp',
-  content: () => props.content?.slice(-30)
+  content: () => props.content?.slice(-30) || '正在生成页面...'
 },
packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue (2)

62-68: LGTM! Clean computed pattern for v-model binding.

The computed getter/setter pattern correctly implements two-way binding with the parent component. The guard on line 65 prevents unnecessary event emissions when the value hasn't actually changed.


55-58: No breaking change detected in this codebase.

Verification found:

  • Zero usages of the old aiType or ai-type prop anywhere in the codebase
  • RobotTypeSelect has only one consumer: Main.vue (line 41)
  • The consumer already uses the new chatMode prop correctly: :chatMode="robotSettingState.chatMode"
  • All related state management code already references chatMode, not the old prop name

Since no code in the repository uses the old prop name, this is not a breaking change within this codebase. The concern raised in the review comment does not apply to the actual state of the code.

Likely an incorrect or invalid review comment.

packages/plugins/robot/src/utils/chat.utils.ts (1)

7-17: LGTM! Correct message formatting with reactive proxy unwrapping.

The formatMessages function correctly:

  • Uses toRaw() to unwrap Vue reactive proxies before sending to external APIs
  • Filters out invalid messages (those without content, tool_calls, or tool_call_id)
  • Conditionally includes optional fields to keep the payload clean
packages/plugins/robot/src/metas/index.ts (1)

7-10: Review comment is incorrect. The code does not have reactivity issues.

The useModelConfig() composable implements a module-scope singleton pattern via Vue's reactive() and closure:

  • robotSettingState is created once at module load using reactive() (not re-created on each call)
  • Each call to useModelConfig() returns a wrapper object referencing the same underlying reactive instance via closure
  • Calling useModelConfig() twice at module scope (lines 8-9) accesses the same reactive state both times—no separate instances are created

Vue's composables can call reactive() at module scope; this is a valid singleton pattern used throughout the codebase. The current code is functionally correct.

Minor note: There is a minor inefficiency (creating two wrapper objects instead of one), but this is not a correctness or reactivity concern.

Likely an incorrect or invalid review comment.

Comment on lines +2 to +8
<tiny-image
v-if="content"
class="img-renderer-container"
:src="content"
:preview-src-list="[content]"
fit="cover"
></tiny-image>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add alt attribute for accessibility.

The <tiny-image> component lacks an alt attribute, which is essential for screen readers and accessibility compliance. Consider adding a descriptive alt text or allowing it to be passed as a prop.

Apply this diff to add alt text support:

 const { content } = defineProps({
   content: {
     type: String,
     default: ''
+  },
+  alt: {
+    type: String,
+    default: 'Image preview'
   }
 })

Then update the template:

   <tiny-image
     v-if="content"
     class="img-renderer-container"
     :src="content"
     :preview-src-list="[content]"
     fit="cover"
+    :alt="alt"
   ></tiny-image>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/plugins/robot/src/components/renderers/ImgRenderer.vue around lines
2 to 8, the tiny-image tag is missing an alt attribute which hurts
accessibility; add an alt prop to this component (prop name: alt, type: String,
default: ''), allow parent components to pass alt through, and bind it to the
tiny-image element as :alt="alt" (or use a computed/descriptive fallback if
needed) so the rendered <tiny-image> always receives an alt string.

Comment on lines +131 to +142
const robotSettingState = reactive({
selectedModel: {
label: storageSettingState.label || getAIModelOptions()[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 || '',
apiKey: storageSettingState.apiKey || ''
},
chatMode: chatMode || CHAT_MODE.Agent,
enableThinking: enableThinking || false
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix incorrect property access in state initialization.

Lines 135-137 reference a .value property that doesn't exist in the DEFAULT_LLM_MODELS structure. According to the structure shown in constants/model-config.ts (lines 10-89), providers use baseUrl and models use name, not value. This will cause the default values to be undefined, breaking the application.

Apply this diff:

 const robotSettingState = reactive({
   selectedModel: {
     label: storageSettingState.label || getAIModelOptions()[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 || getAIModelOptions()[0].baseUrl,
+    model: storageSettingState.model || getAIModelOptions()[0].models[0].name,
+    completeModel: storageSettingState.completeModel || getAIModelOptions()[0].models[0].name || '',
     apiKey: storageSettingState.apiKey || ''
   },
   chatMode: chatMode || CHAT_MODE.Agent,
   enableThinking: enableThinking || false
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const robotSettingState = reactive({
selectedModel: {
label: storageSettingState.label || getAIModelOptions()[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 || '',
apiKey: storageSettingState.apiKey || ''
},
chatMode: chatMode || CHAT_MODE.Agent,
enableThinking: enableThinking || false
})
const robotSettingState = reactive({
selectedModel: {
label: storageSettingState.label || getAIModelOptions()[0].label,
activeName: activeName || EXISTING_MODELS,
baseUrl: storageSettingState.baseUrl || getAIModelOptions()[0].baseUrl,
model: storageSettingState.model || getAIModelOptions()[0].models[0].name,
completeModel: storageSettingState.completeModel || getAIModelOptions()[0].models[0].name || '',
apiKey: storageSettingState.apiKey || ''
},
chatMode: chatMode || CHAT_MODE.Agent,
enableThinking: enableThinking || false
})
🤖 Prompt for AI Agents
In packages/plugins/robot/src/composables/useConfig.ts around lines 131 to 142,
the state initialization incorrectly reads .value from the model options; change
those accesses to use the actual property names from DEFAULT_LLM_MODELS: use
baseUrl instead of .value for the provider URL, and use name instead of .value
for model and completeModel defaults (e.g. getAIModelOptions()[0].baseUrl and
getAIModelOptions()[0].models[0].name). Ensure completeModel falls back to an
empty string if no name exists and keep other fallbacks (label, apiKey,
chatMode, enableThinking) unchanged.

Comment on lines +1 to +9
const reasoningExtraBody = {
extraBody: {
enable: {
enable_thinking: true,
thinking_budget: 1000
},
disable: null
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Export reasoningExtraBody as indicated in the AI summary.

The constant reasoningExtraBody is used internally but the AI summary indicates it should be exported. Currently it's not exported, which limits reusability.

Apply this diff:

-const reasoningExtraBody = {
+export const reasoningExtraBody = {
   extraBody: {
     enable: {
       enable_thinking: true,
       thinking_budget: 1000
     },
     disable: null
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const reasoningExtraBody = {
extraBody: {
enable: {
enable_thinking: true,
thinking_budget: 1000
},
disable: null
}
}
export const reasoningExtraBody = {
extraBody: {
enable: {
enable_thinking: true,
thinking_budget: 1000
},
disable: null
}
}
🤖 Prompt for AI Agents
In packages/plugins/robot/src/constants/model-config.ts around lines 1 to 9, the
constant reasoningExtraBody is currently defined but not exported; update the
file to export it (e.g., change the declaration to a named export like export
const reasoningExtraBody = { ... }) so other modules can import and reuse it,
and keep the existing object structure and property names unchanged.

Comment on lines +6 to +146
"patch": [
{
"op": "add",
"path": "/state/messages",
"value": [
{
"content": "hello"
}
]
},
{
"op": "add",
"path": "/state/inputMessage",
"value": ""
},
{
"op": "add",
"path": "/children/0",
"value": {
"componentName": "div",
"id": "25153243",
"props": {
"className": "component-base-style"
},
"children": [
{
"componentName": "h1",
"props": {
"className": "component-base-style"
},
"children": "消息列表",
"id": "53222591"
},
{
"componentName": "div",
"props": {
"className": "component-base-style div-uhqto",
"alignItems": "flex-start"
},
"children": [
{
"componentName": "div",
"props": {
"className": "component-base-style div-vinko",
"onClick": {
"type": "JSExpression",
"value": "this.onClickMessage",
"params": ["message", "index"]
},
"key": {
"type": "JSExpression",
"value": "index"
}
},
"children": [
{
"componentName": "Text",
"props": {
"style": "display: inline-block;",
"text": {
"type": "JSExpression",
"value": "message.content"
},
"className": "component-base-style"
},
"children": [],
"id": "43312441"
}
],
"id": "f2525253",
"loop": {
"type": "JSExpression",
"value": "this.state.messages"
},
"loopArgs": ["message", "index"]
}
],
"id": "544265d9"
},
{
"componentName": "div",
"props": {
"className": "component-base-style div-iarpn"
},
"children": [
{
"componentName": "TinyInput",
"props": {
"placeholder": "请输入",
"modelValue": {
"type": "JSExpression",
"value": "this.state.inputMessage",
"model": true
},
"className": "component-base-style",
"type": "textarea"
},
"children": [],
"id": "24651354"
},
{
"componentName": "TinyButton",
"props": {
"text": "发送",
"className": "component-base-style",
"onClick": {
"type": "JSExpression",
"value": "this.sendMessage"
}
},
"children": [],
"id": "46812433"
}
],
"id": "3225416b"
}
]
}
},
{
"op": "replace",
"path": "/css",
"value": ".page-base-style {\n padding: 24px;\n background: #ffffff;\n}\n.block-base-style {\n margin: 16px;\n}\n.component-base-style {\n margin: 8px;\n}\n.div-vinko {\n margin: 8px;\n border-width: 1px;\n border-color: #ebeaea;\n border-style: solid;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-radius: 50px;\n}\n.div-iarpn {\n margin: 8px;\n display: flex;\n align-items: center;\n}\n.div-uhqto {\n margin: 8px;\n display: flex;\n flex-direction: column;\n}\n"
},
{
"op": "add",
"path": "/methods/sendMessage",
"value": {
"type": "JSFunction",
"value": "function sendMessage(event) {\n this.state.messages.push({ content: this.state.inputMessage })\n this.state.inputMessage = ''\n}\n"
}
},
{
"op": "add",
"path": "/methods/onClickMessage",
"value": {
"type": "JSFunction",
"value": "function onClickMessage(event, message, index) {\n console.log('这是第' + (index + 1) + '条消息, 消息内容:' + message.content)\n}\n"
}
}
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Component IDs do not comply with specification requirements.

The specification in agent-prompt.md (line 166) requires component IDs to be "unique 8-character random IDs" that "MUST contain at least one uppercase letter, one lowercase letter, and one digit." However, all component IDs in this example fail this requirement:

  • Line 26, 37, 53, etc.: IDs like 25153243, 53222591, f2525253, etc.
  • ❌ Most are purely numeric (no letters) or lack uppercase letters

Per the spec's examples: ✅ Good: a7Kp2sN9 (has uppercase, lowercase, digit) | ❌ Bad: 1234abcd (lacks uppercase and digit).

Regenerate all component IDs to comply with the format requirement. Example:

- "id": "25153243",
+ "id": "a7Kp2sN9",

Apply this pattern to all 8 component IDs in the patch. Use a format that includes:

  1. At least one uppercase letter
  2. At least one lowercase letter
  3. At least one digit
  4. Maintain strong randomness (e.g., bR7mQx4N, sL2pKwJ5, tN9fHvY8)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
packages/plugins/robot/src/prompts/data/examples.json around lines 6-146:
several component "id" values in the patch are invalid per agent-prompt.md (they
must be unique 8-character random IDs containing at least one uppercase, one
lowercase and one digit); regenerate every component id in this patch (all 8
IDs) to a unique 8-character string that includes at least one uppercase letter,
one lowercase letter and one digit (e.g., bR7mQx4N, sL2pKwJ5, tN9fHvY8), replace
each old id with its new id everywhere it appears in the patch, and ensure no
duplicates and that any references (loop/children relationships) continue to
point to the updated ids.

Comment on lines +76 to +105
export const processSSEStream = (data: string, handler: StreamHandler) => {
let finishReason: string | undefined
let latestFinishReason: string | undefined
const lines = data.split('\n\n')
lines.pop()

for (const line of lines) {
if (line.trim() === '') continue
if (line.trim() === 'data: [DONE]') {
if (latestFinishReason) {
finishReason = latestFinishReason
}
handler.onDone(finishReason)
continue
}

try {
// 解析SSE消息
const dataMatch = line.match(/^data: (.+)$/m)
if (!dataMatch) continue

const data = JSON.parse(dataMatch[1])
handler.onData(data)
latestFinishReason = data.choices?.[0]?.finish_reason || undefined
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error parsing SSE message:', error, line)
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Verify robustness of SSE parsing logic.

The SSE parsing implementation has a few areas to consider:

  1. Line 79-80: lines.pop() removes the last element, assuming it's always empty. This assumption may not hold if the stream ends without a trailing newline.
  2. Line 94: The regex ^data: (.+)$ with the m flag may not match correctly if the data contains newlines.
  3. Line 102: Errors are only logged to console; they don't propagate or get reported to the caller.

Consider these improvements:

 export const processSSEStream = (data: string, handler: StreamHandler) => {
   let finishReason: string | undefined
   let latestFinishReason: string | undefined
   const lines = data.split('\n\n')
-  lines.pop()
+  // Only remove last element if it's empty
+  if (lines[lines.length - 1] === '') {
+    lines.pop()
+  }

   for (const line of lines) {
     if (line.trim() === '') continue
     if (line.trim() === 'data: [DONE]') {
       if (latestFinishReason) {
         finishReason = latestFinishReason
       }
       handler.onDone(finishReason)
       continue
     }

     try {
       // Parse SSE message
       const dataMatch = line.match(/^data: (.+)$/m)
       if (!dataMatch) continue

       const data = JSON.parse(dataMatch[1])
       handler.onData(data)
       latestFinishReason = data.choices?.[0]?.finish_reason || undefined
     } catch (error) {
-      // eslint-disable-next-line no-console
-      console.error('Error parsing SSE message:', error, line)
+      // Consider calling an error handler if available
+      if (handler.onError) {
+        handler.onError(error)
+      } else {
+        console.error('Error parsing SSE message:', error, line)
+      }
     }
   }
 }

Additionally, verify that the StreamHandler interface supports an optional onError callback, or add it if missing.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/plugins/robot/src/utils/chat.utils.ts around lines 76 to 105, the
SSE parsing assumes a trailing empty chunk (lines.pop()), uses a regex that
fails if the data contains newlines, and only logs parse errors to console;
update the loop to not blindly pop the last element but instead filter out empty
segments after splitting, extract the SSE payload by removing the "data: "
prefix (or use a regex that allows newlines, e.g. capturing everything after the
prefix with the DOTALL/s behavior) so multi-line JSON is parsed correctly, and
replace console.error with reporting errors to the caller via handler.onError
(add an optional onError to StreamHandler if missing) while preserving calls to
handler.onDone with the last observed finish_reason when 'data: [DONE]' is
received.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (9)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (2)

87-126: Error type detection relies on fragile string matching.

The method infers error types by searching for substrings in lowercased error messages (lines 94-111). This approach is brittle—error messages can vary across browsers and network stacks.

Consider enhancing error detection:

   private toAIAdapterError(error: unknown): AIAdapterError {
+    // Check for specific error types first
+    if (error instanceof TypeError && error.message.includes('fetch')) {
+      return { type: ErrorType.NETWORK_ERROR, message: error.message, originalError: error }
+    }
+    if (error instanceof DOMException && error.name === 'AbortError') {
+      return { type: ErrorType.TIMEOUT_ERROR, message: error.message, originalError: error }
+    }
+
     if (error instanceof Error) {
       const message = error.message.toLowerCase()

342-358: updateConfig has incomplete ProviderConfig support.

The method accepts ProviderConfig but only updates baseURL, apiKey, and defaultModel, silently ignoring httpClientType and axiosClient. This creates a type/behavior mismatch.

Consider either:

  1. Narrow the signature to exclude immutable properties:
    updateConfig(config: Omit<ProviderConfig, 'httpClientType' | 'axiosClient'>): void
  2. Or add handling for these properties with appropriate validation.
packages/plugins/robot/src/composables/useChat.ts (3)

55-99: Consider refactoring to align with provider-layer architecture.

Per the PR comments, tool_call handling and mode-specific logic (agent vs chat) should be encapsulated within the provider layer rather than in a composable's beforeRequest hook. The current implementation mixes concerns across tool integration, agent-mode prompts, and model capability checks.

Based on PR comments.

This would improve separation of concerns and make it easier to add new modes without modifying core chat logic.


55-99: Guard reasoning extraBody assignments against null.

Line 87-92: When robotSettingState.enableThinking is false, the code may pass null to Object.assign, which throws TypeError: Cannot convert undefined or null to object. This occurs for models like bailian where extraBody.disable is null.

Apply this diff:

   if (modelCapabilities?.reasoning?.extraBody) {
-    Object.assign(
-      requestParams,
-      robotSettingState.enableThinking
-        ? modelCapabilities.reasoning.extraBody.enable
-        : modelCapabilities.reasoning.extraBody.disable
-    )
+    const extraBody = robotSettingState.enableThinking
+      ? modelCapabilities.reasoning.extraBody.enable
+      : modelCapabilities.reasoning.extraBody.disable
+    if (extraBody) {
+      Object.assign(requestParams, extraBody)
+    }
   }

147-203: Fix await updatePageSchema(...) runtime crash.

Line 190 awaits updatePageSchema, but updatePageSchema is the throttled wrapper from useAgent.ts (line 193) returned by useThrottleFn, which returns void, not a Promise. This will cause incorrect behavior when the agent reaches finalization.

Consider exporting an unthrottled helper from useAgent.ts for final updates:

In packages/plugins/robot/src/composables/useAgent.ts:

export const updatePageSchemaImmediate = _updatePageSchema
export const updatePageSchema = useThrottleFn(_updatePageSchema, 200, true)

Then in useChat.ts:

+import { updatePageSchema, updatePageSchemaImmediate } from './useAgent'

...

-        const result = await updatePageSchema(lastMessage.content, pageSchema, true)
+        const result = updatePageSchemaImmediate(lastMessage.content, pageSchema, true)
packages/plugins/robot/src/composables/useAgent.ts (3)

108-115: Fix typo: isFinialisFinal.

The parameter name isFinial appears to be a typo and should be isFinal for clarity.

Apply this diff:

-const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => {
+const jsonPatchAutoFix = (jsonPatches: any[], isFinal: boolean) => {
   // 流式渲染过程中,画布只渲染完整的字段或流式的children字段,避免不完整的methods/states/css等字段导致解析报错
   const childrenFilter = (patch, index, arr) =>
-    isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
+    isFinal || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
   const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch)

140-191: Fix typo: isFinialisFinal throughout this function.

The parameter and variable name isFinial (line 140 and throughout) should be isFinal for consistency.

Apply this diff:

-const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => {
+const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinal: boolean = false) => {
   const { robotSettingState, CHAT_MODE } = useModelConfig()
   if (robotSettingState.chatMode !== CHAT_MODE.Agent) {
     return
   }

   // 解析流式返回的schema patch
   let content = getJsonObjectString(streamContent)
   let jsonPatches = []
   try {
-    if (!isFinial) {
+    if (!isFinal) {
       content = jsonrepair(content)
     }
     jsonPatches = JSON.parse(content)
   } catch (error) {
-    if (isFinial) {
+    if (isFinal) {
       logger.error('parse json patch error:', error)
     }
     return { isError: true, error }
   }

   // 过滤有效的json patch
-  if (!isFinial && !isValidFastJsonPatch(jsonPatches)) {
+  if (!isFinal && !isValidFastJsonPatch(jsonPatches)) {
     return { isError: true, error: 'format error: not a valid json patch.' }
   }
-  const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinial)
+  const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinal)

   // 生成新schema
   const originSchema = deepClone(currentPageSchema)
   const newSchema = validJsonPatches.reduce((acc: object, patch: any) => {
     try {
       return jsonpatch.applyPatch(acc, [patch], false, false).newDocument
     } catch (error) {
-      if (isFinial) {
+      if (isFinal) {
         logger.error('apply patch error:', error, patch)
       }
       return acc
     }
   }, originSchema)

   // schema纠错
   fixMethods(newSchema.methods)
   schemaAutoFix(newSchema.children)

   // 更新Schema
   setSchema(newSchema)
-  if (isFinial) {
+  if (isFinal) {
     useHistory().addHistory()
   }

   return { schema: newSchema, isError: false }
 }

195-211: Silent error handling may hide issues.

The empty catch block (lines 207-209) suppresses all search errors. Consider logging errors to aid debugging.

Apply this diff:

   } catch (error) {
-    // error
+    logger.error('Search failed:', error)
   }
packages/plugins/robot/src/prompts/index.ts (1)

16-25: Escape braces when injecting JSON into Markdown.

Line 21 stringifies the patch array without escaping braces, so raw {/} pairs in the template can be interpreted as Markdown syntax, potentially breaking prompt formatting.

Apply this diff:

 const formatExamples = (examples: Record<string, any>): string => {
   return Object.entries(examples)
     .map(([_key, example]) => {
       const { name, description, note, patch } = example
       const header = `### ${name}\n${description ? `${description}\n` : ''}${note ? `**Note**: ${note}\n` : ''}`
-      const patchContent = JSON.stringify(patch)
-      return `${header}\n${patchContent}`
+      const patchContent = '```json\n' + JSON.stringify(patch, null, 2) + '\n```'
+      return `${header}\n\n${patchContent}`
     })
     .join('\n\n')
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2bcbd32 and eee31c8.

📒 Files selected for processing (5)
  • packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1 hunks)
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1 hunks)
  • packages/plugins/robot/src/composables/useAgent.ts (1 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/prompts/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.

Applied to files:

  • packages/plugins/robot/src/composables/useChat.ts
📚 Learning: 2025-01-14T06:50:21.158Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/data-function/parser.ts:191-195
Timestamp: 2025-01-14T06:50:21.158Z
Learning: The `newFn` function in `packages/canvas/render/src/data-function/parser.ts` has a known security issue with evaluating untrusted code through `data.value`. This was identified during code review but intentionally deferred as the original implementation was kept during code refactoring.

Applied to files:

  • packages/plugins/robot/src/composables/useChat.ts
🧬 Code graph analysis (3)
packages/plugins/robot/src/composables/useAgent.ts (5)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • serializeError (19-29)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/register/src/hooks.ts (1)
  • useHistory (82-82)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • formatMessages (7-17)
packages/plugins/robot/src/composables/useChat.ts (9)
packages/plugins/robot/src/types/mcp.types.ts (1)
  • ResponseToolCall (1-7)
packages/register/src/common.ts (2)
  • getMetaApi (20-30)
  • getOptions (32-34)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/types/chat.types.ts (2)
  • LLMMessage (32-36)
  • RobotMessage (38-43)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/plugins/robot/src/composables/useMcp.ts (1)
  • useMcpServer (126-137)
packages/plugins/robot/src/composables/useAgent.ts (4)
  • search (195-211)
  • fetchAssets (213-225)
  • updatePageSchema (193-193)
  • isValidJsonPatchObjectString (123-138)
packages/plugins/robot/src/prompts/index.ts (2)
  • getAgentSystemPrompt (30-52)
  • getJsonFixPrompt (56-87)
packages/plugins/robot/src/utils/chat.utils.ts (3)
  • mergeStringFields (37-56)
  • formatMessages (7-17)
  • serializeError (19-29)
🔇 Additional comments (38)
packages/plugins/robot/src/composables/useAgent.ts (9)

1-12: LGTM!

The imports and initial setup are appropriate. Using console as the logger is acceptable for this context.


14-18: LGTM!

The setSchema function correctly updates the canvas schema and marks it as unsaved.


20-25: LGTM!

The icon fallback logic appropriately handles missing icons by defaulting to IconWarning.


30-35: LGTM!

The function correctly avoids fixing patch operation objects by checking for op and path properties.


55-63: LGTM!

The recursive schema auto-fix logic correctly handles arrays and nested children.


65-97: LGTM! Note non-standard _get operation.

The validation logic is comprehensive and correct. The _get operation (line 66) is a non-standard extension to RFC 6902 but appears to be intentionally supported.


99-106: LGTM!

The patch validation correctly handles both single operations and arrays.


193-193: LGTM!

The 200ms throttle with leading edge is appropriate for streaming schema updates.


213-225: LGTM!

The function appropriately returns an empty array on error, which is acceptable for this non-critical helper.

packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (9)

1-37: LGTM!

The type definitions and imports are well-structured and appropriate for an OpenAI-compatible provider implementation.


39-81: LGTM!

The constructor properly validates configuration, especially the axios client requirement when httpClientType is set to 'axios'.


132-146: LGTM!

The header construction logic correctly handles streaming and authentication scenarios.


152-163: LGTM!

The request preparation logic correctly applies the model selection waterfall and beforeRequest hook.


169-225: LGTM!

The fetch adapter correctly handles URL construction, streaming responses, and JSON parsing with appropriate error handling.


231-249: LGTM!

The fetch request implementation properly handles errors and includes helpful error details.


255-276: LGTM!

The axios request handling correctly validates client presence and handles both function and instance forms of axiosClient.


283-302: LGTM!

The non-streaming chat method correctly branches on HTTP client type and handles responses appropriately.


309-336: LGTM!

The streaming implementation correctly handles both transport types, abort signals, and error mapping.

packages/plugins/robot/src/prompts/index.ts (4)

1-4: LGTM!

The imports appropriately use raw string imports for Markdown templates and JSON data.


9-11: LGTM!

The function correctly wraps the JSONL output in a fenced code block, preventing Markdown interpretation issues.


30-52: LGTM!

The prompt generation logic correctly filters components, formats sections, and replaces placeholders. The TinyNumeric exclusion (line 32) is appropriately documented.


54-87: LGTM!

Both functions provide clear, comprehensive prompts. The JSON fix prompt includes detailed format requirements and examples.

packages/plugins/robot/src/components/renderers/AgentRenderer.vue (4)

1-9: LGTM!

The template cleanly renders status information with appropriate conditional rendering.


11-27: LGTM!

The props are well-typed with appropriate defaults.


28-80: LGTM!

The setup logic appropriately handles different status states and dynamically resolves content. The reasoning completion logic (line 61) correctly determines visibility.


84-109: LGTM!

The styles appropriately handle layout and text overflow scenarios.

packages/plugins/robot/src/composables/useChat.ts (12)

1-34: LGTM!

The imports and initial setup are comprehensive and appropriate for the chat composable.


36-45: LGTM!

The configuration object is well-structured and appropriately uses the meta API for HTTP client access.


47-53: LGTM!

The function correctly handles both adding new system prompts and updating existing ones.


101-101: LGTM!

The client creation appropriately passes config and beforeRequest hook.


103-106: LGTM!

The config update function correctly updates both provider and local configuration.


108-120: LGTM!

The loading removal logic correctly handles both specific and latest loading indicators.


205-207: LGTM!

The conversation manager initialization correctly passes the client and event handlers.


209-254: LGTM!

The delta handlers correctly process reasoning, content, and tool_calls, with appropriate incremental merging logic for tool call chunks.


256-262: LGTM!

The argument parsing appropriately falls back to raw string on JSON parse errors.


264-359: LGTM!

The tool call orchestration correctly handles execution, error states, abort signals, and recursive tool call chains.


361-373: LGTM!

The chat mode change logic correctly updates metadata, persists state, and adjusts the API URL.


375-407: LGTM!

The exported API comprehensively exposes chat functionality with appropriate abort handling and conversation mode restoration.

Comment on lines +37 to +53
const fixMethods = (methods: Record<string, any>) => {
if (methods && Object.keys(methods).length) {
Object.entries(methods).forEach(([methodName, methodValue]: [string, any]) => {
if (
typeof methodValue !== 'object' ||
methodValue?.type !== 'JSFunction' ||
!methodValue?.value.startsWith('function')
) {
methods[methodName] = {
type: 'JSFunction',
value: 'function ' + methodName + '() {\n console.log("' + methodName + '");\n}'
}
logger.log('autofix method to empty function:', methodName, methods[methodName])
}
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against non-string value before calling startsWith.

Line 43 calls .startsWith('function') on methodValue?.value without verifying it's a string, which could throw if value is not a string type.

Apply this diff:

       if (
         typeof methodValue !== 'object' ||
         methodValue?.type !== 'JSFunction' ||
-        !methodValue?.value.startsWith('function')
+        typeof methodValue?.value !== 'string' ||
+        !methodValue.value.startsWith('function')
       ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fixMethods = (methods: Record<string, any>) => {
if (methods && Object.keys(methods).length) {
Object.entries(methods).forEach(([methodName, methodValue]: [string, any]) => {
if (
typeof methodValue !== 'object' ||
methodValue?.type !== 'JSFunction' ||
!methodValue?.value.startsWith('function')
) {
methods[methodName] = {
type: 'JSFunction',
value: 'function ' + methodName + '() {\n console.log("' + methodName + '");\n}'
}
logger.log('autofix method to empty function:', methodName, methods[methodName])
}
})
}
}
const fixMethods = (methods: Record<string, any>) => {
if (methods && Object.keys(methods).length) {
Object.entries(methods).forEach(([methodName, methodValue]: [string, any]) => {
if (
typeof methodValue !== 'object' ||
methodValue?.type !== 'JSFunction' ||
typeof methodValue?.value !== 'string' ||
!methodValue.value.startsWith('function')
) {
methods[methodName] = {
type: 'JSFunction',
value: 'function ' + methodName + '() {\n console.log("' + methodName + '");\n}'
}
logger.log('autofix method to empty function:', methodName, methods[methodName])
}
})
}
}
🤖 Prompt for AI Agents
In packages/plugins/robot/src/composables/useAgent.ts around lines 37 to 53, the
conditional at line 43 calls methodValue?.value.startsWith('function') without
ensuring value is a string; update the check to first verify typeof
methodValue?.value === 'string' (e.g. replace the startsWith clause with typeof
methodValue?.value === 'string' && methodValue.value.startsWith('function')) so
non-string values won't throw, keeping the rest of the autofix behavior the
same.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
packages/plugins/robot/src/Main.vue (1)

180-198: Handle upload failures and notify users.

getMetaApi(...).post returns a promise, so network failures bypass this try/catch, leaving the attachment stuck in “uploading” with no user feedback. Please await the request (or add a .catch) and surface the error via TinyNotify, so updateAttachment('') always runs and the user sees the failure.

-const handleFileSelected = (formData: unknown, updateAttachment: (resourceUrl: string) => void) => {
-  try {
-    getMetaApi(META_SERVICE.Http)
-      .post('/material-center/api/resource/upload', formData, {
-        headers: {
-          'Content-Type': 'multipart/form-data'
-        }
-      })
-      .then((res: any) => {
-        updateAttachment(res?.resourceUrl)
-        if (!inputMessage.value) {
-          inputMessage.value = '生成图片中UI效果'
-        }
-      })
-  } catch (error) {
-    // eslint-disable-next-line no-console
-    console.error('上传失败', error)
-    updateAttachment('')
-  }
-}
+const handleFileSelected = async (
+  formData: FormData,
+  updateAttachment: (resourceUrl: string) => void
+) => {
+  try {
+    const res = await getMetaApi(META_SERVICE.Http).post('/material-center/api/resource/upload', formData, {
+      headers: {
+        'Content-Type': 'multipart/form-data'
+      }
+    })
+    updateAttachment(res?.resourceUrl)
+    if (!inputMessage.value) {
+      inputMessage.value = '生成图片中UI效果'
+    }
+  } catch (error) {
+    updateAttachment('')
+    TinyNotify({
+      type: 'error',
+      message: '文件上传失败,请重试',
+      position: 'top-right',
+      duration: 5000
+    })
+  }
+}

Based on learnings

packages/plugins/robot/src/components/chat/RobotChat.vue (2)

166-214: Fix retry upload type handling and null guards.

files is declared as FileList | null, yet retry passes a single File, and the logic assumes .length exists. This breaks TypeScript expectations and can send undefined to FormData, leaving uploads in limbo. Guard null, normalize to File[], and use the stored rawFile on retry.

-const handleSingleFilesSelected = (files: FileList | null, retry = false) => {
+const handleSingleFilesSelected = (files: FileList | File | null, retry = false) => {
   if (retry) {
     singleAttachmentItems.value[0].status = 'uploading'
     singleAttachmentItems.value[0].isUploading = true
     singleAttachmentItems.value[0].messageType = 'uploading'
   } else {
-    if (!files.length) return
-
-    if (files && files.length > 1) {
+    if (!files) return
+    const selectedFiles = files instanceof FileList ? Array.from(files) : [files]
+    if (!selectedFiles.length) return
+    if (selectedFiles.length > 1) {
       Notify({
         type: 'error',
         message: '当前仅支持上传一张图片',
         position: 'top-right',
         duration: 5000
       })
       return
     }
 
-    if (files && files.length > 0) {
-      // 将选中的文件转换为 Attachment 格式并添加到附件列表
-      const newAttachments = Array.from(files).map((file) => ({
-        size: file.size,
-        rawFile: file
-      }))
-      singleAttachmentItems.value.push(...newAttachments)
-    }
+    const newAttachments = selectedFiles.map((file) => ({
+      size: file.size,
+      rawFile: file
+    }))
+    singleAttachmentItems.value.push(...newAttachments)
   }
 
   // 开始上传
   const formData = new FormData()
-  const fileData = retry ? files : files[0]
-  formData.append('file', fileData)
+  const fileData =
+    retry && files instanceof File
+      ? files
+      : files instanceof FileList
+        ? files[0]
+        : files instanceof File
+          ? files
+          : null
+  if (!fileData) {
+    return
+  }
+  formData.append('file', fileData)
@@
-const handleSingleFileRetry = (file: any) => {
-  handleSingleFilesSelected(file.file, true)
+const handleSingleFileRetry = (file: any) => {
+  handleSingleFilesSelected(file.rawFile, true)
 }

358-360: Guard abort on empty message list.

messages.value.at(-1)! throws if the list is empty (e.g., aborting before any assistant response exists). Add a null check before setting aborted.

 const handleAbortRequest = () => {
   abortRequest()
-  messages.value.at(-1)!.aborted = true
+  const lastMessage = messages.value.at(-1)
+  if (lastMessage) {
+    lastMessage.aborted = true
+  }
 }
packages/plugins/robot/src/composables/useChat.ts (1)

86-93: Guard reasoning extraBody merge.

Some providers return extraBody.disable = null; passing that into Object.assign throws TypeError: Cannot convert undefined or null to object, breaking agent flows when thinking is off. Select the branch and only merge when it’s truthy.

-  if (modelCapabilities?.reasoning?.extraBody) {
-    Object.assign(
-      requestParams,
-      robotSettingState.enableThinking
-        ? modelCapabilities.reasoning.extraBody.enable
-        : modelCapabilities.reasoning.extraBody.disable
-    )
-  }
+  if (modelCapabilities?.reasoning?.extraBody) {
+    const extraBody = robotSettingState.enableThinking
+      ? modelCapabilities.reasoning.extraBody.enable
+      : modelCapabilities.reasoning.extraBody.disable
+    if (extraBody) {
+      Object.assign(requestParams, extraBody)
+    }
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eee31c8 and 2e06be5.

📒 Files selected for processing (5)
  • packages/plugins/robot/src/Main.vue (2 hunks)
  • packages/plugins/robot/src/components/chat/RobotChat.vue (1 hunks)
  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue (11 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/constants/model-config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/plugins/robot/src/constants/model-config.ts
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-15T02:19:06.755Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 940
File: packages/canvas/DesignCanvas/src/DesignCanvas.vue:0-0
Timestamp: 2025-01-15T02:19:06.755Z
Learning: In Vue components using message subscriptions from opentiny/tiny-engine-meta-register, always clean up subscriptions in the onUnmounted hook using useMessage().unsubscribe() to prevent memory leaks.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.

Applied to files:

  • packages/plugins/robot/src/composables/useChat.ts
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。

Applied to files:

  • packages/plugins/robot/src/Main.vue
🧬 Code graph analysis (1)
packages/plugins/robot/src/composables/useChat.ts (9)
packages/plugins/robot/src/types/mcp.types.ts (1)
  • ResponseToolCall (1-7)
packages/register/src/common.ts (2)
  • getMetaApi (20-30)
  • getOptions (32-34)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/types/chat.types.ts (2)
  • LLMMessage (32-36)
  • RobotMessage (38-43)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/plugins/robot/src/composables/useMcp.ts (1)
  • useMcpServer (126-137)
packages/plugins/robot/src/composables/useAgent.ts (4)
  • search (195-211)
  • fetchAssets (213-225)
  • updatePageSchema (193-193)
  • isValidJsonPatchObjectString (123-138)
packages/plugins/robot/src/prompts/index.ts (2)
  • getAgentSystemPrompt (30-52)
  • getJsonFixPrompt (56-87)
packages/plugins/robot/src/utils/chat.utils.ts (3)
  • mergeStringFields (37-56)
  • formatMessages (7-17)
  • serializeError (19-29)

Comment on lines +383 to +404
switchConversation: (conversationId: string) => {
const conversation = conversationState.conversations.find((conversation) => conversation.id === conversationId)
if (!conversation) return

rest.switchConversation(conversationId)
// 切换会话后跟随切换对话模式
if (conversation.metadata?.chatMode) {
robotSettingState.chatMode = robotSettingState.chatMode as string
} else {
robotSettingState.chatMode = CHAT_MODE.Agent
rest.updateMetadata(conversationId, { chatMode: CHAT_MODE.Agent })
rest.saveConversations()
}
if (robotSettingState.chatMode === CHAT_MODE.Agent) {
messageManager.messages.value.at(-1)?.renderContent?.forEach((item) => {
if (item.type === 'loading') {
item.status = 'failed'
}
})
} else {
removeLoading(messageManager.messages.value)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Restore stored chat mode when switching conversations.

When a conversation already has metadata.chatMode, this branch reassigns robotSettingState.chatMode to itself, so the UI keeps the previous mode (file uploads/tools stay misconfigured). Load the stored mode, persist it, and update the provider config.

-      if (conversation.metadata?.chatMode) {
-        robotSettingState.chatMode = robotSettingState.chatMode as string
-      } else {
+      if (conversation.metadata?.chatMode) {
+        robotSettingState.chatMode = conversation.metadata.chatMode as string
+      } else {
         robotSettingState.chatMode = CHAT_MODE.Agent
         rest.updateMetadata(conversationId, { chatMode: CHAT_MODE.Agent })
         rest.saveConversations()
       }
+      saveRobotSettingState({ chatMode: robotSettingState.chatMode })
+      updateLLMConfig({ apiUrl: getApiUrl() })
       if (robotSettingState.chatMode === CHAT_MODE.Agent) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants