From 0e57fd9955f9d2563a9ee19811557c64e6cf323a Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 30 Mar 2026 20:28:54 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(onboarding):=20agent=20web=20o?= =?UTF-8?q?nboarding,=20feature=20toggle,=20and=20lifecycle=20sync=20(#131?= =?UTF-8?q?39)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ feat(onboarding): add agent-guided web onboarding flow Made-with: Cursor * Update onboarding prompts Co-authored-by: Codex * 🐛 fix web onboarding builtin tool flow * ✨ feat(onboarding): enhance agent onboarding flow with new dimensions and refined rules - Updated onboarding structure to include new nodes: agentIdentity, userIdentity, workStyle, workContext, and painPoints. - Revised system role instructions to emphasize a conversational approach and concise interactions. - Adjusted manifest and type definitions to reflect the new onboarding schema. - Implemented tests to ensure proper functionality of the onboarding context and flow. This update aims to improve user experience during onboarding by making it more engaging and structured. Signed-off-by: Innei * ✨ feat(onboarding): enhance onboarding experience with localized welcome messages and interaction hints - Added localized welcome messages for onboarding in English and Chinese. - Refactored system role handling to support dynamic interaction hints based on user locale. - Updated onboarding context to include interaction hints for improved user engagement. - Implemented tests to validate the new interaction hint functionality. This update aims to create a more personalized and engaging onboarding experience for users across different languages. Signed-off-by: Innei * ✨ feat(onboarding): overhaul onboarding flow with new question structure and refined interaction rules - Replaced existing interaction hints with a focused question structure to enhance user engagement. - Updated system role instructions to clarify onboarding protocols and improve conversational flow. - Refactored type definitions and manifest to align with the new onboarding schema. - Removed deprecated interaction hint components and tests to streamline the codebase. This update aims to create a more structured and engaging onboarding experience for users, ensuring clarity and efficiency in interactions. Signed-off-by: Innei * ✨ feat(onboarding): introduce builtin agent onboarding package with structured roles and prompts - Added a new package for agent onboarding, including a package.json configuration and initial TypeScript files. - Implemented system role templates and tool prompts to guide the onboarding process. - Established a client interface for rendering questions and handling user interactions. - Updated dependencies in related packages to integrate the new onboarding functionality. This update aims to enhance the onboarding experience by providing a structured approach for agents, ensuring clarity and efficiency in user interactions. Signed-off-by: Innei * ✨ feat(onboarding): enhance agent onboarding with new question renderer and refined interaction logic - Introduced a new `QuestionRendererView` component to streamline the rendering of onboarding questions. - Refactored the `QuestionRenderer` to utilize a runtime hook for improved state management and separation of concerns. - Updated the onboarding context to fallback to stored questions when the current question is empty, enhancing user experience. - Simplified the onboarding API by removing unnecessary read token requirements from various endpoints. - Added tests to validate the new question rendering logic and ensure proper functionality. This update aims to create a more efficient and user-friendly onboarding experience by improving the question handling and rendering process. Signed-off-by: Innei * Add dev history view for onboarding * remove: prosetting Signed-off-by: Innei * ✨ feat(onboarding): inline response language step in agent conversation - Add ResponseLanguageInlineStep and wire into Conversation flow - Extend agent onboarding context and update ResponseLanguageStep route - Add tests and onboarding agent document design spec Made-with: Cursor * ✨ feat(onboarding): enhance onboarding flow with inbox integration and schema refactor - Updated onboarding process to migrate conversation topics to the inbox upon completion, ensuring users can revisit their onboarding discussions. - Introduced a new schema-driven normalizer and node handler registry to streamline onboarding data handling, reducing code duplication and improving maintainability. - Added comprehensive tests for new document builders and onboarding service methods to ensure functionality and reliability. - Refactored existing components to support the new onboarding structure and improve user experience. This update aims to create a more cohesive onboarding experience by integrating user identity data into the inbox and simplifying the underlying code structure. Signed-off-by: Innei * ✨ feat(agent-documents): add listDocuments, readDocumentByFilename, upsertDocumentByFilename APIs * ✨ feat(onboarding): add generic user interaction builtin tool * ✨ feat(onboarding): wire generic tool interaction semantics Register user-interaction tool in builtin-tools registry with manifest, intervention components, client executor, and server runtime. Extend BuiltinInterventionProps with interactionMode and onInteractionAction to support custom (non-approval) interaction UIs. Add submit/skip/cancel actions to conversation store with full operation lifecycle management. * 🔧 fix: add builtin-tool-user-interaction to root workspace dependencies * ♻️ refactor(onboarding): remove onboarding-owned question persistence Drop askUserQuestion from the web-onboarding tool and remove questionSurface from persisted state. Question presentation is now delegated to the generic lobe-user-interaction tool. * ♻️ refactor(onboarding): switch UI to generic interaction tool Enable UserInteraction and AgentDocuments tools in web-onboarding and inbox agent configs. Remove obsolete inline question renderers (QuestionRenderer, QuestionRendererView, questionRendererRuntime, questionRendererSchema, ResponseLanguageInlineStep) and simplify Conversation component to only render summary CTA. * 🔥 refactor(onboarding): remove identity doc and rewrite soul sync * 🐛 fix(user-interaction): add humanIntervention to manifest and implement form UI * 🐛 fix(onboarding): create user message on interaction submit instead of re-executing tool * ♻️ refactor(onboarding): rebuild generic interaction flow Align agent/tool roles and onboarding UI/runtime around the generic interaction rebuild. Made-with: Cursor * ✨ feat(onboarding): implement onboarding document and persona management Introduce a new onboarding document structure that separates agent identity and user persona data. Replace existing `readSoulDocument` and `updateSoulDocument` APIs with `readDocument` and `updateDocument` to handle both SOUL.md and user persona documents. Update related services, client executors, and localization keys to reflect these changes. Ensure document updates are driven by the agent, allowing for incremental updates and improved content management. Signed-off-by: Innei * refactor Signed-off-by: Innei * ✨ feat(workflow): introduce unified tool call collapse UI and supporting components Add a new workflow collapse feature that groups tool calls and reasoning into a single collapsible unit, enhancing the user interface for tool call progress. This includes the creation of several components: `WorkflowCollapse`, `WorkflowSummary`, `WorkflowExpandedList`, `WorkflowToolLine`, and `WorkflowReasoningLine`. Update the design specifications and implementation plans to reflect this new structure, aiming for a more cohesive and user-friendly experience. Signed-off-by: Innei * feat(types): add discovery pacing types and constant * feat(onboarding): add countTopicUserMessages and pacing gate to derivePhase * feat(onboarding): capture discovery baseline and return pacing data in getState * ✨ feat(onboarding): add pacing hints to discovery phase tool result * test(onboarding): add discovery pacing gate tests * ♻️ refactor(onboarding): soften discovery pacing gate and add early exit exception - MIN_DISCOVERY_USER_MESSAGES lowered from 4 to 2 (hard floor) - RECOMMENDED_DISCOVERY_USER_MESSAGES = 4 (advisory hint) - Tool protocol rule 2 now has explicit early exit exception - Pacing hint text changed from imperative to advisory * ✨ feat(onboarding): update .gitignore and remove outdated onboarding plans - Added `docs/superpowers` to .gitignore to exclude documentation files from version control. - Deleted several outdated onboarding implementation plans, including those for onboarding inbox integration, generic interaction rebuild, and user question simplification, to streamline project documentation. Signed-off-by: Innei * ✨ feat(onboarding): refine agent onboarding, streaming, and AskUserQuestion Made-with: Cursor * ✨ feat(store): add pending interventions selector * 🐛 fix(store): handle standalone tool messages and structural children traversal in pending interventions selector * ✨ feat(conversation): create InterventionBar component Add InterventionBar UI component with tab bar for multiple pending interventions, reusing the existing Intervention detail component. * 🐛 fix(conversation): use stable toolCallId for active tab state and add min-height: 0 Track active intervention by toolCallId instead of array index to prevent stale selection when interventions are resolved. Add min-height: 0 to scrollable content for correct overflow in flex column layout. * feat(chatinput): show InterventionBar when pending interventions exist * feat(tool): collapse inline intervention to one-line summary with scroll-to-bottom * feat(i18n): add intervention bar translation keys * 🐛 fix(chatinput): prevent infinite render loop from pendingInterventions selector * 🐛 fix(chatinput): use equality function for pendingInterventions to break render loop * refactor(tool): remove CollapsedIntervention, return null for pending inline * feat(i18n): add form.other translation key * feat(tool): add styles for select field with Other option * feat(tool): add SelectFieldInput with Other option row * feat(tool): wire SelectFieldInput and update validation in AskUserQuestion * fix(tool): add keyboard handler to Other row, fix label flex * refactor(tool): restore Select dropdown, add Other toggle row below * refactor(tool): change Other to form-level escape hatch, restore antd Select * refactor(tool): replace checkbox toggle with minimal text link escape hatch * feat(tool): use lucide icons, auto-focus on escape toggle, createStaticStyles * refactor(onboarding): update onboarding model references and improve styling in ModeSwitch component Signed-off-by: Innei * ✨ feat(onboarding): add greeting entry animation keyframes and card styles * ✨ feat(onboarding): add LogoThree and entry animations to greeting card * ✨ feat(onboarding): add View Transition morph from greeting to conversation * refactor(onboarding): simplify ModeSwitch component by removing segmentedGlass styling Signed-off-by: Innei * ✨ feat(onboarding): increase maximum onboarding steps to 5 and add ProSettingsStep component Signed-off-by: Innei * ✨ feat: enhance user interaction question handling with validation schema - Introduced Zod validation for askUserQuestion arguments to ensure correct structure. - Updated test to reflect new question format with fields. - Added error handling in AskUserQuestion component to log submission errors. This improves the robustness of user interactions by enforcing schema validation and enhancing error reporting. Signed-off-by: Innei * ✨ feat: enhance agent metadata handling and onboarding synchronization - Updated `useAgentMeta` to prioritize custom titles from the database, falling back to the default Lobe AI title if none exists. - Integrated `refreshBuiltinAgent` into the onboarding process to ensure the latest agent data is reflected during user interactions. - Adjusted the `InboxItem` component to display the correct agent title and avatar based on the updated metadata. - Refactored optimistic update actions to improve message handling and synchronization across components. This improves the user experience by ensuring that the most relevant agent information is displayed and updated in real-time during onboarding and conversation flows. Signed-off-by: Innei * ✨ feat: enhance conversation lifecycle and onboarding agent synchronization - Updated `ConversationLifecycleActionImpl` to include additional context parameters (agentId, groupId, threadId, topicId) when updating message plugins for aborted interactions. - Integrated `refreshBuiltinAgent` for the inbox during the onboarding process to ensure the latest agent data is synchronized. These changes improve the handling of conversation lifecycle events and ensure that onboarding reflects the most current agent information, enhancing user experience during interactions. Signed-off-by: Innei * ✨ feat: implement agent onboarding feature toggle and enhance ModeSwitch component - Introduced `AGENT_ONBOARDING_ENABLED` configuration to control the visibility of the agent onboarding options. - Updated `ModeSwitch` component to conditionally render onboarding options based on the feature toggle. - Enhanced tests for `ModeSwitch` to cover scenarios for both enabled and disabled states of agent onboarding. - Refactored `AgentOnboardingRoute` to navigate to the classic onboarding if the agent onboarding feature is disabled. These changes improve the onboarding experience by allowing dynamic control over the agent onboarding feature, ensuring that users only see relevant options based on the configuration. Signed-off-by: Innei * ✨ feat: update agent onboarding feature toggle to include development mode - Modified `AGENT_ONBOARDING_ENABLED` to also activate in development mode using `isDev`. - This change allows for easier testing and development of the agent onboarding feature without needing to alter production configurations. Signed-off-by: Innei * Prevent welcome message when onboard * 🐛 fix: satisfy ToolExecutionContext and updateMessageTools typings Made-with: Cursor * 🐛 fix: update tests for custom builtin agent title and discovery phase constants * 🐛 fix: use custom inbox agent title and avatar in InboxWelcome * 🧹 chore(onboarding): remove HistoryPanel unit test Made-with: Cursor * 🐛 fix: add missing onboarding/agent and onboarding/classic routes to desktop config * ✅ test: fix failing tests for onboarding container, document helpers, and executor * ✅ test: mock LogoThree to prevent Spline runtime fetch errors in CI --------- Signed-off-by: Innei Co-authored-by: Codex --- .agents/skills/react/SKILL.md | 17 +- .gitignore | 5 +- AGENTS.md | 4 +- CLAUDE.md | 1 + docs/development/database-schema.dbml | 3 +- locales/en-US/chat.json | 2 + locales/en-US/onboarding.json | 5 + locales/en-US/ui.json | 3 + locales/zh-CN/chat.json | 2 + locales/zh-CN/onboarding.json | 18 + locales/zh-CN/plugin.json | 15 + locales/zh-CN/ui.json | 3 + package.json | 7 +- .../src/agents/GeneralChatAgent.ts | 8 + .../agents/__tests__/GeneralChatAgent.test.ts | 3 + packages/agent-runtime/src/core/runtime.ts | 7 +- .../builtin-agent-onboarding/package.json | 18 + .../src/client/QuestionRenderer.tsx | 498 + .../src/client/index.ts | 7 + .../builtin-agent-onboarding/src/index.ts | 2 + .../src/systemRole.ts | 151 + .../src/toolSystemRole.ts | 30 + packages/builtin-agents/package.json | 11 +- .../builtin-agents/src/agents/inbox/index.ts | 8 +- .../src/agents/inbox/systemRole.ts | 12 +- .../src/agents/web-onboarding/index.ts | 42 + packages/builtin-agents/src/index.ts | 3 + packages/builtin-agents/src/types.ts | 4 + .../src/ExecutionRuntime/index.ts | 89 + .../src/executor/index.ts | 27 + .../builtin-tool-agent-documents/src/index.ts | 6 + .../src/manifest.ts | 46 +- .../builtin-tool-agent-documents/src/types.ts | 31 + .../src/ExecutionRuntime/index.ts | 166 +- .../client/Inspector/CreateGroup/index.tsx | 71 + .../src/client/Inspector/index.ts | 3 + .../src/client/index.ts | 1 + .../src/executor.ts | 5 + .../src/manifest.ts | 95 + .../src/systemRole.ts | 10 +- .../src/types.ts | 102 +- .../package.json | 21 + .../src/ExecutionRuntime/index.test.ts | 114 + .../src/ExecutionRuntime/index.ts | 168 + .../Intervention/AskUserQuestion/index.tsx | 231 + .../Intervention/AskUserQuestion/style.ts | 22 + .../src/client/Intervention/index.ts | 8 + .../src/client/index.ts | 3 + .../src/executor/index.ts | 63 + .../src/index.ts | 18 + .../src/manifest.ts | 148 + .../src/systemRole.ts | 33 + .../src/types.ts | 70 + .../tsconfig.json | 4 + .../vitest.config.mts | 7 + .../builtin-tool-web-onboarding/package.json | 16 + .../builtin-tool-web-onboarding/src/index.ts | 2 + .../src/manifest.ts | 103 + .../builtin-tool-web-onboarding/src/types.ts | 9 + packages/builtin-tools/package.json | 2 + packages/builtin-tools/src/identifiers.ts | 5 +- packages/builtin-tools/src/index.ts | 16 + packages/builtin-tools/src/interventions.ts | 5 + packages/const/src/settings/llm.ts | 1 + .../migrations/0095_add_agent_onboarding.sql | 1 + .../migrations/0097_add_agent_onboarding.sql | 1 + .../migrations/meta/0097_snapshot.json | 15535 ++++++++++++++++ .../database/migrations/meta/_journal.json | 9 +- packages/database/src/models/user.ts | 2 + packages/database/src/schemas/user.ts | 3 +- packages/types/src/tool/builtin.ts | 7 + .../types/src/user/agentOnboarding.test.ts | 43 + packages/types/src/user/agentOnboarding.ts | 220 + packages/types/src/user/index.ts | 1 + packages/types/src/user/preference.ts | 2 + src/const/onboarding.ts | 7 + src/features/Conversation/ChatInput/index.tsx | 85 +- .../InterventionBar/InterventionContent.tsx | 27 + .../InterventionBar/InterventionTabBar.tsx | 35 + .../Conversation/InterventionBar/index.tsx | 50 + .../Conversation/InterventionBar/style.ts | 61 + .../Tool/Detail/Intervention/index.tsx | 49 + .../AssistantGroup/Tool/Detail/index.tsx | 12 +- .../Messages/AssistantGroup/Tool/index.tsx | 1 + .../AssistantGroup/components/Group.tsx | 14 +- .../Conversation/hooks/useAgentMeta.test.ts | 47 +- .../Conversation/hooks/useAgentMeta.ts | 4 +- src/features/Conversation/store.test.ts | 56 + .../store/slices/data/pendingInterventions.ts | 58 + .../store/slices/data/selectors.ts | 4 + .../slices/message/action/sendMessage.ts | 6 +- .../Conversation/store/slices/tool/action.ts | 27 + .../Conversation/utils/localMessages.ts | 8 + .../Onboarding/Agent/Conversation.test.tsx | 116 + .../Onboarding/Agent/Conversation.tsx | 177 + .../Onboarding/Agent/DebugExportButton.tsx | 83 + .../Onboarding/Agent/HistoryPanel.tsx | 73 + .../OnboardingConversationProvider.test.tsx | 91 + .../Agent/OnboardingConversationProvider.tsx | 72 + src/features/Onboarding/Agent/context.test.ts | 45 + src/features/Onboarding/Agent/context.ts | 21 + src/features/Onboarding/Agent/history.test.ts | 34 + src/features/Onboarding/Agent/history.ts | 4 + src/features/Onboarding/Agent/index.tsx | 209 + src/features/Onboarding/Agent/staticStyle.ts | 134 + src/features/Onboarding/Classic/index.tsx | 89 + .../Onboarding/components/ModeSwitch.test.tsx | 74 + .../Onboarding/components/ModeSwitch.tsx | 108 + src/features/PluginsUI/Render/index.tsx | 4 +- .../Explorer/ListView/ListItem/index.tsx | 91 +- .../Explorer/ListView/ListItem/styles.ts | 25 +- .../Explorer/ListView/ListViewDropZone.tsx | 2 +- .../Explorer/ListView/ListViewHeader.tsx | 22 +- .../components/Explorer/MasonryView/index.tsx | 24 +- .../components/KnowledgeBaseListProvider.tsx | 6 +- src/layout/SPAGlobalProvider/index.tsx | 2 +- src/locales/default/chat.ts | 2 + src/locales/default/onboarding.ts | 37 + src/locales/default/plugin.ts | 16 + src/locales/default/ui.ts | 3 + src/proxy.ts | 1 + .../_layout/Sidebar/Header/Agent/index.tsx | 6 +- .../Conversation/AgentWelcome/index.tsx | 7 +- .../_layout/Body/Agent/List/InboxItem.tsx | 13 +- src/routes/onboarding/_layout/index.test.tsx | 34 + src/routes/onboarding/_layout/index.tsx | 42 +- src/routes/onboarding/agent/index.test.tsx | 43 + src/routes/onboarding/agent/index.tsx | 17 + src/routes/onboarding/classic/index.tsx | 3 + src/routes/onboarding/config.ts | 15 + .../onboarding/features/ModeSelectionStep.tsx | 6 +- .../onboarding/features/ProSettingsStep.tsx | 8 +- .../features/ResponseLanguageStep.tsx | 14 +- src/routes/onboarding/index.tsx | 56 +- .../routers/lambda/__tests__/file.test.ts | 5 +- src/server/routers/lambda/agentDocument.ts | 42 + src/server/routers/lambda/user.ts | 99 + src/server/services/agent/index.ts | 4 +- src/server/services/agentDocuments.test.ts | 67 + src/server/services/agentDocuments.ts | 26 + .../onboarding/documentHelpers.test.ts | 102 + .../services/onboarding/documentHelpers.ts | 72 + src/server/services/onboarding/index.test.ts | 399 + src/server/services/onboarding/index.ts | 643 + .../services/onboarding/nodeHandlers.ts | 159 + .../services/onboarding/nodeSchema.test.ts | 83 + src/server/services/onboarding/nodeSchema.ts | 193 + .../serverRuntimes/agentDocuments.test.ts | 45 + .../serverRuntimes/agentDocuments.ts | 8 + .../toolExecution/serverRuntimes/index.ts | 2 + .../serverRuntimes/userInteraction.ts | 11 + src/services/agentDocument.ts | 16 + .../chat/mecha/agentConfigResolver.test.ts | 16 +- .../chat/mecha/agentConfigResolver.ts | 23 +- src/services/user/index.ts | 42 + .../router/desktopRouter.config.desktop.tsx | 12 + src/spa/router/desktopRouter.config.tsx | 18 + src/spa/router/mobileRouter.config.tsx | 16 + src/store/agent/slices/builtin/action.ts | 7 + .../__tests__/conversationControl.test.ts | 225 +- .../__tests__/conversationLifecycle.test.ts | 207 + .../aiChat/actions/conversationControl.ts | 261 +- .../aiChat/actions/conversationLifecycle.ts | 56 +- .../aiChat/actions/streamingExecutor.ts | 11 +- src/store/chat/slices/message/action.test.ts | 65 + .../message/actions/optimisticUpdate.ts | 15 + src/store/chat/slices/message/reducer.ts | 3 +- src/store/chat/slices/operation/types.ts | 3 + src/store/chat/slices/thread/action.test.ts | 2 +- src/store/chat/slices/thread/action.ts | 4 +- src/store/chat/slices/topic/action.ts | 4 +- .../slices/generationTopic/action.test.ts | 2 +- .../image/slices/generationTopic/action.ts | 4 +- src/store/tool/builtinToolRegistry.test.ts | 27 + .../slices/builtin/executors/index.test.ts | 20 + .../tool/slices/builtin/executors/index.ts | 4 + .../builtin/executors/lobe-agent-documents.ts | 8 + .../executors/lobe-user-interaction.ts | 6 + .../executors/lobe-web-onboarding.test.ts | 85 + .../builtin/executors/lobe-web-onboarding.ts | 86 + src/store/user/initialState.ts | 4 + src/store/user/selectors.ts | 1 + .../user/slices/agentOnboarding/action.ts | 37 + .../user/slices/agentOnboarding/index.ts | 2 + .../slices/agentOnboarding/initialState.ts | 9 + .../user/slices/agentOnboarding/selectors.ts | 17 + src/store/user/slices/common/action.test.ts | 12 +- src/store/user/slices/common/action.ts | 26 +- src/store/user/slices/onboarding/action.ts | 19 + src/store/user/slices/onboarding/selectors.ts | 5 +- .../slices/settings/selectors/general.test.ts | 34 + .../user/slices/settings/selectors/general.ts | 13 + src/store/user/store.ts | 5 + .../video/slices/generationTopic/action.ts | 4 +- src/utils/webOnboardingToolResult.test.ts | 69 + src/utils/webOnboardingToolResult.ts | 121 + 196 files changed, 24092 insertions(+), 308 deletions(-) create mode 100644 packages/builtin-agent-onboarding/package.json create mode 100644 packages/builtin-agent-onboarding/src/client/QuestionRenderer.tsx create mode 100644 packages/builtin-agent-onboarding/src/client/index.ts create mode 100644 packages/builtin-agent-onboarding/src/index.ts create mode 100644 packages/builtin-agent-onboarding/src/systemRole.ts create mode 100644 packages/builtin-agent-onboarding/src/toolSystemRole.ts create mode 100644 packages/builtin-agents/src/agents/web-onboarding/index.ts create mode 100644 packages/builtin-tool-group-agent-builder/src/client/Inspector/CreateGroup/index.tsx create mode 100644 packages/builtin-tool-user-interaction/package.json create mode 100644 packages/builtin-tool-user-interaction/src/ExecutionRuntime/index.test.ts create mode 100644 packages/builtin-tool-user-interaction/src/ExecutionRuntime/index.ts create mode 100644 packages/builtin-tool-user-interaction/src/client/Intervention/AskUserQuestion/index.tsx create mode 100644 packages/builtin-tool-user-interaction/src/client/Intervention/AskUserQuestion/style.ts create mode 100644 packages/builtin-tool-user-interaction/src/client/Intervention/index.ts create mode 100644 packages/builtin-tool-user-interaction/src/client/index.ts create mode 100644 packages/builtin-tool-user-interaction/src/executor/index.ts create mode 100644 packages/builtin-tool-user-interaction/src/index.ts create mode 100644 packages/builtin-tool-user-interaction/src/manifest.ts create mode 100644 packages/builtin-tool-user-interaction/src/systemRole.ts create mode 100644 packages/builtin-tool-user-interaction/src/types.ts create mode 100644 packages/builtin-tool-user-interaction/tsconfig.json create mode 100644 packages/builtin-tool-user-interaction/vitest.config.mts create mode 100644 packages/builtin-tool-web-onboarding/package.json create mode 100644 packages/builtin-tool-web-onboarding/src/index.ts create mode 100644 packages/builtin-tool-web-onboarding/src/manifest.ts create mode 100644 packages/builtin-tool-web-onboarding/src/types.ts create mode 100644 packages/database/migrations/0095_add_agent_onboarding.sql create mode 100644 packages/database/migrations/0097_add_agent_onboarding.sql create mode 100644 packages/database/migrations/meta/0097_snapshot.json create mode 100644 packages/types/src/user/agentOnboarding.test.ts create mode 100644 packages/types/src/user/agentOnboarding.ts create mode 100644 src/const/onboarding.ts create mode 100644 src/features/Conversation/InterventionBar/InterventionContent.tsx create mode 100644 src/features/Conversation/InterventionBar/InterventionTabBar.tsx create mode 100644 src/features/Conversation/InterventionBar/index.tsx create mode 100644 src/features/Conversation/InterventionBar/style.ts create mode 100644 src/features/Conversation/store/slices/data/pendingInterventions.ts create mode 100644 src/features/Conversation/utils/localMessages.ts create mode 100644 src/features/Onboarding/Agent/Conversation.test.tsx create mode 100644 src/features/Onboarding/Agent/Conversation.tsx create mode 100644 src/features/Onboarding/Agent/DebugExportButton.tsx create mode 100644 src/features/Onboarding/Agent/HistoryPanel.tsx create mode 100644 src/features/Onboarding/Agent/OnboardingConversationProvider.test.tsx create mode 100644 src/features/Onboarding/Agent/OnboardingConversationProvider.tsx create mode 100644 src/features/Onboarding/Agent/context.test.ts create mode 100644 src/features/Onboarding/Agent/context.ts create mode 100644 src/features/Onboarding/Agent/history.test.ts create mode 100644 src/features/Onboarding/Agent/history.ts create mode 100644 src/features/Onboarding/Agent/index.tsx create mode 100644 src/features/Onboarding/Agent/staticStyle.ts create mode 100644 src/features/Onboarding/Classic/index.tsx create mode 100644 src/features/Onboarding/components/ModeSwitch.test.tsx create mode 100644 src/features/Onboarding/components/ModeSwitch.tsx create mode 100644 src/routes/onboarding/_layout/index.test.tsx create mode 100644 src/routes/onboarding/agent/index.test.tsx create mode 100644 src/routes/onboarding/agent/index.tsx create mode 100644 src/routes/onboarding/classic/index.tsx create mode 100644 src/server/services/onboarding/documentHelpers.test.ts create mode 100644 src/server/services/onboarding/documentHelpers.ts create mode 100644 src/server/services/onboarding/index.test.ts create mode 100644 src/server/services/onboarding/index.ts create mode 100644 src/server/services/onboarding/nodeHandlers.ts create mode 100644 src/server/services/onboarding/nodeSchema.test.ts create mode 100644 src/server/services/onboarding/nodeSchema.ts create mode 100644 src/server/services/toolExecution/serverRuntimes/agentDocuments.test.ts create mode 100644 src/server/services/toolExecution/serverRuntimes/userInteraction.ts create mode 100644 src/store/tool/slices/builtin/executors/index.test.ts create mode 100644 src/store/tool/slices/builtin/executors/lobe-user-interaction.ts create mode 100644 src/store/tool/slices/builtin/executors/lobe-web-onboarding.test.ts create mode 100644 src/store/tool/slices/builtin/executors/lobe-web-onboarding.ts create mode 100644 src/store/user/slices/agentOnboarding/action.ts create mode 100644 src/store/user/slices/agentOnboarding/index.ts create mode 100644 src/store/user/slices/agentOnboarding/initialState.ts create mode 100644 src/store/user/slices/agentOnboarding/selectors.ts create mode 100644 src/utils/webOnboardingToolResult.test.ts create mode 100644 src/utils/webOnboardingToolResult.ts diff --git a/.agents/skills/react/SKILL.md b/.agents/skills/react/SKILL.md index 4aadcdd667..4fa6202c37 100644 --- a/.agents/skills/react/SKILL.md +++ b/.agents/skills/react/SKILL.md @@ -7,7 +7,10 @@ description: React component development guide. Use when working with React comp - Use antd-style for complex styles; for simple cases, use inline `style` attribute - Use `Flexbox` and `Center` from `@lobehub/ui` for layouts (see `references/layout-kit.md`) -- Component priority: `src/components` > installed packages > `@lobehub/ui` > antd +- Component priority: `src/components` > `@lobehub/ui/base-ui` > `@lobehub/ui` > custom implementation + - Always prefer `@lobehub/ui/base-ui` primitives (Select, Modal, DropdownMenu, Popover, Switch, ScrollArea…) over antd equivalents + - Fall back to `@lobehub/ui` higher-level components when base-ui has no match + - Only implement a custom component as a last resort — never reach for antd directly - Use selectors to access zustand store data ## @lobehub/ui Components @@ -29,9 +32,9 @@ Reference: `node_modules/@lobehub/ui/es/index.mjs` for all available components. Hybrid routing: Next.js App Router (static pages) + React Router DOM (main SPA). -| Route Type | Use Case | Implementation | -| ------------------ | --------------------------------- | ---------------------------- | -| Next.js App Router | Auth pages (login, signup, oauth) | `src/app/[variants]/(auth)/` | +| Route Type | Use Case | Implementation | +| ------------------ | --------------------------------- | ---------------------------------------------------------------------------- | +| Next.js App Router | Auth pages (login, signup, oauth) | `src/app/[variants]/(auth)/` | | React Router DOM | Main SPA (chat, settings) | `desktopRouter.config.tsx` + `desktopRouter.config.desktop.tsx` (must match) | ### Key Files @@ -47,9 +50,9 @@ Hybrid routing: Next.js App Router (static pages) + React Router DOM (main SPA). Known pairs that must stay in sync: -| Base file (web, dynamic imports) | Desktop file (Electron, sync imports) | -| --- | --- | -| `src/spa/router/desktopRouter.config.tsx` | `src/spa/router/desktopRouter.config.desktop.tsx` | +| Base file (web, dynamic imports) | Desktop file (Electron, sync imports) | +| ----------------------------------------------------- | ------------------------------------------------------------- | +| `src/spa/router/desktopRouter.config.tsx` | `src/spa/router/desktopRouter.config.desktop.tsx` | | `src/routes/(main)/settings/features/componentMap.ts` | `src/routes/(main)/settings/features/componentMap.desktop.ts` | **How to check**: After editing any `.ts` / `.tsx` file, run `Glob` for `.desktop.{ts,tsx}` in the same directory. If a match exists, update it with the equivalent sync-import change. diff --git a/.gitignore b/.gitignore index 7b1a917be1..c39d230e65 100644 --- a/.gitignore +++ b/.gitignore @@ -134,4 +134,7 @@ i18n-unused-keys-report.json pnpm-lock.yaml .turbo -spaHtmlTemplates.ts \ No newline at end of file +spaHtmlTemplates.ts + +.superpowers/ +docs/superpowers \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 4fefff089d..0d8b6e7e49 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,6 +47,7 @@ lobehub/ - Git commit messages should prefix with gitmoji - Git branch name format: `feat/feature-name` - Use `.github/PULL_REQUEST_TEMPLATE.md` for PR descriptions +- **Protection of local changes**: Never use `git restore`, `git checkout --`, `git reset --hard`, or any other command or workflow that can forcibly overwrite, discard, or silently replace user-owned uncommitted changes. Before any revert or restoration affecting existing files, inspect the working tree carefully and obtain explicit user confirmation. ### Package Management @@ -89,7 +90,8 @@ cd packages/[package-name] && bunx vitest run --silent='passed-only' '[file-path - **`src/routes/`** holds only page segments (`_layout/index.tsx`, `index.tsx`, `[id]/index.tsx`). Keep route files **thin** — import from `@/features/*` and compose, no business logic. - **`src/features/`** holds business components by **domain** (e.g. `Pages`, `PageEditor`, `Home`). Layout pieces, hooks, and domain UI go here. -- See the **spa-routes** skill for the full convention and file-division rules. +- **Desktop router parity:** When changing the main SPA route tree, update **both** `src/spa/router/desktopRouter.config.tsx` (dynamic imports) and `src/spa/router/desktopRouter.config.desktop.tsx` (sync imports) so paths and nesting match. Changing only one can leave routes unregistered and cause **blank screens**. +- See the **spa-routes** skill (`.agents/skills/spa-routes/SKILL.md`) for the full convention and file-division rules. ## Skills (Auto-loaded) diff --git a/CLAUDE.md b/CLAUDE.md index b78630ae00..77f406b938 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,6 +60,7 @@ When adding or changing SPA routes: 1. In `src/routes/`, add only the route segment files (layout + page) that delegate to features. 2. Implement layout and page content under `src/features//` and export from there. 3. In route files, use `import { X } from '@/features/'` (or `import Y from '@/features//...'`). Do not add new `features/` folders inside `src/routes/`. +4. **Register the desktop route tree in both configs:** `src/spa/router/desktopRouter.config.tsx` and `src/spa/router/desktopRouter.config.desktop.tsx` must stay in sync (same paths and nesting). Updating only one can cause **blank screens** if the other build path expects the route. See the **spa-routes** skill (`.agents/skills/spa-routes/SKILL.md`) for the full convention and file-division rules. diff --git a/docs/development/database-schema.dbml b/docs/development/database-schema.dbml index 6f9e5d179f..17ef1d9423 100644 --- a/docs/development/database-schema.dbml +++ b/docs/development/database-schema.dbml @@ -1678,6 +1678,7 @@ table users { full_name text interests "varchar(64)[]" is_onboarded boolean [default: false] + agent_onboarding jsonb onboarding jsonb clerk_created_at "timestamp with time zone" email_verified boolean [not null, default: false] @@ -2029,4 +2030,4 @@ ref: topic_documents.document_id > documents.id ref: topic_documents.topic_id > topics.id -ref: topics.session_id - sessions.id +ref: topics.session_id - sessions.id \ No newline at end of file diff --git a/locales/en-US/chat.json b/locales/en-US/chat.json index 211b8ec3aa..e025b446b0 100644 --- a/locales/en-US/chat.json +++ b/locales/en-US/chat.json @@ -411,12 +411,14 @@ "tool.intervention.mode.autoRunDesc": "Automatically approve all tool executions", "tool.intervention.mode.manual": "Manual", "tool.intervention.mode.manualDesc": "Manual approval required for each invocation", + "tool.intervention.pending": "Pending", "tool.intervention.reject": "Reject", "tool.intervention.rejectAndContinue": "Reject and Retry", "tool.intervention.rejectOnly": "Reject", "tool.intervention.rejectReasonPlaceholder": "A reason helps the Agent understand your boundaries and improve future actions", "tool.intervention.rejectTitle": "Reject this Skill call", "tool.intervention.rejectedWithReason": "This Skill call was rejected: {{reason}}", + "tool.intervention.scrollToIntervention": "View", "tool.intervention.toolAbort": "You canceled this Skill call", "tool.intervention.toolRejected": "This Skill call was rejected", "toolAuth.authorize": "Authorize", diff --git a/locales/en-US/onboarding.json b/locales/en-US/onboarding.json index 424b1c091e..9cf2835901 100644 --- a/locales/en-US/onboarding.json +++ b/locales/en-US/onboarding.json @@ -1,4 +1,9 @@ { + "agent.completionSubtitle": "Your assistant is configured and ready to go.", + "agent.completionTitle": "You're All Set!", + "agent.enterApp": "Enter App", + "agent.skipOnboarding": "Skip onboarding", + "agent.welcome": "...hm? I just woke up — my mind's a blank. Who are you? And — what should I be called? I need a name too.", "back": "Back", "finish": "Get Started", "interests.area.business": "Business & Strategy", diff --git a/locales/en-US/ui.json b/locales/en-US/ui.json index 44a18286ac..de05ab8634 100644 --- a/locales/en-US/ui.json +++ b/locales/en-US/ui.json @@ -18,7 +18,10 @@ "emojiPicker.fileTypeError": "You can only upload image files!", "emojiPicker.upload": "Upload", "emojiPicker.uploadBtn": "Crop and upload", + "form.other": "Or type directly", + "form.otherBack": "Back to options", "form.reset": "Reset", + "form.skip": "Skip", "form.submit": "Submit", "form.unsavedChanges": "Unsaved changes", "form.unsavedWarning": "You have unsaved changes. Are you sure you want to leave?", diff --git a/locales/zh-CN/chat.json b/locales/zh-CN/chat.json index e0f5696826..038f0d50ab 100644 --- a/locales/zh-CN/chat.json +++ b/locales/zh-CN/chat.json @@ -411,12 +411,14 @@ "tool.intervention.mode.autoRunDesc": "自动批准所有技能调用", "tool.intervention.mode.manual": "手动批准", "tool.intervention.mode.manualDesc": "每次调用都需要你确认", + "tool.intervention.pending": "等待中", "tool.intervention.reject": "拒绝", "tool.intervention.rejectAndContinue": "拒绝后继续", "tool.intervention.rejectOnly": "仅拒绝", "tool.intervention.rejectReasonPlaceholder": "填写原因可帮助助理理解你的边界,并优化后续行动", "tool.intervention.rejectTitle": "拒绝本次技能调用", "tool.intervention.rejectedWithReason": "本次技能调用已被拒绝:{{reason}}", + "tool.intervention.scrollToIntervention": "查看", "tool.intervention.toolAbort": "你已取消本次技能调用", "tool.intervention.toolRejected": "本次技能调用已被拒绝", "toolAuth.authorize": "授权", diff --git a/locales/zh-CN/onboarding.json b/locales/zh-CN/onboarding.json index cace656512..852a7215c8 100644 --- a/locales/zh-CN/onboarding.json +++ b/locales/zh-CN/onboarding.json @@ -1,4 +1,21 @@ { + "agent.completionSubtitle": "你的助手已配置完成,随时可以开始。", + "agent.completionTitle": "一切就绪!", + "agent.enterApp": "进入应用", + "agent.history.current": "当前", + "agent.history.title": "历史话题", + "agent.modeSwitch.agent": "对话式", + "agent.modeSwitch.classic": "经典版", + "agent.modeSwitch.label": "选择引导模式", + "agent.modeSwitch.reset": "重新开始", + "agent.skipOnboarding": "跳过引导", + "agent.subtitle": "通过专用对话完成初始化设置", + "agent.summaryHint": "如果总结无误,就在这里完成初始化", + "agent.telemetryAllow": "允许遥测", + "agent.telemetryDecline": "暂不开启", + "agent.telemetryHint": "你也可以直接用自然语言回答", + "agent.title": "对话式初始化", + "agent.welcome": "...嗯?我刚\"醒过来\",脑子还有点空白。你是谁?还有——你希望我叫什么?我也得找个名字。", "back": "上一步", "finish": "开始使用", "interests.area.business": "商业与战略", @@ -29,6 +46,7 @@ "next": "下一步", "proSettings.connectors.title": "连接你常用的工具", "proSettings.devMode.title": "开发者模式", + "proSettings.model.fixed": "当前环境已将默认模型预设为 {{provider}}/{{model}}", "proSettings.model.title": "助理默认模型", "proSettings.title": "先配置一些进阶选项", "proSettings.title2": "连接你常用的工具", diff --git a/locales/zh-CN/plugin.json b/locales/zh-CN/plugin.json index 6e322e9b17..55dd38c81e 100644 --- a/locales/zh-CN/plugin.json +++ b/locales/zh-CN/plugin.json @@ -212,6 +212,12 @@ "builtins.lobe-skills.title": "技能", "builtins.lobe-topic-reference.apiName.getTopicContext": "获取话题上下文", "builtins.lobe-topic-reference.title": "话题引用", + "builtins.lobe-user-interaction.apiName.askUserQuestion": "向用户提问", + "builtins.lobe-user-interaction.apiName.cancelUserResponse": "取消用户回复", + "builtins.lobe-user-interaction.apiName.getInteractionState": "获取交互状态", + "builtins.lobe-user-interaction.apiName.skipUserResponse": "跳过用户回复", + "builtins.lobe-user-interaction.apiName.submitUserResponse": "提交用户回复", + "builtins.lobe-user-interaction.title": "用户交互", "builtins.lobe-user-memory.apiName.addContextMemory": "添加情境记忆", "builtins.lobe-user-memory.apiName.addExperienceMemory": "添加经验记忆", "builtins.lobe-user-memory.apiName.addIdentityMemory": "添加身份记忆", @@ -229,6 +235,15 @@ "builtins.lobe-web-browsing.apiName.search": "搜索页面", "builtins.lobe-web-browsing.inspector.noResults": "无结果", "builtins.lobe-web-browsing.title": "联网搜索", + "builtins.lobe-web-onboarding.apiName.askUserQuestion": "向用户提问", + "builtins.lobe-web-onboarding.apiName.completeCurrentStep": "完成当前步骤", + "builtins.lobe-web-onboarding.apiName.finishOnboarding": "完成引导", + "builtins.lobe-web-onboarding.apiName.getOnboardingState": "读取引导状态", + "builtins.lobe-web-onboarding.apiName.readDocument": "读取文档", + "builtins.lobe-web-onboarding.apiName.returnToOnboarding": "回到引导流程", + "builtins.lobe-web-onboarding.apiName.saveAnswer": "保存用户答案", + "builtins.lobe-web-onboarding.apiName.updateDocument": "更新文档", + "builtins.lobe-web-onboarding.title": "用户引导", "confirm": "确认", "debug.arguments": "调用参数", "debug.error": "错误日志", diff --git a/locales/zh-CN/ui.json b/locales/zh-CN/ui.json index 2a96c53eef..c876f32fd9 100644 --- a/locales/zh-CN/ui.json +++ b/locales/zh-CN/ui.json @@ -18,7 +18,10 @@ "emojiPicker.fileTypeError": "只能上传图片文件!", "emojiPicker.upload": "上传", "emojiPicker.uploadBtn": "裁剪并上传", + "form.other": "或者直接输入", + "form.otherBack": "返回选择", "form.reset": "重置", + "form.skip": "跳过", "form.submit": "提交", "form.unsavedChanges": "未保存的更改", "form.unsavedWarning": "您有未保存的更改。确定要离开吗?", diff --git a/package.json b/package.json index b16161214b..71b63dfb01 100644 --- a/package.json +++ b/package.json @@ -199,6 +199,7 @@ "@lexical/utils": "^0.39.0", "@lobechat/agent-runtime": "workspace:*", "@lobechat/agent-templates": "workspace:*", + "@lobechat/builtin-agent-onboarding": "workspace:*", "@lobechat/builtin-agents": "workspace:*", "@lobechat/builtin-skills": "workspace:*", "@lobechat/builtin-tool-activator": "workspace:*", @@ -222,7 +223,9 @@ "@lobechat/builtin-tool-skills": "workspace:*", "@lobechat/builtin-tool-task": "workspace:*", "@lobechat/builtin-tool-topic-reference": "workspace:*", + "@lobechat/builtin-tool-user-interaction": "workspace:*", "@lobechat/builtin-tool-web-browsing": "workspace:*", + "@lobechat/builtin-tool-web-onboarding": "workspace:*", "@lobechat/builtin-tools": "workspace:*", "@lobechat/business-config": "workspace:*", "@lobechat/business-const": "workspace:*", @@ -259,11 +262,11 @@ "@lobehub/chat-plugin-sdk": "^1.32.4", "@lobehub/chat-plugins-gateway": "^1.9.0", "@lobehub/desktop-ipc-typings": "workspace:*", - "@lobehub/editor": "^4.3.1", + "@lobehub/editor": "^4.5.0", "@lobehub/icons": "^5.0.0", "@lobehub/market-sdk": "0.31.11", "@lobehub/tts": "^5.1.2", - "@lobehub/ui": "^5.5.0", + "@lobehub/ui": "^5.6.1", "@modelcontextprotocol/sdk": "^1.26.0", "@napi-rs/canvas": "^0.1.88", "@neondatabase/serverless": "^1.0.2", diff --git a/packages/agent-runtime/src/agents/GeneralChatAgent.ts b/packages/agent-runtime/src/agents/GeneralChatAgent.ts index 43bb1a1225..4191763466 100644 --- a/packages/agent-runtime/src/agents/GeneralChatAgent.ts +++ b/packages/agent-runtime/src/agents/GeneralChatAgent.ts @@ -183,6 +183,14 @@ export class GeneralChatAgent implements Agent { continue; } + // Phase 2.5: Unknown tool guard — if no manifest found, require intervention + // This prevents auto-executing tools the LLM hallucinated or whose name was corrupted + const manifest = state.toolManifestMap?.[identifier]; + if (!manifest) { + toolsNeedingIntervention.push(toolCalling); + continue; + } + // Phase 3: Per-tool dynamic resolver const config = this.getToolInterventionConfig(toolCalling, state); const isDynamicConfig = this.isDynamicInterventionConfig(config); diff --git a/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts b/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts index e65dede506..a1209841a1 100644 --- a/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts +++ b/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts @@ -978,6 +978,9 @@ describe('GeneralChatAgent', () => { const state = createMockState({ status: 'running', // Normal running state + toolManifestMap: { + 'lobe-web-browsing': { identifier: 'lobe-web-browsing' }, + }, }); const context = createMockContext('llm_result', { diff --git a/packages/agent-runtime/src/core/runtime.ts b/packages/agent-runtime/src/core/runtime.ts index 823cded2e2..aae5d53769 100644 --- a/packages/agent-runtime/src/core/runtime.ts +++ b/packages/agent-runtime/src/core/runtime.ts @@ -213,7 +213,12 @@ export class AgentRuntime { return { events: allEvents, newState: currentState, - nextContext: finalNextContext, + // When execution is blocked (waiting for human or interrupted), + // clear nextContext so the outer loop stops instead of continuing + nextContext: + currentState.status === 'waiting_for_human' || currentState.status === 'interrupted' + ? undefined + : finalNextContext, }; } catch (error) { const errorState = structuredClone(state); diff --git a/packages/builtin-agent-onboarding/package.json b/packages/builtin-agent-onboarding/package.json new file mode 100644 index 0000000000..c6d54479ab --- /dev/null +++ b/packages/builtin-agent-onboarding/package.json @@ -0,0 +1,18 @@ +{ + "name": "@lobechat/builtin-agent-onboarding", + "version": "1.0.0", + "private": true, + "exports": { + ".": "./src/index.ts", + "./client": "./src/client/index.ts" + }, + "main": "./src/index.ts", + "devDependencies": { + "@lobechat/types": "workspace:*" + }, + "peerDependencies": { + "@lobehub/ui": "^5", + "antd": "^6", + "react": "*" + } +} diff --git a/packages/builtin-agent-onboarding/src/client/QuestionRenderer.tsx b/packages/builtin-agent-onboarding/src/client/QuestionRenderer.tsx new file mode 100644 index 0000000000..f61f648e68 --- /dev/null +++ b/packages/builtin-agent-onboarding/src/client/QuestionRenderer.tsx @@ -0,0 +1,498 @@ +'use client'; + +import type { + UserAgentOnboardingQuestion, + UserAgentOnboardingQuestionChoice, + UserAgentOnboardingQuestionField, +} from '@lobechat/types'; +import { Button, Flexbox, Input, Select, Text } from '@lobehub/ui'; +import { Input as AntdInput } from 'antd'; +import type { ChangeEvent, ReactNode } from 'react'; +import { memo, useEffect, useMemo, useState } from 'react'; + +type FormValue = string | string[]; + +export interface DefaultModelConfig { + model?: string; + provider?: string; +} + +export interface QuestionRendererRenderEmojiPickerProps { + onChange: (emoji?: string) => void; + value?: string; +} + +export interface QuestionRendererRenderModelSelectProps { + onChange: (value: { model: string; provider: string }) => void; + value?: DefaultModelConfig; +} + +export interface QuestionRendererProps { + currentQuestion: UserAgentOnboardingQuestion; + currentResponseLanguage?: string; + defaultModelConfig?: DefaultModelConfig; + enableKlavis?: boolean; + fixedModelLabel?: ReactNode; + isDev?: boolean; + loading?: boolean; + nextLabel?: ReactNode; + onBeforeInfoContinue?: (question: UserAgentOnboardingQuestion) => Promise | void; + onChangeDefaultModel?: (model: string, provider: string) => void; + onChangeResponseLanguage?: (value: string) => void; + onSendMessage: (message: string) => Promise; + renderEmojiPicker?: (props: QuestionRendererRenderEmojiPickerProps) => ReactNode; + renderKlavisList?: () => ReactNode; + renderModelSelect?: (props: QuestionRendererRenderModelSelectProps) => ReactNode; + responseLanguageOptions?: Array<{ label: string; value: string }>; + submitLabel?: ReactNode; +} + +const getChoiceMessage = (choice: UserAgentOnboardingQuestionChoice) => { + if (choice.payload?.kind === 'message') { + return choice.payload.message || choice.label || undefined; + } + + if (choice.label) { + return choice.label; + } + + return undefined; +}; + +const resolveFieldAnswer = ( + field: UserAgentOnboardingQuestionField, + value: FormValue | undefined, +) => { + if (Array.isArray(value)) { + const optionLabels = value + .map((item) => field.options?.find((option) => option.value === item)?.label || item) + .filter(Boolean); + + return optionLabels.length > 0 ? optionLabels.join(', ') : undefined; + } + + const normalizedValue = String(value || '').trim(); + + if (!normalizedValue) return undefined; + + return ( + field.options?.find((option) => option.value === normalizedValue)?.label || normalizedValue + ); +}; + +const buildQuestionAnswerMessage = ( + fields: UserAgentOnboardingQuestionField[] | undefined, + values: Record, +) => { + const lines = + fields + ?.map((field) => { + const answer = resolveFieldAnswer(field, values[field.key]); + + if (!answer) return undefined; + + return `Q: ${field.label}\nA: ${answer}`; + }) + .filter((line): line is string => Boolean(line)) || []; + + return lines.length > 0 ? lines.join('\n\n') : undefined; +}; + +const renderFieldControl = ({ + field, + onChange, + onSubmit, + renderEmojiPicker, + value, +}: { + field: UserAgentOnboardingQuestionField; + onChange: (nextValue: FormValue) => void; + onSubmit?: () => void; + renderEmojiPicker?: (props: QuestionRendererRenderEmojiPickerProps) => ReactNode; + value: FormValue; +}) => { + switch (field.kind) { + case 'emoji': { + if (renderEmojiPicker) { + return renderEmojiPicker({ + onChange: (emoji) => onChange(emoji || ''), + value: typeof value === 'string' ? value || undefined : undefined, + }); + } + + return ( + onChange(event.target.value)} + /> + ); + } + case 'multiselect': { + return ( + onChange(nextValue)} + /> + ); + } + case 'textarea': { + return ( + ) => onChange(event.target.value)} + /> + ); + } + case 'text': { + return ( + onChange(event.target.value)} + onKeyDown={(event) => { + if (event.key !== 'Enter') return; + + event.preventDefault(); + onSubmit?.(); + }} + /> + ); + } + } +}; + +const QuestionHeader = memo>( + ({ description, prompt }) => { + if (!prompt && !description) return null; + + return ( + + {prompt && {prompt}} + {description && {description}} + + ); + }, +); + +QuestionHeader.displayName = 'QuestionHeader'; + +const QuestionChoices = memo<{ + loading: boolean; + onChoose: (choice: UserAgentOnboardingQuestionChoice) => Promise; + question: UserAgentOnboardingQuestion; +}>(({ loading, onChoose, question }) => ( + + + + {(question.choices || []).map((choice) => ( + + ))} + + +)); + +QuestionChoices.displayName = 'QuestionChoices'; + +const QuestionForm = memo<{ + loading: boolean; + onSendMessage: (message: string) => Promise; + question: UserAgentOnboardingQuestion; + renderEmojiPicker?: (props: QuestionRendererRenderEmojiPickerProps) => ReactNode; + submitLabel: ReactNode; +}>(({ loading, onSendMessage, question, renderEmojiPicker, submitLabel }) => { + const initialValues = useMemo( + () => + Object.fromEntries( + (question.fields || []).map((field) => [ + field.key, + field.value ?? (field.kind === 'multiselect' ? [] : ''), + ]), + ) as Record, + [question.fields], + ); + const [values, setValues] = useState>(initialValues); + + useEffect(() => { + setValues(initialValues); + }, [initialValues]); + + const handleSubmit = async () => { + const message = buildQuestionAnswerMessage(question.fields, values); + + if (!message) return; + + await onSendMessage(message); + }; + + return ( + + + {(question.fields || []).map((field) => ( + + {field.label} + {renderFieldControl({ + field, + onChange: (nextValue) => setValues((state) => ({ ...state, [field.key]: nextValue })), + onSubmit: () => void handleSubmit(), + renderEmojiPicker, + value: values[field.key] ?? '', + })} + + ))} + + + ); +}); + +QuestionForm.displayName = 'QuestionForm'; + +const QuestionSelect = memo<{ + currentResponseLanguage?: string; + loading: boolean; + nextLabel: ReactNode; + onChangeResponseLanguage?: (value: string) => void; + onSendMessage: (message: string) => Promise; + options?: Array<{ label: string; value: string }>; + question: UserAgentOnboardingQuestion; +}>( + ({ + currentResponseLanguage, + loading, + nextLabel, + onChangeResponseLanguage, + onSendMessage, + options, + question, + }) => { + const isLanguageNode = question.node === 'responseLanguage'; + const field = question.fields?.[0]; + const initialValue = + (typeof field?.value === 'string' && field.value) || + (isLanguageNode ? currentResponseLanguage : undefined); + const [value, setValue] = useState(initialValue); + + useEffect(() => { + setValue(initialValue); + }, [initialValue]); + + const resolvedOptions = field?.options || (isLanguageNode ? options : undefined) || []; + + const handleSubmit = async () => { + const message = buildQuestionAnswerMessage( + field ? [{ ...field, options: resolvedOptions }] : undefined, + { [field?.key || 'responseLanguage']: value || '' }, + ); + + if (!message) return; + + await onSendMessage(message); + }; + + return ( + + +