From afc042942fc93a930dbf828987a1c85532d5af84 Mon Sep 17 00:00:00 2001 From: daguanren21 <1363917353@qq.com> Date: Fri, 11 Sep 2026 14:54:37 +0800 Subject: [PATCH 1/2] feat(weapp): upgrade toolchain and add web preview Upgrade wevu, weapp-vite, and weapp-tailwindcss without pinning patch versions, restyle Weapp Agent UI status variants with class modifiers, and add a private Web compatibility preview for compiled mini-program artifacts. --- .changeset/spotty-geckos-flow.md | 5 + .changeset/wevu-seven-components.md | 4 +- apps/docs/.vitepress/config.ts | 2 + apps/docs/.vitepress/theme/agent-docs.css | 14 +- apps/docs/.vitepress/theme/index.ts | 6 +- apps/docs/ai/index.md | 4 +- apps/docs/ai/rag-pipeline.md | 66 + apps/docs/components/map.md | 6 +- apps/docs/components/robot-chat.md | 6 + apps/docs/en/ai/index.md | 4 +- apps/docs/en/ai/rag-pipeline.md | 66 + apps/docs/en/components/map.md | 6 +- apps/docs/en/components/robot-chat.md | 6 + apps/docs/en/guide/installation.md | 25 + apps/docs/guide/installation.md | 25 + apps/docs/src/agent-component-catalog.ts | 6 + .../src/components/AgentComponentDemo.test.ts | 16 +- .../src/components/AgentComponentDemo.vue | 26 +- .../components/AgentComponentsDemo.test.ts | 3 + .../src/components/AgentComponentsDemo.vue | 19 +- apps/docs/src/components/MapDemo.test.ts | 22 + apps/docs/src/components/MapDemo.vue | 337 +++ .../docs/src/components/RobotChatDemo.test.ts | 20 + apps/docs/src/components/RobotChatDemo.vue | 248 +++ .../components/agent-ui/AgentRagPipeline.vue | 1094 ++++++++++ apps/docs/src/components/agent-ui/advanced.ts | 308 ++- .../components/agent-ui/agent-advanced.css | 993 ++++++++- .../docs/src/components/agent-ui/agent-ui.css | 761 ++++++- apps/docs/src/components/agent-ui/index.ts | 422 ++-- .../docs/src/components/agent-ui/rag-icons.ts | 9 + .../src/components/agent-ui/rag-pipeline.ts | 81 + apps/docs/src/components/tencent-map.ts | 78 + .../src/composables/useRagPipelineDemo.ts | 126 ++ .../components/agent-ui/AgentRagPipeline.vue | 26 +- .../src/components/agent-ui/advanced.ts | 329 ++- .../components/agent-ui/agent-advanced.css | 721 ++++++- .../src/components/agent-ui/agent-ui.css | 619 +++++- .../src/components/agent-ui/index.ts | 413 ++-- apps/playground-weapp-preview/index.html | 14 + apps/playground-weapp-preview/package.json | 28 + apps/playground-weapp-preview/runtime.html | 13 + .../scripts/smoke-preview.mjs | 179 ++ apps/playground-weapp-preview/src/App.vue | 344 +++ .../src/components/PreviewFrame.vue | 395 ++++ .../src/components/PreviewToolbar.vue | 267 +++ apps/playground-weapp-preview/src/env.d.ts | 2 + apps/playground-weapp-preview/src/main.ts | 6 + apps/playground-weapp-preview/src/protocol.ts | 52 + .../src/runtime/entry.ts | 74 + .../src/runtime/index.ts | 27 + .../src/runtime/runtime.css | 37 + apps/playground-weapp-preview/src/styles.css | 55 + apps/playground-weapp-preview/tsconfig.json | 9 + apps/playground-weapp-preview/vite.config.ts | 44 + apps/playground-weapp/package.json | 8 +- apps/playground-weapp/src/app.vue | 5 + .../src/components/agent-ui/AgentActivity.vue | 285 ++- .../src/components/agent-ui/AgentArtifact.vue | 119 +- .../components/agent-ui/AgentCitations.vue | 187 +- .../agent-ui/AgentCommandSearch.vue | 181 +- .../agent-ui/AgentComposerScope.vue | 298 ++- .../components/agent-ui/AgentContextCard.vue | 211 +- .../src/components/agent-ui/AgentFineTune.vue | 175 +- .../components/agent-ui/AgentFlowchart.vue | 266 ++- .../components/agent-ui/AgentInsightCard.vue | 224 +- .../components/agent-ui/AgentMarkdownNode.vue | 17 +- .../agent-ui/AgentRetrievalProgress.vue | 305 ++- .../agent-ui/AgentSelectionActions.vue | 66 +- .../src/components/agent-ui/AgentSidebar.vue | 220 +- .../agent-ui/AgentSourceReceipt.vue | 242 ++- .../src/components/agent-ui/AgentTaskList.vue | 319 ++- .../components/agent-ui/AgentTaskRunner.vue | 171 +- .../agent-ui/AgentThreadVersions.vue | 241 ++- .../components/agent-ui/AgentToolResult.vue | 202 +- .../src/components/agent-ui/agent-icons.ts | 10 + .../src/components/ui/v-input.vue | 19 +- .../src/components/ui/v-textarea.vue | 1 + .../src/pages/web-preview-agent/index.vue | 284 +++ .../src/pages/web-preview-map/index.vue | 110 + .../src/pages/web-preview-rag/index.vue | 119 ++ .../pages/web-preview-robot-chat/index.vue | 85 + .../web-preview/PreviewContextConsumer.vue | 47 + .../web-preview/PreviewContextProvider.vue | 40 + .../src/pages/web-preview/index.vue | 282 +++ .../src/pages/web-preview/preview-context.ts | 10 + apps/realworld-weapp/package.json | 6 +- apps/realworld-weapp/pnpm-workspace.yaml | 26 +- package.json | 6 +- .../components/agent-ui/AgentActivity.vue | 287 ++- .../components/agent-ui/AgentApproval.vue | 40 +- .../components/agent-ui/AgentArtifact.vue | 119 +- .../agent-ui/AgentComposerScope.vue | 391 ++++ .../components/agent-ui/AgentFileDiff.vue | 139 +- .../components/agent-ui/AgentFlowchart.vue | 268 ++- .../components/agent-ui/AgentInsightCard.vue | 224 +- .../agent-ui/AgentRetrievalProgress.vue | 333 +++ .../components/agent-ui/AgentSidebar.vue | 222 +- .../agent-ui/AgentSourceReceipt.vue | 312 +++ .../components/agent-ui/AgentTaskList.vue | 318 ++- .../components/agent-ui/AgentTaskRunner.vue | 244 +++ .../agent-ui/AgentThreadVersions.vue | 317 +++ .../components/agent-ui/AgentToolResult.vue | 202 +- .../components/agent-ui/agent-file-diff.css | 75 +- .../components/agent-ui/agent-icons.ts | 25 +- .../components/agent-ui/h5/agent-ui.css | 761 ++++++- .../registry/components/agent-ui/h5/index.ts | 745 ++++++- packages/ui-weapp/README.md | 4 +- packages/ui-weapp/package.json | 2 +- packages/weapp-web/package.json | 65 + packages/weapp-web/src/index.ts | 34 + packages/weapp-web/src/plugin/index.ts | 4 + .../weapp-web/src/plugin/native-artifacts.ts | 1055 +++++++++ packages/weapp-web/src/plugin/options.ts | 35 + .../weapp-web/src/plugin/source-scanners.ts | 340 +++ packages/weapp-web/src/runtime/artifacts.ts | 26 + packages/weapp-web/src/runtime/backend.ts | 77 + packages/weapp-web/src/runtime/harness.ts | 37 + packages/weapp-web/src/runtime/index.ts | 16 + .../weapp-web/src/runtime/module-loader.ts | 59 + packages/weapp-web/src/runtime/mount.ts | 131 ++ .../weapp-web/src/runtime/native-elements.css | 287 +++ .../weapp-web/src/runtime/native-elements.ts | 1882 +++++++++++++++++ packages/weapp-web/src/runtime/native-host.ts | 327 +++ .../weapp-web/src/runtime/native-templates.ts | 13 + packages/weapp-web/src/runtime/session.ts | 6 + packages/weapp-web/src/runtime/tencent-map.ts | 99 + packages/weapp-web/src/runtime/wx-host.ts | 113 + packages/weapp-web/src/virtual.d.ts | 4 + .../weapp-web/tests/module-loader.test.ts | 53 + .../tests/native-artifacts-plugin.test.ts | 65 + .../weapp-web/tests/plugin-options.test.ts | 57 + .../weapp-web/tests/source-scanners.test.ts | 20 + packages/weapp-web/tests/tencent-map.test.ts | 48 + packages/weapp-web/tsconfig.json | 10 + pnpm-lock.yaml | 1702 ++++++++------- registry/component-tiers.v0.1.json | 2 +- .../components/agent-ui/AgentActivity.vue | 285 ++- .../components/agent-ui/AgentArtifact.vue | 119 +- .../components/agent-ui/AgentCitations.vue | 187 +- .../agent-ui/AgentCommandSearch.vue | 181 +- .../agent-ui/AgentComposerScope.vue | 298 ++- .../components/agent-ui/AgentContextCard.vue | 211 +- .../components/agent-ui/AgentFineTune.vue | 175 +- .../components/agent-ui/AgentFlowchart.vue | 266 ++- .../components/agent-ui/AgentInsightCard.vue | 224 +- .../components/agent-ui/AgentMarkdownNode.vue | 17 +- .../agent-ui/AgentRetrievalProgress.vue | 305 ++- .../agent-ui/AgentSelectionActions.vue | 66 +- registry/components/agent-ui/AgentSidebar.vue | 220 +- .../agent-ui/AgentSourceReceipt.vue | 242 ++- .../components/agent-ui/AgentTaskList.vue | 319 ++- .../components/agent-ui/AgentTaskRunner.vue | 171 +- .../agent-ui/AgentThreadVersions.vue | 241 ++- .../components/agent-ui/AgentToolResult.vue | 202 +- registry/components/agent-ui/advanced.ts | 308 ++- .../components/agent-ui/agent-advanced.css | 993 ++++++++- registry/components/agent-ui/agent-icons.ts | 10 + .../agent-ui/h5/AgentRagPipeline.vue | 26 +- registry/components/agent-ui/h5/agent-ui.css | 619 +++++- registry/components/agent-ui/h5/index.ts | 413 ++-- registry/components/input/v-input.vue | 19 +- registry/components/textarea/v-textarea.vue | 1 + stylelint.config.js | 13 + tsconfig.base.json | 3 + turbo.json | 7 + vitest.config.ts | 3 + 166 files changed, 27763 insertions(+), 3231 deletions(-) create mode 100644 .changeset/spotty-geckos-flow.md create mode 100644 apps/docs/ai/rag-pipeline.md create mode 100644 apps/docs/en/ai/rag-pipeline.md create mode 100644 apps/docs/src/components/MapDemo.test.ts create mode 100644 apps/docs/src/components/MapDemo.vue create mode 100644 apps/docs/src/components/RobotChatDemo.test.ts create mode 100644 apps/docs/src/components/RobotChatDemo.vue create mode 100644 apps/docs/src/components/agent-ui/AgentRagPipeline.vue create mode 100644 apps/docs/src/components/agent-ui/rag-icons.ts create mode 100644 apps/docs/src/components/agent-ui/rag-pipeline.ts create mode 100644 apps/docs/src/components/tencent-map.ts create mode 100644 apps/docs/src/composables/useRagPipelineDemo.ts create mode 100644 apps/playground-weapp-preview/index.html create mode 100644 apps/playground-weapp-preview/package.json create mode 100644 apps/playground-weapp-preview/runtime.html create mode 100644 apps/playground-weapp-preview/scripts/smoke-preview.mjs create mode 100644 apps/playground-weapp-preview/src/App.vue create mode 100644 apps/playground-weapp-preview/src/components/PreviewFrame.vue create mode 100644 apps/playground-weapp-preview/src/components/PreviewToolbar.vue create mode 100644 apps/playground-weapp-preview/src/env.d.ts create mode 100644 apps/playground-weapp-preview/src/main.ts create mode 100644 apps/playground-weapp-preview/src/protocol.ts create mode 100644 apps/playground-weapp-preview/src/runtime/entry.ts create mode 100644 apps/playground-weapp-preview/src/runtime/index.ts create mode 100644 apps/playground-weapp-preview/src/runtime/runtime.css create mode 100644 apps/playground-weapp-preview/src/styles.css create mode 100644 apps/playground-weapp-preview/tsconfig.json create mode 100644 apps/playground-weapp-preview/vite.config.ts create mode 100644 apps/playground-weapp/src/pages/web-preview-agent/index.vue create mode 100644 apps/playground-weapp/src/pages/web-preview-map/index.vue create mode 100644 apps/playground-weapp/src/pages/web-preview-rag/index.vue create mode 100644 apps/playground-weapp/src/pages/web-preview-robot-chat/index.vue create mode 100644 apps/playground-weapp/src/pages/web-preview/PreviewContextConsumer.vue create mode 100644 apps/playground-weapp/src/pages/web-preview/PreviewContextProvider.vue create mode 100644 apps/playground-weapp/src/pages/web-preview/index.vue create mode 100644 apps/playground-weapp/src/pages/web-preview/preview-context.ts create mode 100644 packages/cli/registry/components/agent-ui/AgentComposerScope.vue create mode 100644 packages/cli/registry/components/agent-ui/AgentRetrievalProgress.vue create mode 100644 packages/cli/registry/components/agent-ui/AgentSourceReceipt.vue create mode 100644 packages/cli/registry/components/agent-ui/AgentTaskRunner.vue create mode 100644 packages/cli/registry/components/agent-ui/AgentThreadVersions.vue create mode 100644 packages/weapp-web/package.json create mode 100644 packages/weapp-web/src/index.ts create mode 100644 packages/weapp-web/src/plugin/index.ts create mode 100644 packages/weapp-web/src/plugin/native-artifacts.ts create mode 100644 packages/weapp-web/src/plugin/options.ts create mode 100644 packages/weapp-web/src/plugin/source-scanners.ts create mode 100644 packages/weapp-web/src/runtime/artifacts.ts create mode 100644 packages/weapp-web/src/runtime/backend.ts create mode 100644 packages/weapp-web/src/runtime/harness.ts create mode 100644 packages/weapp-web/src/runtime/index.ts create mode 100644 packages/weapp-web/src/runtime/module-loader.ts create mode 100644 packages/weapp-web/src/runtime/mount.ts create mode 100644 packages/weapp-web/src/runtime/native-elements.css create mode 100644 packages/weapp-web/src/runtime/native-elements.ts create mode 100644 packages/weapp-web/src/runtime/native-host.ts create mode 100644 packages/weapp-web/src/runtime/native-templates.ts create mode 100644 packages/weapp-web/src/runtime/session.ts create mode 100644 packages/weapp-web/src/runtime/tencent-map.ts create mode 100644 packages/weapp-web/src/runtime/wx-host.ts create mode 100644 packages/weapp-web/src/virtual.d.ts create mode 100644 packages/weapp-web/tests/module-loader.test.ts create mode 100644 packages/weapp-web/tests/native-artifacts-plugin.test.ts create mode 100644 packages/weapp-web/tests/plugin-options.test.ts create mode 100644 packages/weapp-web/tests/source-scanners.test.ts create mode 100644 packages/weapp-web/tests/tencent-map.test.ts create mode 100644 packages/weapp-web/tsconfig.json diff --git a/.changeset/spotty-geckos-flow.md b/.changeset/spotty-geckos-flow.md new file mode 100644 index 00000000..85802d16 --- /dev/null +++ b/.changeset/spotty-geckos-flow.md @@ -0,0 +1,5 @@ +--- +"@varo-ui/cli": patch +--- + +Fix native input length defaults and recursive Markdown layout and accessibility. diff --git a/.changeset/wevu-seven-components.md b/.changeset/wevu-seven-components.md index c9ccaac3..f76c08db 100644 --- a/.changeset/wevu-seven-components.md +++ b/.changeset/wevu-seven-components.md @@ -1,6 +1,6 @@ --- -"@varo-ui/weapp": major +"@varo-ui/weapp": patch "@varo-ui/cli": patch --- -Require the Wevu 7.0.4 and weapp-vite 7.0.4 toolchain, adopt the built-in weapp-tailwindcss 5.5.1 pipeline, and preserve typed editable components with native-safe nullable and union prop descriptors. +Upgrade the Wevu 7 and weapp-vite 7 toolchain without pinning patch versions, keep native-safe nullable and union prop descriptors, and restyle Weapp Agent UI status variants with class modifiers. diff --git a/apps/docs/.vitepress/config.ts b/apps/docs/.vitepress/config.ts index 76d70ebc..dfd4750d 100644 --- a/apps/docs/.vitepress/config.ts +++ b/apps/docs/.vitepress/config.ts @@ -195,6 +195,7 @@ export default defineConfig({ { text: 'AgentAttachmentList', link: '/ai/attachments' }, { text: 'AgentSourceList', link: '/ai/sources' }, { text: 'AgentCitations', link: '/ai/citations' }, + { text: 'AgentRagPipeline', link: '/ai/rag-pipeline' }, { text: 'AgentContextCard', link: '/ai/context-card' }, { text: 'AgentCodeBlock', link: '/ai/code-block' }, { text: 'AgentFileDiff', link: '/ai/file-diff' }, @@ -407,6 +408,7 @@ export default defineConfig({ { text: 'AgentAttachmentList', link: '/en/ai/attachments' }, { text: 'AgentSourceList', link: '/en/ai/sources' }, { text: 'AgentCitations', link: '/en/ai/citations' }, + { text: 'AgentRagPipeline', link: '/en/ai/rag-pipeline' }, { text: 'AgentContextCard', link: '/en/ai/context-card' }, { text: 'AgentCodeBlock', link: '/en/ai/code-block' }, { text: 'AgentFileDiff', link: '/en/ai/file-diff' }, diff --git a/apps/docs/.vitepress/theme/agent-docs.css b/apps/docs/.vitepress/theme/agent-docs.css index eadfb681..a58f45ae 100644 --- a/apps/docs/.vitepress/theme/agent-docs.css +++ b/apps/docs/.vitepress/theme/agent-docs.css @@ -28,11 +28,9 @@ .dark .agent-component-demo .agent-message-scroller, .dark .agent-component-demo .agent-file-diff, -.dark .agent-component-demo .agent-tool-result, .dark .agent-component-demo .agent-citations, .dark .agent-component-demo .agent-activity, .dark .agent-component-demo .agent-context-card, -.dark .agent-component-demo .agent-insight-card, .dark .agent-component-demo .agent-tool-approval, .dark .agent-component-demo .agent-fine-tune, .dark .agent-component-demo .agent-sidebar, @@ -45,13 +43,13 @@ border-color: var(--varo-border) !important; } -.dark .agent-component-demo .agent-sidebar > header, -.dark .agent-component-demo .agent-flowchart > header, -.dark .agent-component-demo .agent-command-search > label, +.dark .agent-component-demo .agent-sidebar__header, +.dark .agent-component-demo .agent-flowchart__header, +.dark .agent-component-demo .agent-command-search__field, .dark .agent-component-demo .agent-activity > header, .dark .agent-component-demo .agent-context-card > header, .dark .agent-component-demo .agent-insight-card > header, -.dark .agent-component-demo .agent-fine-tune > header { +.dark .agent-component-demo .agent-fine-tune__header { color: var(--varo-foreground) !important; background: var(--varo-surface-strong) !important; border-color: var(--varo-border) !important; @@ -77,12 +75,10 @@ .dark .agent-component-demo .agent-radio-group__item, .dark .agent-component-demo .agent-file-diff__header, -.dark .agent-component-demo .agent-tool-result__header, .dark .agent-component-demo .agent-citations__trigger, -.dark .agent-component-demo .agent-context-card article, .dark .agent-component-demo .agent-sidebar__item[data-active='true'], .dark .agent-component-demo .agent-flowchart__node, -.dark .agent-component-demo .agent-command-search > div button:hover { +.dark .agent-component-demo .agent-command-search__item:hover { color: var(--varo-foreground); background: var(--varo-surface-strong); border-color: var(--varo-border); diff --git a/apps/docs/.vitepress/theme/index.ts b/apps/docs/.vitepress/theme/index.ts index 6cb44581..a33519e5 100644 --- a/apps/docs/.vitepress/theme/index.ts +++ b/apps/docs/.vitepress/theme/index.ts @@ -31,14 +31,15 @@ import AgentComponentsDemo from '../../src/components/AgentComponentsDemo.vue' import ComponentCatalog from '../../src/components/ComponentCatalog.vue' import FormComponentDemo from '../../src/components/FormComponentDemo.vue' import InteractivePreview from '../../src/components/InteractivePreview.vue' +import MapDemo from '../../src/components/MapDemo.vue' import MiniProgramBlocksGallery from '../../src/components/MiniProgramBlocksGallery.vue' import PlatformTabsDemo from '../../src/components/PlatformTabsDemo.vue' import PrimitiveCatalog from '../../src/components/PrimitiveCatalog.vue' import PrimitiveExample from '../../src/components/PrimitiveExample.vue' import PrimitiveInteractionDemo from '../../src/components/PrimitiveInteractionDemo.vue' -import RegionPickerDemo from '../../src/components/RegionPickerDemo.vue' import RegistryCoverageEvidence from '../../src/components/RegistryCoverageEvidence.vue' import RegistryInstallStrip from '../../src/components/RegistryInstallStrip.vue' +import RobotChatDemo from '../../src/components/RobotChatDemo.vue' import DocsLayout from './DocsLayout.vue' import '@varo-ui/h5/source/style.css' import './tailwind.css' @@ -55,8 +56,9 @@ const theme: Theme = { app.component('InteractivePreview', InteractivePreview) app.component('ComponentCatalog', ComponentCatalog) app.component('MiniProgramBlocksGallery', MiniProgramBlocksGallery) + app.component('MapDemo', MapDemo) + app.component('RobotChatDemo', RobotChatDemo) app.component('PlatformTabsDemo', PlatformTabsDemo) - app.component('RegionPickerDemo', RegionPickerDemo) app.component('PrimitiveCatalog', PrimitiveCatalog) app.component('PrimitiveExample', PrimitiveExample) app.component('RegistryInstallStrip', RegistryInstallStrip) diff --git a/apps/docs/ai/index.md b/apps/docs/ai/index.md index 8b96fa04..0d4c19b8 100644 --- a/apps/docs/ai/index.md +++ b/apps/docs/ai/index.md @@ -71,7 +71,7 @@ pnpm dlx @varo-ui/cli add --target weapp blocks/agent-workspace | [AgentSourceList](./sources) | 来源列表 | | [AgentCitations](./citations) | 可折叠引用集合 | | `AgentRetrievalProgress` | 可见检索队列与失败重试 | -| `AgentRagPipeline` | 五阶段 RAG、上下文组装与流式引用联动 | +| [AgentRagPipeline](./rag-pipeline) | 五阶段 RAG、上下文组装与流式引用联动 | | `AgentSourceReceipt` | 回答完成后的来源读取回执 | | [AgentContextCard](./context-card) | 检索知识块 | | [AgentCodeBlock](./code-block) | 独立流式代码块 | @@ -119,7 +119,7 @@ pnpm dlx @varo-ui/cli add --target weapp blocks/agent-workspace H5 自动遵循 `prefers-reduced-motion`,小程序同时提供 `reducedMotion` 属性与媒体查询样式。动效使用主题中的 `--varo-agent-motion-*` 变量以及 transform/opacity,不引入动画运行时依赖。 -`AgentWorkspace` 的 `execution` 插槽可放入此组件,替换默认的对话、检索、任务与回执区域;来源授权、版本与输入区仍由 Workspace 组合。Weapp 的 `weapp-vite` 配置应设置 `weapp.vue.template.scopedSlotsRequireProps: true`,让没有插槽参数的 `#execution` 保持普通命名插槽,避免 7.0.4 的泛型插槽自动 `setData.pick` 遗漏父级状态。两端 playground 提供可取消、重播的演示数据流程。 +`AgentWorkspace` 的 `execution` 插槽可放入此组件,替换默认的对话、检索、任务与回执区域;来源授权、版本与输入区仍由 Workspace 组合。Weapp 的 `weapp-vite` 配置应设置 `weapp.vue.template.scopedSlotsRequireProps: true`,让没有插槽参数的 `#execution` 保持普通命名插槽,避免泛型插槽自动 `setData.pick` 遗漏父级状态。两端 playground 提供可取消、重播的演示数据流程。 ## 完整 Block diff --git a/apps/docs/ai/rag-pipeline.md b/apps/docs/ai/rag-pipeline.md new file mode 100644 index 00000000..7c51fed4 --- /dev/null +++ b/apps/docs/ai/rag-pipeline.md @@ -0,0 +1,66 @@ +# AgentRagPipeline + +五阶段检索增强生成:进度、来源卡片,以及可追溯到来源的引用回答。 + +## 案例 + + + +## 安装 + +```bash +pnpm add @varo-ui/ai +pnpm dlx @varo-ui/cli add --target h5 components/agent-ui +pnpm dlx @varo-ui/cli add --target weapp components/agent-ui +``` + +UI 组件由 Registry 安装到项目本地,因此从 `@/components/agent-ui` 导入;`@varo-ui/ai` 只提供事件协议、流控制和 Markdown 能力,不导出 Vue/Wevu UI 组件。`AgentRagPipeline` 只投影受控快照,不执行检索、模型请求或来源授权。 + +## 基础用法 + +```vue + + + +``` + +## Props + +| Prop | Type | Default | 说明 | +| --------------- | ---------------------- | -------------- | ------------------ | +| `query` | `string` | `''` | 当前查询 | +| `steps` | `AgentRagStep[]` | `[]` | 五阶段进度 | +| `sources` | `AgentRagSource[]` | `[]` | 检索来源 | +| `answer` | `AgentRagAnswerPart[]` | `[]` | 文本片段与引用来源 | +| `title` | `string` | `检索增强生成` | 标题 | +| `elapsedMs` | `number` | `undefined` | 总耗时 | +| `reducedMotion` | `boolean` | `false` | 关闭非必要动效 | + +## Events + +| Event | Payload | 说明 | +| -------------- | ---------------- | ------------ | +| `run` | `void` | 开始或重跑 | +| `cancel` | `void` | 停止当前流程 | +| `selectSource` | `AgentRagSource` | 选中某个来源 | + +## 平台差异 + +| Target | Import | +| ------ | ------------------------------------------------------- | +| H5 | Named export from `@/components/agent-ui` | +| weapp | 默认导出自 `@/components/agent-ui/AgentRagPipeline.vue` | + +组件 API 在两个目标保持一致;DOM/WXML、调度和原生事件由目标实现负责。文档演示使用本地快照,不调用模型或检索服务。 diff --git a/apps/docs/components/map.md b/apps/docs/components/map.md index 32298f71..ce42c344 100644 --- a/apps/docs/components/map.md +++ b/apps/docs/components/map.md @@ -2,7 +2,11 @@ `VMap` 是微信小程序原生 `` 的类型化 Wevu 封装,统一尺寸、地图能力和事件命名。该组件仅支持 `weapp` target;业务接口、位置权限和地图 Context 仍由页面负责。 - +## 演示 + + + +文档站不能跑微信客户端同层地图。演示用腾讯地图 JS API GL 展示同一套 VMap 契约:中心点、标记和 `regionchange`。定位权限和 Map Context 仍只在小程序运行时可用。 ## 使用 diff --git a/apps/docs/components/robot-chat.md b/apps/docs/components/robot-chat.md index 02694cea..dac2d7a0 100644 --- a/apps/docs/components/robot-chat.md +++ b/apps/docs/components/robot-chat.md @@ -4,6 +4,12 @@ +## 演示 + + + +文档站不能加载 chatbotwidget。演示画出欢迎语、operateCard、发送和 `queryCallback`。这是 Web Preview 的可见宿主,不是微信对话开放平台。 + ## 前置配置 小程序需先开通插件,并在 `app.json` 中使用固定别名 `varoRobot`: diff --git a/apps/docs/en/ai/index.md b/apps/docs/en/ai/index.md index 601af693..9d95699f 100644 --- a/apps/docs/en/ai/index.md +++ b/apps/docs/en/ai/index.md @@ -71,7 +71,7 @@ pnpm dlx @varo-ui/cli add --target weapp blocks/agent-workspace | [AgentSourceList](./sources) | Source list | | [AgentCitations](./citations) | Collapsible citations | | `AgentRetrievalProgress` | Visible retrieval queue and failed-item retry | -| `AgentRagPipeline` | Five-stage RAG, context assembly, and linked streaming citations | +| [AgentRagPipeline](./rag-pipeline) | Five-stage RAG, context assembly, and linked streaming citations | | `AgentSourceReceipt` | Post-answer source read receipt | | [AgentContextCard](./context-card) | Retrieved knowledge chunks | | [AgentCodeBlock](./code-block) | Standalone streaming code block | @@ -119,7 +119,7 @@ A newly arriving citation briefly highlights its source and context segment. H5 H5 follows `prefers-reduced-motion` automatically. Weapp provides the `reducedMotion` prop and media-query styles. Motion uses theme `--varo-agent-motion-*` variables with transform/opacity and adds no animation runtime dependency. -Place the component in the `AgentWorkspace` `execution` slot to replace its default conversation, retrieval, task, and receipt region while retaining source permissions, thread versions, and input. In Weapp, set `weapp.vue.template.scopedSlotsRequireProps: true` in the `weapp-vite` configuration so a `#execution` slot without scope parameters remains an ordinary named slot. This avoids version 7.0.4's generic-slot automatic `setData.pick` omitting parent state. Both playgrounds provide a cancellable, replayable demo-data flow. +Place the component in the `AgentWorkspace` `execution` slot to replace its default conversation, retrieval, task, and receipt region while retaining source permissions, thread versions, and input. In Weapp, set `weapp.vue.template.scopedSlotsRequireProps: true` in the `weapp-vite` configuration so a `#execution` slot without scope parameters remains an ordinary named slot. This avoids generic-slot automatic `setData.pick` omitting parent state. Both playgrounds provide a cancellable, replayable demo-data flow. ## Complete Block diff --git a/apps/docs/en/ai/rag-pipeline.md b/apps/docs/en/ai/rag-pipeline.md new file mode 100644 index 00000000..4975e129 --- /dev/null +++ b/apps/docs/en/ai/rag-pipeline.md @@ -0,0 +1,66 @@ +# AgentRagPipeline + +Five-stage retrieval-augmented generation: progress, source cards, and citations that point back to sources. + +## Demo + + + +## Install + +```bash +pnpm add @varo-ui/ai +pnpm dlx @varo-ui/cli add --target h5 components/agent-ui +pnpm dlx @varo-ui/cli add --target weapp components/agent-ui +``` + +Registry installs the UI component into your project, so import it from `@/components/agent-ui`; `@varo-ui/ai` provides the event protocol, stream controller, and Markdown primitives—not Vue/Wevu UI components. `AgentRagPipeline` projects a controlled snapshot; it does not retrieve, call a model, or authorize sources. + +## Basic Usage + +```vue + + + +``` + +## Props + +| Prop | Type | Default | Description | +| --------------- | ---------------------- | -------------- | ----------------------------------- | +| `query` | `string` | `''` | Current query | +| `steps` | `AgentRagStep[]` | `[]` | Five-stage progress | +| `sources` | `AgentRagSource[]` | `[]` | Retrieved sources | +| `answer` | `AgentRagAnswerPart[]` | `[]` | Text fragments and source citations | +| `title` | `string` | `检索增强生成` | Title | +| `elapsedMs` | `number` | `undefined` | Elapsed time | +| `reducedMotion` | `boolean` | `false` | Disable non-essential motion | + +## Events + +| Event | Payload | Description | +| -------------- | ---------------- | -------------------- | +| `run` | `void` | Start or replay | +| `cancel` | `void` | Stop the current run | +| `selectSource` | `AgentRagSource` | Select a source | + +## Target Notes + +| Target | Import | +| ------ | ---------------------------------------------------------------- | +| H5 | Named export from `@/components/agent-ui` | +| weapp | Default export from `@/components/agent-ui/AgentRagPipeline.vue` | + +The public API stays aligned across targets; DOM/WXML, scheduling, and native events are target-owned. The docs demo uses a local snapshot and does not call a model or retrieval service. diff --git a/apps/docs/en/components/map.md b/apps/docs/en/components/map.md index 935a324e..0d441a83 100644 --- a/apps/docs/en/components/map.md +++ b/apps/docs/en/components/map.md @@ -2,7 +2,11 @@ `VMap` is a typed Wevu wrapper around the native WeChat mini-program ``. It normalizes sizing, capabilities, and event names. The component only supports the `weapp` target; application code still owns remote APIs, permission flows, and Map Context usage. - +## Demo + + + +The docs site cannot run WeChat's native map view. This demo uses Tencent Map JS API GL to show the same VMap contract: center, marker count, and `regionchange`. Location permission and Map Context remain mini-program-only. ## Usage diff --git a/apps/docs/en/components/robot-chat.md b/apps/docs/en/components/robot-chat.md index 655c0e9d..43abf7e3 100644 --- a/apps/docs/en/components/robot-chat.md +++ b/apps/docs/en/components/robot-chat.md @@ -4,6 +4,12 @@ +## Demo + + + +The docs site cannot load chatbotwidget. This demo shows welcome text, the operate card, send, and `queryCallback`. It is the Web Preview host, not WeChat Dialog Open Platform. + ## App configuration Enable the plugin for the Mini Program, then declare it under the fixed `varoRobot` alias in `app.json`: diff --git a/apps/docs/en/guide/installation.md b/apps/docs/en/guide/installation.md index 94e50e8b..ada98b36 100644 --- a/apps/docs/en/guide/installation.md +++ b/apps/docs/en/guide/installation.md @@ -73,6 +73,31 @@ Copy-owned mini-program Registry components use native Wevu SFCs and consume Tai `@varo-ui/cli` only copies Registry files and prints `Dependencies:` / `Dev dependencies:`; it does not install npm packages. After each `add`, install every reported dependency that the project does not already contain. See the [one-time Wevu Registry setup](/en/guide/shadcn-mode) for managed registration of `src/styles.css` and the Registry theme at `src/styles/varo.css`, plus the complete Tailwind options. +## Preview mini-program artifacts in a browser + +Start the standalone Web compatibility preview from the Varo repository root: + +```bash +pnpm --filter @varo/playground-weapp-preview dev +``` + +The development command builds the mini program first, then serves button, controlled-input, slot/lifecycle, and Agent scenarios at `http://127.0.0.1:5182`. After changing native examples, run `pnpm --filter @varo/playground-weapp-preview prepare:artifacts` to refresh their compiled artifacts. + +The wx runtime now lives in the private `@varo/weapp-web` package: a Vite plugin compiles Wevu artifacts into `virtual:varo-native-artifacts`, and a replaceable harness owns `wx` APIs and native elements. The playground is only a sandbox consumer; the package is not published yet. The host runs Wevu-generated JS, JSON, WXML, and WXSS through glass-easel's DOM backend rather than substituting H5 business components. Narrow windows scale the presentation while preserving the selected native viewport width. **This is not a WeChat-client or device emulator.** Unsupported capabilities such as login and payment fail explicitly instead of returning fabricated success. + +Build and verify the production surface: + +```bash +pnpm exec turbo run build --filter=@varo/playground-weapp-preview +pnpm --filter @varo/playground-weapp-preview preview +# Run in another terminal; a local Chrome installation is required +pnpm --filter @varo/playground-weapp-preview smoke:browser +``` + +The production preview defaults to `http://127.0.0.1:4182`; deployable files are in `apps/playground-weapp-preview/dist`. The browser smoke exercises native interaction, event counts, context, stream stop/resume, layout boundaries, and error recovery, and writes screenshots to a temporary directory. Set `PREVIEW_URL` to test another running preview. + +The repository lockfile records the SDK versions required by this pipeline. The CLI does not automatically install those dependencies into external consumer projects. WeChat-specific capabilities, device performance, and final native visuals still require validation in WeChat. + ## Engineering notes - Keep docs, playgrounds, and packages in the monorepo diff --git a/apps/docs/guide/installation.md b/apps/docs/guide/installation.md index 105add75..11fde709 100644 --- a/apps/docs/guide/installation.md +++ b/apps/docs/guide/installation.md @@ -73,6 +73,31 @@ copy-owned 小程序 Registry 组件使用真正的 Wevu SFC,并通过 `styleI `@varo-ui/cli` 只复制 Registry 文件并输出 `Dependencies:` / `Dev dependencies:`,不会安装 npm 包;每次执行 `add` 后都要安装它报告且工程尚未包含的依赖。`src/styles.css`、Registry 主题 `src/styles/varo.css` 的托管注册和完整 Tailwind 选项见 [Wevu Registry 一次性接入](/guide/shadcn-mode)。 +## 在浏览器预览小程序产物 + +在 Varo 仓库根目录启动独立的 Web 兼容预览: + +```bash +pnpm --filter @varo/playground-weapp-preview dev +``` + +开发服务先构建小程序,再在 `http://127.0.0.1:5182` 提供按钮、受控输入、插槽生命周期和 Agent 内容场景。修改原生示例后,可运行 `pnpm --filter @varo/playground-weapp-preview prepare:artifacts` 更新编译产物。 + +wx 运行时已抽到私有包 `@varo/weapp-web`:Vite 插件负责把 Wevu 产物编进 `virtual:varo-native-artifacts`,运行时 harness 可替换 `wx` API 和原生元素。当前 playground 只是这个包的沙盒消费者;包暂不对外发布。该宿主使用 glass-easel 的 DOM 后端运行 Wevu 生成的 JS、JSON、WXML 和 WXSS,不会换成 H5 业务组件。窄窗口会缩放预览画面,但保留选定的原生视口宽度。**这不是微信客户端或真机模拟器**;登录、支付等未支持能力会失败,不会伪造成功结果。 + +生产构建与真实浏览器回归: + +```bash +pnpm exec turbo run build --filter=@varo/playground-weapp-preview +pnpm --filter @varo/playground-weapp-preview preview +# 在另一个终端运行;需要本机安装 Chrome +pnpm --filter @varo/playground-weapp-preview smoke:browser +``` + +生产预览默认使用 `http://127.0.0.1:4182`,可部署产物位于 `apps/playground-weapp-preview/dist`。浏览器回归覆盖原生交互、事件次数、上下文、流式取消/继续、样式边界与错误恢复,并将截图写入临时目录。可用 `PREVIEW_URL` 指定其他已启动的预览地址。 + +仓库 lockfile 记录该运行链需要的 SDK 版本;CLI 不会把这些依赖安装到外部业务项目。微信专属能力、真机性能与最终视觉仍需在微信环境验收。 + ## 工程化建议 - 文档站、playground 与组件包统一放在 monorepo 内维护 diff --git a/apps/docs/src/agent-component-catalog.ts b/apps/docs/src/agent-component-catalog.ts index f1caf3ed..3b6a9c97 100644 --- a/apps/docs/src/agent-component-catalog.ts +++ b/apps/docs/src/agent-component-catalog.ts @@ -114,6 +114,12 @@ const markdownContent = '## Result\\n\\n**Ready**' example: '', code: '\n\n', }, + 'rag-pipeline': { + name: 'AgentRagPipeline', + importPath: '@/components/agent-ui', + example: '', + code: '\n\n', + }, 'attachments': { name: 'AgentAttachmentList', importPath: '@/components/agent-ui', diff --git a/apps/docs/src/components/AgentComponentDemo.test.ts b/apps/docs/src/components/AgentComponentDemo.test.ts index 9f289553..b9a3a5a6 100644 --- a/apps/docs/src/components/AgentComponentDemo.test.ts +++ b/apps/docs/src/components/AgentComponentDemo.test.ts @@ -1,4 +1,4 @@ -import { mount } from '@vue/test-utils' +import { flushPromises, mount } from '@vue/test-utils' import { describe, expect, it } from 'vitest' import { agentDemoCatalog } from '../agent-component-catalog' import AgentComponentDemo from './AgentComponentDemo.vue' @@ -20,6 +20,7 @@ const components = [ 'response-actions', 'artifact', 'sources', + 'rag-pipeline', 'attachments', 'event-renderer', 'message-scroller', @@ -90,6 +91,19 @@ describe('AgentComponentDemo', () => { expect(wrapper.get('.agent-component-demo__source').text()).toContain(':suggestions=\"suggestions\"') }) + it('reruns a completed RAG pipeline from the footer action', async () => { + const wrapper = mount(AgentComponentDemo, { props: { component: 'rag-pipeline' } }) + expect(wrapper.get('[data-rag-stage="generate"]').attributes('data-status')).toBe('completed') + expect(wrapper.get('[data-rag-source="support"]').text()).toContain('客服工单') + await wrapper.get('[data-rag-citation="support"]').trigger('click') + expect(wrapper.get('output').text()).toBe('客服工单') + + await wrapper.get('.agent-rag__action').trigger('click') + await flushPromises() + expect(wrapper.get('[data-rag-stage="query"]').attributes('data-status')).not.toBe('completed') + expect(wrapper.get('.agent-rag__action').text()).not.toBe('重新运行') + }) + it('renders a neutral approval surface with explicit selected and decision states', async () => { const wrapper = mount(AgentComponentDemo, { props: { component: 'approval' } }) diff --git a/apps/docs/src/components/AgentComponentDemo.vue b/apps/docs/src/components/AgentComponentDemo.vue index 26940e57..a44b6c72 100644 --- a/apps/docs/src/components/AgentComponentDemo.vue +++ b/apps/docs/src/components/AgentComponentDemo.vue @@ -2,6 +2,7 @@ import type { AgentStreamSnapshot } from '@varo-ui/ai' import { computed, nextTick, shallowRef } from 'vue' import { agentDemoCatalog } from '../agent-component-catalog' +import { useRagPipelineDemo } from '../composables/useRagPipelineDemo' import { AgentActivity, AgentApproval, @@ -27,6 +28,7 @@ import { AgentMessageScroller, AgentPromptSuggestions, AgentRadioGroup, + AgentRagPipeline, AgentRecommendation, AgentRecordsTable, AgentResponseActions, @@ -89,6 +91,7 @@ const radioValue = shallowRef('balanced') const approvalValue = shallowRef('verify') const prompt = shallowRef('') const feedback = shallowRef('') +const { snapshot: ragSnapshot, run: runRag, cancel: cancelRag } = useRagPipelineDemo(props.locale) const sidebarActive = shallowRef('release') const sidebarCollapsed = shallowRef(false) const insightCurrent = shallowRef(0) @@ -115,9 +118,9 @@ const tools = [ { id: 'publish', name: 'npm.publish', status: 'waiting' as const, summary: '等待审批' }, ] const tasks = [ - { id: 'typecheck', title: '类型检查', status: 'completed' as const, progress: 100, meta: '16/16' }, - { id: 'test', title: '行为测试', status: 'completed' as const, progress: 100, meta: 'passed' }, - { id: 'build', title: '双端构建', status: 'running' as const, progress: 72, meta: '2 targets' }, + { id: 'typecheck', title: '类型检查', status: 'completed' as const, progress: 100, meta: '16/16', description: 'workspace typecheck 已通过。' }, + { id: 'test', title: '行为测试', status: 'completed' as const, progress: 100, meta: 'passed', description: 'Vitest 覆盖双端组件行为。' }, + { id: 'build', title: '双端构建', status: 'running' as const, progress: 72, meta: '2 targets', description: 'H5 与 Weapp 产物正在打包。' }, ] const choices = [ { label: '仅验证', value: 'verify', description: '不产生外部副作用' }, @@ -186,6 +189,7 @@ const contextChunks = [ { content: 'Cold-chain certification must be verified before a new supplier can be approved.', id: 'policy', label: 'Supplier policy', source: 'Onboarding SOP.pdf', sourceType: 'PDF' }, { content: 'H5 and weapp-vite builds both completed successfully.', id: 'build', label: 'Build report', source: 'CI report.json', sourceType: 'JSON' }, ] + const insights = [ { action: 'Rebalance', description: '小程序首屏包体比上次降低 6%。', id: 'bundle', label: 'Bundle insight', tone: 'success' as const, value: '−6%' }, { action: 'Inspect', description: '两个工具调用仍在等待用户审批。', id: 'approval', label: 'Workflow insight', tone: 'warning' as const, value: '2' }, @@ -319,6 +323,7 @@ function handleDemoTabKeydown(event: KeyboardEvent) { + @@ -637,11 +642,9 @@ function handleDemoTabKeydown(event: KeyboardEvent) { :deep( .agent-message-scroller, .agent-file-diff, - .agent-tool-result, .agent-citations, .agent-activity, .agent-context-card, - .agent-insight-card, .agent-tool-approval, .agent-fine-tune, .agent-sidebar, @@ -659,19 +662,20 @@ function handleDemoTabKeydown(event: KeyboardEvent) { .agent-component-demo__stage :deep( .agent-file-diff__header, - .agent-tool-result__header, .agent-citations__trigger, - .agent-sidebar > header, - .agent-command-search > label, - .agent-table th, - .agent-context-card article + .agent-sidebar__header, + .agent-command-search__field, + .agent-table th ) { color: var(--varo-foreground); background: var(--varo-surface-strong); border-color: var(--varo-border); } -:global(.dark) .agent-component-demo__stage :deep(button:not(.agent-code-block button)) { +:global(.dark) + .agent-component-demo__stage + :deep( + button:not(.agent-code-block button, .agent-rag__source, .agent-tool-result__header, .agent-tool-result__retry, .agent-artifact__open, .agent-insight-card__action, .agent-insight-card__nav-btn, .agent-selection-actions__action, .agent-sidebar__item, .agent-sidebar__create, .agent-command-search__item)) { color: inherit; } diff --git a/apps/docs/src/components/AgentComponentsDemo.test.ts b/apps/docs/src/components/AgentComponentsDemo.test.ts index cab55d03..5125691c 100644 --- a/apps/docs/src/components/AgentComponentsDemo.test.ts +++ b/apps/docs/src/components/AgentComponentsDemo.test.ts @@ -27,6 +27,9 @@ describe('AgentComponentsDemo', () => { await wrapper.get('.ai-docs-demo__follow').trigger('click') await vi.advanceTimersByTimeAsync(20) expect(transcriptElement.scrollTop).toBe(900) + expect(wrapper.get('[data-rag-stage="generate"]').attributes('data-status')).toBe('completed') + expect(wrapper.get('[data-rag-source="support"]').text()).toContain('客服工单') + wrapper.unmount() }) }) diff --git a/apps/docs/src/components/AgentComponentsDemo.vue b/apps/docs/src/components/AgentComponentsDemo.vue index 3f0a260f..aea7a584 100644 --- a/apps/docs/src/components/AgentComponentsDemo.vue +++ b/apps/docs/src/components/AgentComponentsDemo.vue @@ -2,12 +2,14 @@ import { nextTick, onBeforeUnmount, shallowRef, useTemplateRef, watch } from 'vue' import { agentInventory } from '../agent-component-catalog' import { useAgentDocsDemo } from '../composables/useAgentDocsDemo' +import { useRagPipelineDemo } from '../composables/useRagPipelineDemo' import { AgentArtifact, AgentAttachmentList, AgentComposer, AgentConversation, AgentEventRenderer, + AgentRagPipeline, AgentRecommendation, AgentResponseActions, AgentSourceList, @@ -18,6 +20,7 @@ type Locale = 'en' | 'zh' const props = withDefaults(defineProps<{ locale?: Locale }>(), { locale: 'zh' }) const { approve, busy, messages, prompt, reject, retry, run, snapshot } = useAgentDocsDemo() +const { snapshot: ragSnapshot, run: runRag, cancel: cancelRag } = useRagPipelineDemo(props.locale) const transcript = useTemplateRef('transcript') const followsLatest = shallowRef(true) @@ -75,9 +78,9 @@ onBeforeUnmount(() => { }) const tasks = [ - { id: 'protocol', title: '统一事件协议', status: 'completed' as const, progress: 100 }, - { id: 'renderer', title: '双端增量渲染', status: 'completed' as const, progress: 100 }, - { id: 'approval', title: '人工审批门禁', status: 'running' as const, progress: 72 }, + { id: 'protocol', title: '统一事件协议', status: 'completed' as const, progress: 100, description: '业务只提供事件来源。' }, + { id: 'renderer', title: '双端增量渲染', status: 'completed' as const, progress: 100, description: 'H5 与 Weapp 共用状态投影。' }, + { id: 'approval', title: '人工审批门禁', status: 'running' as const, progress: 72, description: '等待发布动作确认。' }, ] const artifact = { content: `const controller = createAgentStreamController()\nawait controller.connect(events)`, @@ -175,6 +178,16 @@ function t(zh: string, en: string) { + diff --git a/apps/docs/src/components/MapDemo.test.ts b/apps/docs/src/components/MapDemo.test.ts new file mode 100644 index 00000000..97bbbdd4 --- /dev/null +++ b/apps/docs/src/components/MapDemo.test.ts @@ -0,0 +1,22 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' +import MapDemo from './MapDemo.vue' + +describe('MapDemo', () => { + it('exposes map center, markers, and regionchange as observable state', async () => { + const wrapper = mount(MapDemo, { props: { locale: 'zh' } }) + expect(wrapper.get('[data-preview-field="map-center"]').text()).toContain('杭州西湖') + expect(wrapper.get('[data-preview-field="map-coordinates"]').text()).toContain('30.274') + expect(wrapper.get('[data-preview-field="map-marker-count"]').text()).toBe('1') + expect(wrapper.get('[data-preview-field="map-state"]').attributes('data-preview-value')).toContain('region=0') + + await wrapper.get('.map-demo__surface').trigger('click') + expect(wrapper.get('[data-preview-field="map-region-count"]').text()).toBe('1') + expect(wrapper.get('[data-preview-field="map-last-event"]').text()).toContain('regionchange #1') + + await wrapper.get('.map-demo__actions button').trigger('click') + expect(wrapper.get('[data-preview-field="map-center"]').text()).toContain('上海外滩') + expect(wrapper.get('[data-preview-field="map-coordinates"]').text()).toContain('31.23') + expect(wrapper.get('[data-preview-field="map-state"]').attributes('data-preview-value')).toContain('center=上海外滩') + }) +}) diff --git a/apps/docs/src/components/MapDemo.vue b/apps/docs/src/components/MapDemo.vue new file mode 100644 index 00000000..34c9fc7f --- /dev/null +++ b/apps/docs/src/components/MapDemo.vue @@ -0,0 +1,337 @@ + + + + + diff --git a/apps/docs/src/components/RobotChatDemo.test.ts b/apps/docs/src/components/RobotChatDemo.test.ts new file mode 100644 index 00000000..f8ed3061 --- /dev/null +++ b/apps/docs/src/components/RobotChatDemo.test.ts @@ -0,0 +1,20 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' +import RobotChatDemo from './RobotChatDemo.vue' + +describe('RobotChatDemo', () => { + it('shows a visible conversation and queryCallback after send', async () => { + const wrapper = mount(RobotChatDemo, { props: { locale: 'zh' } }) + expect(wrapper.get('[data-preview-field="robot-status"]').text()).toContain('机器人已连接') + expect(wrapper.get('[data-preview-field="robot-message-count"]').text()).toBe('1') + expect(wrapper.get('[data-role="assistant"]').text()).toContain('你好') + + await wrapper.get('input').setValue('查订单') + await wrapper.get('form').trigger('submit') + + expect(wrapper.get('[data-preview-field="robot-last-query"]').text()).toContain('查订单') + expect(wrapper.get('[data-preview-field="robot-query-count"]').text()).toBe('1') + expect(wrapper.get('[data-preview-field="robot-message-count"]').text()).toBe('3') + expect(wrapper.get('[data-role="user"]').text()).toBe('查订单') + }) +}) diff --git a/apps/docs/src/components/RobotChatDemo.vue b/apps/docs/src/components/RobotChatDemo.vue new file mode 100644 index 00000000..5e849615 --- /dev/null +++ b/apps/docs/src/components/RobotChatDemo.vue @@ -0,0 +1,248 @@ + + + + + diff --git a/apps/docs/src/components/agent-ui/AgentRagPipeline.vue b/apps/docs/src/components/agent-ui/AgentRagPipeline.vue new file mode 100644 index 00000000..83179b2d --- /dev/null +++ b/apps/docs/src/components/agent-ui/AgentRagPipeline.vue @@ -0,0 +1,1094 @@ + + + + + diff --git a/apps/docs/src/components/agent-ui/advanced.ts b/apps/docs/src/components/agent-ui/advanced.ts index cd3e25d1..77980eab 100644 --- a/apps/docs/src/components/agent-ui/advanced.ts +++ b/apps/docs/src/components/agent-ui/advanced.ts @@ -78,6 +78,39 @@ function renderStatus(status: AgentAdvancedStatus) { ]) } +function activityKindLabel(kind: AgentActivityItem['kind']) { + if (kind === 'reasoning') { return '推理' } + if (kind === 'search') { return '检索' } + if (kind === 'tool') { return '工具' } + return '轨迹' +} + +function renderActivityKindIcon(kind: AgentActivityItem['kind']) { + const children = kind === 'search' + ? [h('circle', { cx: '11', cy: '11', r: '6' }), h('path', { d: 'm16 16 4 4' })] + : [h('path', { + d: kind === 'reasoning' + ? 'M12 3.5 13.4 8 18 9.2 13.4 10.5 12 15l-1.4-4.5L6 9.2 10.6 8 12 3.5zM18 13.5l.8 2.4 2.4.8-2.4.8-.8 2.4-.8-2.4-2.4-.8 2.4-.8.8-2.4z' + : kind === 'tool' + ? 'M8 7h8M8 12h8M8 17h5' + : 'M7 7h10M7 12h10M7 17h6', + })] + return h('svg', { + 'aria-hidden': 'true', + 'fill': 'none', + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': 1.8, + 'viewBox': '0 0 24 24', + }, children) +} + +function citationMark(item: AgentCitationItem) { + const source = item.domain || item.title + return source.trim().charAt(0).toUpperCase() || '#' +} + function renderPlusIcon() { return h('svg', { 'fill': 'none', @@ -91,6 +124,30 @@ function renderPlusIcon() { }, [h('path', { d: 'M12 5v14M5 12h14' })]) } +function renderChatIcon() { + return h('svg', { + 'aria-hidden': 'true', + 'fill': 'none', + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': 1.8, + 'viewBox': '0 0 24 24', + }, [h('path', { d: 'M5 6h14a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H9l-4 3v-3H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z' })]) +} + +function renderSearchIcon() { + return h('svg', { + 'aria-hidden': 'true', + 'fill': 'none', + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': 1.8, + 'viewBox': '0 0 24 24', + }, [h('circle', { cx: '11', cy: '11', r: '6' }), h('path', { d: 'm16 16 4 4' })]) +} + export const AgentRadioGroup = defineComponent({ name: 'AgentRadioGroup', props: { @@ -488,7 +545,8 @@ export const AgentToolResult = defineComponent({ emits: { 'retry': () => true, 'update:open': (_value: boolean) => true }, setup(props, { emit, slots }) { const open = shallowRef(props.defaultOpen || props.status === 'running' || props.status === 'failed') - return () => h('section', { 'class': 'agent-tool-result', 'data-status': props.status }, [ + const meta = computed(() => [props.duration, props.summary].filter(Boolean).join(' · ')) + return () => h('section', { 'class': 'agent-tool-result', 'data-open': String(open.value), 'data-status': props.status }, [ h('button', { 'class': 'agent-tool-result__header', 'type': 'button', @@ -498,8 +556,14 @@ export const AgentToolResult = defineComponent({ emit('update:open', open.value) }, }, [ - h('span', [renderStatus(props.status), h('strong', props.name)]), - h('small', props.duration || props.summary), + h('span', { class: 'agent-tool-result__command' }, [ + h('span', { 'class': 'agent-tool-result__prompt', 'aria-hidden': 'true' }, '$'), + h('strong', props.name), + ]), + h('span', { class: 'agent-tool-result__meta' }, [ + renderStatus(props.status), + meta.value ? h('small', meta.value) : null, + ]), ]), open.value ? h('div', { class: 'agent-tool-result__body' }, slots.default?.() ?? [h('pre', props.output)]) @@ -688,12 +752,22 @@ export const AgentCitations = defineComponent({ open.value = !open.value emit('update:open', open.value) }, - }, [h('strong', props.title), h('span', `${props.items.length}`)]), + }, [ + h('span', { class: 'agent-citations__heading' }, [ + h('strong', props.title), + h('small', open.value ? '点击来源打开原文' : '展开查看引用来源'), + ]), + h('span', { class: 'agent-citations__count' }, `${props.items.length}`), + ]), open.value - ? h('ol', props.items.map((item, index) => h('li', { key: item.id }, [ - h('button', { type: 'button', onClick: () => emit('open', item) }, [ - h('span', String(index + 1)), - h('span', [h('strong', item.title), h('small', item.domain || item.description || item.url)]), + ? h('ol', { class: 'agent-citations__list' }, props.items.map(item => h('li', { key: item.id }, [ + h('button', { class: 'agent-citations__item', type: 'button', onClick: () => emit('open', item) }, [ + h('span', { 'class': 'agent-citations__mark', 'aria-hidden': 'true' }, citationMark(item)), + h('span', { class: 'agent-citations__copy' }, [ + h('strong', item.title), + h('small', item.domain || item.url || ''), + item.description ? h('p', item.description) : null, + ]), ]), ]))) : null, @@ -708,12 +782,39 @@ export const AgentActivity = defineComponent({ title: { type: String, default: 'Agent activity' }, }, setup(props) { + const completed = computed(() => props.items.filter(item => item.status === 'completed').length) + const current = computed(() => props.items.find(item => item.status === 'running' || item.status === 'failed')) return () => h('section', { 'class': 'agent-activity', 'aria-live': 'polite' }, [ - h('header', [h('strong', props.title), h('span', `${props.items.filter(item => item.status === 'completed').length}/${props.items.length}`)]), - h('ol', props.items.map(item => h('li', { 'data-kind': item.kind, 'data-status': item.status, 'key': item.id }, [ - h('i', { 'aria-hidden': 'true' }), - h('div', [h('span', [h('strong', item.title), item.duration ? h('small', item.duration) : null]), item.detail ? h('p', item.detail) : null]), - renderStatus(item.status), + h('header', { class: 'agent-activity__header' }, [ + h('span', { class: 'agent-activity__heading' }, [ + h('strong', props.title), + current.value + ? h('small', current.value.status === 'failed' ? `阻塞于 ${current.value.title}` : `正在执行 ${current.value.title}`) + : h('small', completed.value === props.items.length && props.items.length ? '全部完成' : '等待开始'), + ]), + h('span', { class: 'agent-activity__count' }, `${completed.value}/${props.items.length}`), + ]), + h('ol', { class: 'agent-activity__body' }, props.items.map(item => h('li', { + 'class': 'agent-activity__item', + 'data-kind': item.kind, + 'data-status': item.status, + 'aria-current': item.status === 'running' ? 'step' : undefined, + 'key': item.id, + }, [ + h('span', { 'class': 'agent-activity__kind', 'aria-hidden': 'true' }, [ + renderActivityKindIcon(item.kind), + ]), + h('span', { class: 'agent-activity__copy' }, [ + h('span', { class: 'agent-activity__row' }, [ + h('strong', item.title), + renderStatus(item.status), + ]), + h('span', { class: 'agent-activity__meta' }, [ + h('small', { class: 'agent-activity__kind-label' }, activityKindLabel(item.kind)), + item.duration ? h('small', item.duration) : null, + ]), + item.detail ? h('p', item.detail) : null, + ]), ]))), ]) }, @@ -735,13 +836,26 @@ export const AgentSidebar = defineComponent({ }, setup(props, { emit, slots }) { return () => h('aside', { 'class': 'agent-sidebar', 'data-collapsed': String(props.collapsed) }, [ - h('header', [ - h('strong', props.collapsed ? 'AI' : props.title), - h('button', { 'type': 'button', 'aria-label': 'Toggle sidebar', 'onClick': () => emit('update:collapsed', !props.collapsed) }, props.collapsed ? '›' : '‹'), + h('header', { class: 'agent-sidebar__header' }, [ + h('span', { class: 'agent-sidebar__heading' }, [ + h('span', { 'class': 'agent-sidebar__brand', 'aria-hidden': 'true' }, [renderChatIcon()]), + !props.collapsed ? h('strong', props.title) : null, + ]), + h('button', { + 'class': 'agent-sidebar__toggle', + 'type': 'button', + 'aria-label': 'Toggle sidebar', + 'onClick': () => emit('update:collapsed', !props.collapsed), + }, props.collapsed ? '›' : '‹'), ]), - !props.collapsed ? h('button', { class: 'agent-sidebar__create', type: 'button', onClick: () => emit('create') }, '+ New chat') : null, - h('nav', props.groups.map(group => h('section', { key: group.id }, [ - !props.collapsed ? h('small', group.label) : null, + !props.collapsed + ? h('button', { class: 'agent-sidebar__create', type: 'button', onClick: () => emit('create') }, [ + renderPlusIcon(), + 'New chat', + ]) + : null, + h('nav', { class: 'agent-sidebar__nav', role: 'navigation' }, props.groups.map(group => h('section', { class: 'agent-sidebar__group', key: group.id }, [ + !props.collapsed ? h('small', { class: 'agent-sidebar__group-label' }, group.label) : null, ...group.items.map(item => h('button', { 'class': 'agent-sidebar__item', 'data-active': String(item.id === props.activeId), @@ -753,9 +867,14 @@ export const AgentSidebar = defineComponent({ emit('select', item) }, }, [ - h('i', { 'aria-hidden': 'true' }, item.label.charAt(0).toUpperCase()), - !props.collapsed ? h('span', [h('strong', item.label), item.meta ? h('small', item.meta) : null]) : null, - !props.collapsed && item.badge !== undefined ? h('b', String(item.badge)) : null, + h('span', { 'class': 'agent-sidebar__mark', 'aria-hidden': 'true' }, [renderChatIcon()]), + !props.collapsed + ? h('span', { class: 'agent-sidebar__copy' }, [ + h('strong', item.label), + item.meta ? h('small', item.meta) : null, + ]) + : null, + !props.collapsed && item.badge !== undefined ? h('b', { class: 'agent-sidebar__badge' }, String(item.badge)) : null, ])), ]))), slots.footer?.(), @@ -772,11 +891,28 @@ export const AgentContextCard = defineComponent({ emits: { open: (_chunk: AgentContextChunk) => true }, setup(props, { emit }) { return () => h('section', { class: 'agent-context-card' }, [ - h('header', [h('strong', props.title), h('span', `${props.chunks.length} chunks`)]), - h('div', props.chunks.map(chunk => h('article', { key: chunk.id }, [ - h('header', [h('strong', chunk.label || chunk.source || 'Context'), chunk.content.length ? h('small', `${chunk.content.length} chars`) : null]), - h('p', chunk.content), - h('footer', [h('span', chunk.sourceType || 'Source'), h('button', { type: 'button', onClick: () => emit('open', chunk) }, chunk.source || 'Open')]), + h('header', { class: 'agent-context-card__header' }, [ + h('span', { class: 'agent-context-card__heading' }, [ + h('strong', props.title), + h('small', props.chunks.length ? '来自检索结果的上下文片段' : '暂无检索上下文'), + ]), + h('span', { class: 'agent-context-card__count' }, `${props.chunks.length}`), + ]), + h('ol', { class: 'agent-context-card__list' }, props.chunks.map((chunk, index) => h('li', { key: chunk.id }, [ + h('article', { class: 'agent-context-card__chunk' }, [ + h('header', { class: 'agent-context-card__chunk-head' }, [ + h('span', { 'class': 'agent-context-card__index', 'aria-hidden': 'true' }, String(index + 1).padStart(2, '0')), + h('span', { class: 'agent-context-card__chunk-copy' }, [ + h('strong', chunk.label || chunk.source || 'Context'), + chunk.sourceType ? h('small', { class: 'agent-context-card__type' }, chunk.sourceType) : null, + ]), + ]), + h('p', { class: 'agent-context-card__quote' }, chunk.content), + h('footer', { class: 'agent-context-card__chunk-foot' }, [ + h('small', chunk.source || 'Source'), + h('button', { class: 'agent-context-card__open', type: 'button', onClick: () => emit('open', chunk) }, '打开'), + ]), + ]), ]))), ]) }, @@ -797,11 +933,27 @@ export const AgentInsightCard = defineComponent({ emit('update:current', (props.current + delta + props.insights.length) % props.insights.length) } return () => h('article', { 'class': 'agent-insight-card', 'data-tone': active.value?.tone || 'default' }, [ - h('header', [h('strong', props.title), h('span', `${props.current + 1}/${props.insights.length}`)]), - active.value ? h('div', [active.value.label ? h('small', active.value.label) : null, h('h3', active.value.description), active.value.value ? h('b', active.value.value) : null]) : h('p', 'No insights'), - h('footer', [ - h('span', [h('button', { type: 'button', onClick: () => move(-1) }, '‹'), h('button', { type: 'button', onClick: () => move(1) }, '›')]), - active.value?.action ? h('button', { type: 'button', onClick: () => emit('action', active.value!) }, active.value.action) : null, + h('header', { class: 'agent-insight-card__header' }, [ + h('span', { class: 'agent-insight-card__heading' }, [ + h('strong', props.title), + h('small', active.value?.label || '运行指标'), + ]), + h('span', { class: 'agent-insight-card__count' }, `${props.insights.length ? props.current + 1 : 0}/${props.insights.length}`), + ]), + active.value + ? h('div', { class: 'agent-insight-card__body' }, [ + active.value.value ? h('b', { class: 'agent-insight-card__value' }, active.value.value) : null, + h('p', { class: 'agent-insight-card__copy' }, active.value.description), + ]) + : h('p', { class: 'agent-insight-card__empty' }, '暂无洞察'), + h('footer', { class: 'agent-insight-card__footer' }, [ + h('span', { class: 'agent-insight-card__nav' }, [ + h('button', { 'class': 'agent-insight-card__nav-btn', 'type': 'button', 'aria-label': 'Previous insight', 'onClick': () => move(-1) }, '‹'), + h('button', { 'class': 'agent-insight-card__nav-btn', 'type': 'button', 'aria-label': 'Next insight', 'onClick': () => move(1) }, '›'), + ]), + active.value?.action + ? h('button', { class: 'agent-insight-card__action', type: 'button', onClick: () => emit('action', active.value!) }, active.value.action) + : null, ]), ]) }, @@ -816,8 +968,9 @@ export const AgentSelectionActions = defineComponent({ emits: { select: (_payload: { action: AgentSelectionAction, text: string }) => true }, setup(props, { emit }) { return () => h('section', { class: 'agent-selection-actions' }, [ - h('blockquote', props.text), - h('div', props.actions.map(action => h('button', { + h('p', { class: 'agent-selection-actions__quote' }, props.text), + h('div', { class: 'agent-selection-actions__toolbar' }, props.actions.map(action => h('button', { + class: 'agent-selection-actions__action', key: action.id, type: 'button', onClick: () => emit('select', { action, text: props.text }), @@ -937,14 +1090,38 @@ export const AgentCommandSearch = defineComponent({ const query = props.modelValue.trim().toLowerCase() return query ? props.items.filter(item => `${item.label} ${item.description ?? ''} ${item.group ?? ''}`.toLowerCase().includes(query)) : props.items }) + const groupedItems = computed(() => { + const groups = new Map() + for (const item of visibleItems.value) { + const key = item.group || 'Commands' + const list = groups.get(key) + if (list) { list.push(item) } + else { groups.set(key, [item]) } + } + return [...groups.entries()].map(([label, items]) => ({ label, items })) + }) return () => h('section', { class: 'agent-command-search' }, [ - h('label', [h('span', '⌕'), h('input', { value: props.modelValue, placeholder: props.placeholder, onInput: (event: Event) => emit('update:modelValue', eventValue(event)) })]), - visibleItems.value.length - ? h('div', visibleItems.value.map(item => h('button', { key: item.id, type: 'button', onClick: () => emit('select', item) }, [ - h('span', [h('strong', item.label), item.description ? h('small', item.description) : null]), - item.shortcut ? h('kbd', item.shortcut) : null, - ]))) - : h('p', props.emptyText), + h('label', { class: 'agent-command-search__field' }, [ + h('span', { 'class': 'agent-command-search__icon', 'aria-hidden': 'true' }, [renderSearchIcon()]), + h('input', { value: props.modelValue, placeholder: props.placeholder, onInput: (event: Event) => emit('update:modelValue', eventValue(event)) }), + ]), + groupedItems.value.length + ? h('div', { class: 'agent-command-search__list' }, groupedItems.value.flatMap(group => [ + h('small', { class: 'agent-command-search__group', key: `group-${group.label}` }, group.label), + ...group.items.map(item => h('button', { + class: 'agent-command-search__item', + key: item.id, + type: 'button', + onClick: () => emit('select', item), + }, [ + h('span', { class: 'agent-command-search__copy' }, [ + h('strong', item.label), + item.description ? h('small', item.description) : null, + ]), + item.shortcut ? h('kbd', item.shortcut) : null, + ])), + ])) + : h('p', { class: 'agent-command-search__empty' }, props.emptyText), ]) }, }) @@ -957,17 +1134,36 @@ export const AgentFlowchart = defineComponent({ }, emits: { add: (_afterId?: string) => true, select: (_node: AgentFlowNode) => true }, setup(props, { emit }) { + const typeLabel = (type: AgentFlowNode['type']) => { + if (type === 'trigger') { return '触发' } + if (type === 'condition') { return '条件' } + if (type === 'action') { return '动作' } + return '结果' + } return () => h('section', { class: 'agent-flowchart' }, [ - h('header', [ - h('strong', props.title), - h('button', { type: 'button', onClick: () => emit('add', undefined) }, [renderPlusIcon(), ' Step']), + h('header', { class: 'agent-flowchart__header' }, [ + h('span', { class: 'agent-flowchart__heading' }, [ + h('strong', props.title), + h('small', `${props.nodes.length} 个步骤`), + ]), + h('button', { class: 'agent-flowchart__add', type: 'button', onClick: () => emit('add', undefined) }, [renderPlusIcon(), 'Step']), ]), - h('div', props.nodes.flatMap((node, index): VNodeChild[] => [ - h('button', { 'class': 'agent-flowchart__node', 'data-type': node.type, 'key': node.id, 'type': 'button', 'onClick': () => emit('select', node) }, [ - h('small', node.type), + h('div', { class: 'agent-flowchart__canvas' }, props.nodes.flatMap((node, index): VNodeChild[] => [ + h('button', { + 'class': 'agent-flowchart__node', + 'data-type': node.type, + 'data-status': node.status || 'waiting', + 'key': node.id, + 'type': 'button', + 'onClick': () => emit('select', node), + }, [ + h('span', { class: 'agent-flowchart__node-head' }, [ + h('span', { 'class': 'agent-flowchart__index', 'aria-hidden': 'true' }, String(index + 1).padStart(2, '0')), + h('small', { class: 'agent-flowchart__type' }, typeLabel(node.type)), + node.status ? renderStatus(node.status) : null, + ]), h('strong', node.label), - node.detail ? h('span', node.detail) : null, - node.status ? renderStatus(node.status) : null, + node.detail ? h('span', { class: 'agent-flowchart__detail' }, node.detail) : null, ]), index < props.nodes.length - 1 ? h('button', { @@ -1039,9 +1235,17 @@ export const AgentFineTune = defineComponent({ } return () => h('section', { class: 'agent-fine-tune' }, [ - h('header', [h('strong', props.title), h('span', 'Adjust')]), - h('div', props.controls.map(renderControl)), - h('footer', [h('button', { type: 'button', onClick: () => emit('apply', props.controls) }, 'Apply changes')]), + h('header', { class: 'agent-fine-tune__header' }, [ + h('span', { class: 'agent-fine-tune__heading' }, [ + h('strong', props.title), + h('small', '调整生成参数后立即生效'), + ]), + h('span', { class: 'agent-fine-tune__count' }, `${props.controls.length} 项`), + ]), + h('div', { class: 'agent-fine-tune__grid' }, props.controls.map(renderControl)), + h('footer', { class: 'agent-fine-tune__footer' }, [ + h('button', { class: 'agent-fine-tune__apply', type: 'button', onClick: () => emit('apply', props.controls) }, 'Apply changes'), + ]), ]) }, }) diff --git a/apps/docs/src/components/agent-ui/agent-advanced.css b/apps/docs/src/components/agent-ui/agent-advanced.css index 1716ae61..84e5799f 100644 --- a/apps/docs/src/components/agent-ui/agent-advanced.css +++ b/apps/docs/src/components/agent-ui/agent-advanced.css @@ -161,10 +161,88 @@ .agent-code-block__cursor { position: absolute; right: 12px; bottom: 12px; width: 6px; height: 14px; background: #2dd4bf; animation: varo-agent-code-cursor .75s steps(1) infinite; } @keyframes varo-agent-code-cursor { 50% { opacity: 0; } } -.agent-tool-result, .agent-citations, .agent-activity, .agent-context-card, .agent-insight-card, .agent-tool-approval, .agent-fine-tune { overflow: hidden; background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 16px; } -.agent-tool-result__header, .agent-citations__trigger { display: flex; gap: 12px; align-items: center; justify-content: space-between; width: 100%; min-height: 48px; padding: 0 13px; text-align: left; background: var(--agent-surface); border: 0; } -.agent-tool-result__header > span { display: flex; gap: 9px; align-items: center; min-width: 0; } -.agent-tool-result__header strong { overflow: hidden; text-overflow: ellipsis; font-size: 12px; color: var(--agent-text); white-space: nowrap; } +.agent-citations, .agent-activity, .agent-context-card, .agent-insight-card, .agent-tool-approval, .agent-fine-tune { overflow: hidden; background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 16px; } + + +.agent-tool-result { + overflow: hidden; + color: #dbeafe; + background: #0f172a; + border: 1px solid #1e293b; + border-radius: 16px; +} + +.agent-tool-result__header { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; + width: 100%; + min-height: 42px; + padding: 0 12px; + color: inherit; + text-align: left; + background: #111c30; + border: 0; + border-bottom: 1px solid #26334a; +} + +.agent-tool-result[data-open='false'] .agent-tool-result__header { + border-bottom-color: transparent; +} + +.agent-tool-result__command { + display: flex; + gap: 8px; + align-items: baseline; + min-width: 0; +} + +.agent-tool-result__prompt { + flex: none; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + font-weight: 700; + color: #2dd4bf; +} + +.agent-tool-result__command strong { + overflow: hidden; + text-overflow: ellipsis; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + font-weight: 650; + color: #f8fafc; + white-space: nowrap; +} + +.agent-tool-result__meta { + display: flex; + flex: none; + gap: 8px; + align-items: center; +} + +.agent-tool-result__meta .agent-advanced__status { + padding: 3px 8px; + color: #cbd5e1; + background: rgb(148 163 184 / 12%); + border: 1px solid rgb(148 163 184 / 18%); + border-radius: 999px; +} + +.agent-tool-result__meta .agent-advanced__status[data-status='completed'], +.agent-tool-result__meta .agent-advanced__status[data-status='running'] { + color: #5eead4; + background: rgb(45 212 191 / 12%); + border-color: rgb(45 212 191 / 24%); +} + +.agent-tool-result__meta .agent-advanced__status[data-status='failed'] { + color: #fca5a5; + background: rgb(248 113 113 / 12%); + border-color: rgb(248 113 113 / 24%); +} .agent-file-diff { --agent-diff-surface: var(--varo-agent-surface, #fff); @@ -397,9 +475,9 @@ .agent-file-diff__action:hover { color: var(--agent-diff-text); border-color: #99a7b6; } .agent-file-diff__action:last-child { color: var(--agent-primary-foreground); background: var(--agent-primary); border-color: var(--agent-primary); box-shadow: 0 5px 12px rgb(15 118 110 / 20%); } .agent-file-diff__action:disabled { cursor: not-allowed; opacity: .5; } -.agent-tool-approval > footer, .agent-insight-card > footer, .agent-fine-tune > footer, .agent-image-generation > footer { display: flex; gap: 8px; align-items: center; justify-content: flex-end; min-height: 48px; padding: 0 12px; border-top: 1px solid var(--agent-border); } -.agent-tool-approval button, .agent-insight-card button, .agent-fine-tune button, .agent-image-generation button { min-height: 34px; padding: 0 11px; font-size: 11px; font-weight: 750; color: var(--agent-text-regular); background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 10px; } -.agent-tool-approval > footer button:last-child, .agent-fine-tune > footer button { color: var(--agent-primary-foreground); background: var(--agent-primary); border-color: var(--agent-primary); } +.agent-tool-approval > footer, .agent-image-generation > footer { display: flex; gap: 8px; align-items: center; justify-content: flex-end; min-height: 48px; padding: 0 12px; border-top: 1px solid var(--agent-border); } +.agent-tool-approval button, .agent-image-generation button { min-height: 34px; padding: 0 11px; font-size: 11px; font-weight: 750; color: var(--agent-text-regular); background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 10px; } +.agent-tool-approval > footer button:last-child { color: var(--agent-primary-foreground); background: var(--agent-primary); border-color: var(--agent-primary); } @media (max-width: 600px) { .agent-file-diff__header { gap: 7px; padding-inline: 9px; } @@ -411,10 +489,10 @@ .agent-file-diff__hunk-count { display: none; } .agent-file-diff__footer { padding-inline: 9px; } } -.agent-tool-result__header small { font-size: 11px; color: var(--agent-muted); } -.agent-tool-result__body { padding: 12px; color: #cbd5e1; background: #0f172a; border-top: 1px solid var(--agent-border); } -.agent-tool-result__body pre { margin: 0; overflow-x: auto; font-family: ui-monospace, monospace; font-size: 11px; white-space: pre-wrap; } -.agent-tool-result__retry { min-height: 32px; padding: 0 10px; margin: 0 12px 12px; font-size: 11px; color: #dc2626; background: var(--agent-surface); border: 1px solid #fecaca; border-radius: 9px; } +.agent-tool-result__header small { font-size: 11px; font-variant-numeric: tabular-nums; color: #94a3b8; } +.agent-tool-result__body { padding: 12px; color: #cbd5e1; background: #0f172a; } +.agent-tool-result__body pre { margin: 0; overflow-x: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; line-height: 1.7; white-space: pre-wrap; } +.agent-tool-result__retry { min-height: 32px; padding: 0 10px; margin: 0 12px 12px; font-size: 11px; font-weight: 750; color: #fecaca; background: rgb(248 113 113 / 10%); border: 1px solid rgb(248 113 113 / 28%); border-radius: 9px; } .agent-image-generation { margin: 0; } .agent-image-generation__canvas { @@ -471,65 +549,747 @@ .agent-tool-approval dt { color: var(--agent-muted); } .agent-tool-approval dd { margin: 0; font-weight: 650; color: var(--agent-text); text-align: right; } .agent-tool-approval > label { display: flex; gap: 7px; align-items: center; padding: 0 14px 12px; font-size: 11px; color: var(--agent-text-regular); } -.agent-citations__trigger strong { font-size: 12px; } -.agent-citations__trigger span { display: grid; place-items: center; width: 24px; height: 24px; font-size: 11px; color: var(--agent-muted); background: var(--agent-fill); border-radius: 999px; } -.agent-citations ol { display: grid; gap: 7px; padding: 0 12px 12px; margin: 0; list-style: none; } -.agent-citations li button { display: flex; gap: 9px; align-items: center; width: 100%; min-height: 42px; padding: 6px 9px; text-align: left; background: var(--agent-surface-strong); border: 1px solid var(--agent-border); border-radius: 11px; } -.agent-citations li button > span:first-child { display: grid; flex: none; place-items: center; width: 22px; height: 22px; font-size: 10px; color: var(--agent-muted); background: var(--agent-surface); border-radius: 999px; } -.agent-citations li button > span:last-child { display: grid; min-width: 0; } -.agent-citations li strong, .agent-citations li small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.agent-citations li strong { font-size: 11px; color: var(--agent-text); } -.agent-citations li small { font-size: 10px; color: var(--agent-muted); } -.agent-activity > header, .agent-context-card > header, .agent-insight-card > header, .agent-fine-tune > header { display: flex; align-items: center; justify-content: space-between; min-height: 46px; padding: 0 13px; border-bottom: 1px solid var(--agent-border); } -.agent-activity > header strong, .agent-context-card > header strong, .agent-insight-card > header strong, .agent-fine-tune > header strong { font-size: 12px; } -.agent-activity > header span, .agent-context-card > header span, .agent-insight-card > header span, .agent-fine-tune > header span { font-size: 11px; color: var(--agent-muted); } -.agent-activity ol { display: grid; gap: 0; padding: 10px 13px 13px; margin: 0; list-style: none; } -.agent-activity li { position: relative; display: grid; grid-template-columns: 12px minmax(0, 1fr) auto; gap: 9px; padding: 8px 0; } -.agent-activity li > i { width: 9px; height: 9px; margin-top: 4px; background: var(--agent-muted); border: 2px solid var(--agent-surface); border-radius: 999px; box-shadow: 0 0 0 1px var(--agent-border-strong); } -.agent-activity li[data-status='running'] > i { background: var(--agent-primary); } -.agent-activity li[data-status='completed'] > i { background: var(--agent-success); } -.agent-activity li > div { display: grid; min-width: 0; } -.agent-activity li > div > span { display: flex; gap: 8px; justify-content: space-between; } -.agent-activity li strong { font-size: 11px; color: var(--agent-text); } -.agent-activity li small, .agent-activity li p { font-size: 10px; color: var(--agent-muted); } -.agent-activity li p { margin: 2px 0 0; } -.agent-sidebar { display: grid; align-content: start; width: 248px; min-height: 360px; overflow: hidden; background: var(--agent-surface-strong); border: 1px solid var(--agent-border); border-radius: 18px; transition: width .2s ease; } + +.agent-citations__trigger { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + width: 100%; + min-height: 52px; + padding: 12px 14px 11px; + text-align: left; + background: var(--agent-surface); + border: 0; +} + +.agent-citations__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-citations__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-citations__heading small, +.agent-citations__count { + font-size: 11px; + line-height: 16px; + color: var(--agent-muted); +} + +.agent-citations__count { + display: grid; + flex: none; + place-items: center; + min-width: 24px; + height: 24px; + padding: 0 7px; + font-variant-numeric: tabular-nums; + background: var(--agent-fill); + border-radius: 999px; +} + +.agent-citations__list { + display: grid; + gap: 8px; + padding: 0 12px 12px; + margin: 0; + list-style: none; +} + +.agent-citations__list > li { + display: block; + list-style: none; +} + +.agent-citations__list > li::marker { + content: none; +} + +.agent-citations__item { + display: grid; + grid-template-columns: 28px minmax(0, 1fr); + gap: 10px; + align-items: start; + width: 100%; + min-height: 58px; + padding: 9px 10px; + text-align: left; + background: var(--agent-surface-strong); + border: 1px solid var(--agent-border); + border-radius: 12px; +} + +.agent-citations__mark { + display: grid; + flex: none; + place-items: center; + width: 28px; + height: 28px; + font-size: 12px; + font-weight: 800; + color: var(--agent-primary); + background: var(--agent-primary-soft); + border-radius: 9px; +} + +.agent-citations__copy { + display: grid; + gap: 3px; + min-width: 0; +} + +.agent-citations__copy strong { + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + font-weight: 700; + line-height: 18px; + color: var(--agent-text); + white-space: nowrap; +} + +.agent-citations__copy small { + overflow: hidden; + text-overflow: ellipsis; + font-size: 11px; + line-height: 16px; + color: var(--agent-muted); + white-space: nowrap; +} + +.agent-citations__copy p { + margin: 0; + font-size: 11px; + line-height: 16px; + color: var(--agent-muted); +} +.agent-fine-tune__header { display: flex; gap: 12px; align-items: flex-start; justify-content: space-between; min-height: 52px; padding: 12px 14px 11px; border-bottom: 1px solid var(--agent-border); } +.agent-fine-tune__heading { display: grid; gap: 2px; min-width: 0; } +.agent-fine-tune__heading strong { font-size: 13px; font-weight: 760; line-height: 18px; } + +.agent-fine-tune__heading small, +.agent-fine-tune__count { font-size: 11px; line-height: 16px; color: var(--agent-muted); } +.agent-fine-tune__count { flex: none; font-variant-numeric: tabular-nums; } + +.agent-activity__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + border-bottom: 1px solid var(--agent-border); +} + +.agent-activity__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-activity__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-activity__heading small, +.agent-activity__count, +.agent-activity__meta small { + font-size: 11px; + line-height: 16px; + color: var(--agent-muted); +} + +.agent-activity__count { + flex: none; + font-variant-numeric: tabular-nums; +} + +.agent-activity__body { + display: grid; + padding: 8px 10px 12px; + margin: 0; + list-style: none; +} + +.agent-activity__item { + display: grid; + grid-template-columns: 28px minmax(0, 1fr); + gap: 10px; + min-height: 58px; + padding: 9px 10px; + list-style: none; + border-radius: 12px; +} + +.agent-activity__item::marker { + content: none; +} + +.agent-activity__item[data-status='running'] { + background: color-mix(in srgb, var(--agent-primary-soft) 70%, transparent); +} + +.agent-activity__item[data-status='failed'] { + background: color-mix(in srgb, var(--agent-danger-soft) 70%, transparent); +} + +.agent-activity__kind { + display: grid; + place-items: center; + width: 28px; + height: 28px; + color: var(--agent-muted); + background: var(--agent-fill); + border: 1px solid var(--agent-border); + border-radius: 9px; +} + +.agent-activity__kind svg { + display: block; + width: 14px; + height: 14px; +} + +.agent-activity__item[data-kind='reasoning'] .agent-activity__kind { + color: var(--agent-primary); + background: var(--agent-primary-soft); + border-color: color-mix(in srgb, var(--agent-primary) 28%, transparent); +} + +.agent-activity__item[data-kind='search'] .agent-activity__kind { + color: var(--varo-agent-source-blue, #2563eb); + background: var(--varo-agent-source-blue-soft, #eff6ff); + border-color: color-mix(in srgb, var(--varo-agent-source-blue, #2563eb) 28%, transparent); +} + +.agent-activity__item[data-kind='tool'] .agent-activity__kind { + color: var(--varo-agent-source-violet, #7c3aed); + background: var(--varo-agent-source-violet-soft, #f5f3ff); + border-color: color-mix(in srgb, var(--varo-agent-source-violet, #7c3aed) 28%, transparent); +} + +.agent-activity__item[data-status='running'] .agent-activity__kind { + animation: varo-agent-advanced-pulse 1s ease-in-out infinite; +} + +.agent-activity__copy { + display: grid; + gap: 4px; + min-width: 0; +} + +.agent-activity__row { + display: flex; + gap: 10px; + align-items: flex-start; + justify-content: space-between; +} + +.agent-activity__copy strong { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + font-weight: 700; + line-height: 18px; + white-space: nowrap; +} + +.agent-activity__meta { + display: flex; + gap: 8px; + align-items: center; +} + +.agent-activity__kind-label { + padding: 1px 7px; + font-size: 10px; + font-weight: 750; + color: var(--agent-muted); + background: var(--agent-fill); + border-radius: 999px; +} + +.agent-activity__copy p { + margin: 0; + font-size: 11px; + line-height: 16px; + color: var(--agent-muted); +} + +@media (prefers-reduced-motion: reduce) { + .agent-activity__item[data-status='running'] .agent-activity__kind { + animation: none; + } +} + +.agent-sidebar { + display: grid; + align-content: start; + width: 248px; + min-height: 360px; + overflow: hidden; + background: var(--agent-surface-strong); + border: 1px solid var(--agent-border); + border-radius: 18px; + transition: width .2s ease; +} .agent-sidebar[data-collapsed='true'] { width: 64px; } -.agent-sidebar > header { display: flex; align-items: center; justify-content: space-between; min-height: 54px; padding: 0 12px; background: var(--agent-surface); border-bottom: 1px solid var(--agent-border); } -.agent-sidebar > header strong { font-size: 12px; } -.agent-sidebar > header button { display: grid; place-items: center; width: 28px; height: 28px; color: var(--agent-muted); background: var(--agent-fill); border: 0; border-radius: 9px; } -.agent-sidebar__create { min-height: 36px; margin: 10px 10px 5px; font-size: 11px; font-weight: 750; color: var(--agent-primary); background: var(--agent-primary-soft); border: 1px solid #99f6e4; border-radius: 10px; } -.agent-sidebar nav { display: grid; gap: 10px; padding: 7px; } -.agent-sidebar nav section { display: grid; gap: 3px; } -.agent-sidebar nav section > small { padding: 2px 7px; font-size: 10px; font-weight: 750; color: var(--agent-muted); text-transform: uppercase; } -.agent-sidebar__item { display: flex; gap: 8px; align-items: center; width: 100%; min-height: 38px; padding: 0 7px; color: var(--agent-muted); text-align: left; background: transparent; border: 0; border-radius: 10px; } -.agent-sidebar__item[data-active='true'] { color: var(--agent-primary); background: var(--agent-surface); box-shadow: 0 3px 12px rgb(15 23 42 / 6%); } -.agent-sidebar__item > i { display: grid; flex: none; place-items: center; width: 24px; height: 24px; font-size: 10px; font-style: normal; font-weight: 800; background: var(--agent-border); border-radius: 8px; } -.agent-sidebar__item > span { display: grid; flex: 1; min-width: 0; } -.agent-sidebar__item strong, .agent-sidebar__item small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.agent-sidebar__item strong { font-size: 11px; } -.agent-sidebar__item small { font-size: 10px; color: var(--agent-muted); } -.agent-sidebar__item > b { font-size: 10px; } -.agent-context-card > div { display: grid; gap: 9px; padding: 11px; } -.agent-context-card article { overflow: hidden; background: var(--agent-surface-strong); border: 1px solid var(--agent-border); border-radius: 12px; } -.agent-context-card article > header, .agent-context-card article > footer { display: flex; align-items: center; justify-content: space-between; min-height: 34px; padding: 0 10px; } -.agent-context-card article strong { font-size: 11px; } -.agent-context-card article small { font-size: 10px; color: var(--agent-muted); } -.agent-context-card article p { padding: 9px 10px; margin: 0; font-size: 11px; line-height: 1.55; color: var(--agent-text-regular); border-top: 1px solid var(--agent-border); border-bottom: 1px solid var(--agent-border); } -.agent-context-card article footer { font-size: 10px; color: var(--agent-muted); } -.agent-context-card article footer button { font-size: 10px; font-weight: 750; color: var(--agent-primary); background: transparent; border: 0; } -.agent-insight-card > div { display: grid; gap: 8px; align-content: center; min-height: 150px; padding: 16px; background: linear-gradient(145deg, var(--agent-surface), var(--agent-surface-strong)); } -.agent-insight-card[data-tone='danger'] > div { background: linear-gradient(145deg, var(--agent-surface), var(--agent-danger-soft)); } -.agent-insight-card[data-tone='success'] > div { background: linear-gradient(145deg, var(--agent-surface), var(--agent-success-soft)); } -.agent-insight-card > div small { font-size: 10px; font-weight: 750; color: var(--agent-muted); text-transform: uppercase; } -.agent-insight-card > div h3 { margin: 0; font-size: 16px; line-height: 1.4; color: var(--agent-text); } -.agent-insight-card > div b { font-size: 24px; color: var(--agent-primary); } -.agent-insight-card > footer { justify-content: space-between; } -.agent-insight-card > footer span { display: flex; gap: 5px; } -.agent-selection-actions { display: grid; gap: 10px; } -.agent-selection-actions blockquote { padding: 12px 14px; margin: 0; font-size: 12px; line-height: 1.65; color: var(--agent-text); background: var(--agent-primary-soft); border-left: 3px solid #14b8a6; border-radius: 0 12px 12px 0; } -.agent-selection-actions > div { display: flex; flex-wrap: wrap; gap: 7px; } -.agent-selection-actions button { min-height: 34px; padding: 0 11px; font-size: 11px; font-weight: 750; color: var(--agent-text-regular); background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 999px; } + +.agent-sidebar__header { + display: flex; + gap: 8px; + align-items: center; + justify-content: space-between; + min-height: 54px; + padding: 0 12px; + background: var(--agent-surface); + border-bottom: 1px solid var(--agent-border); +} + +.agent-sidebar__heading { + display: flex; + gap: 8px; + align-items: center; + min-width: 0; +} + +.agent-sidebar__brand, +.agent-sidebar__mark { + display: grid; + flex: none; + place-items: center; + width: 24px; + height: 24px; + color: var(--agent-primary); + background: var(--agent-primary-soft); + border-radius: 8px; +} + +.agent-sidebar__brand svg, +.agent-sidebar__mark svg { + display: block; + width: 13px; + height: 13px; +} +.agent-sidebar__heading strong { overflow: hidden; text-overflow: ellipsis; font-size: 12px; font-weight: 760; white-space: nowrap; } + +.agent-sidebar__toggle { + display: grid; + place-items: center; + width: 28px; + height: 28px; + color: var(--agent-muted); + background: var(--agent-fill); + border: 0; + border-radius: 9px; +} + +.agent-sidebar__create { + display: flex; + gap: 6px; + align-items: center; + justify-content: center; + min-height: 36px; + margin: 10px 10px 5px; + font-size: 11px; + font-weight: 750; + color: var(--agent-primary); + background: var(--agent-primary-soft); + border: 1px solid color-mix(in srgb, var(--agent-primary) 28%, transparent); + border-radius: 10px; +} +.agent-sidebar__create svg { width: 13px; height: 13px; } +.agent-sidebar__nav { display: grid; gap: 10px; padding: 7px; } +.agent-sidebar__group { display: grid; gap: 3px; } +.agent-sidebar__group-label { padding: 2px 7px; font-size: 10px; font-weight: 750; color: var(--agent-muted); text-transform: uppercase; } + +.agent-sidebar__item { + display: flex; + gap: 8px; + align-items: center; + width: 100%; + min-height: 42px; + padding: 0 7px; + color: var(--agent-muted); + text-align: left; + background: transparent; + border: 0; + border-radius: 10px; +} + +.agent-sidebar__item[data-active='true'] { + color: var(--agent-primary); + background: var(--agent-surface); + box-shadow: 0 3px 12px rgb(15 23 42 / 6%); +} + +.agent-sidebar__item[data-active='true'] .agent-sidebar__mark { + color: var(--agent-primary-foreground); + background: var(--agent-primary); +} +.agent-sidebar__copy { display: grid; flex: 1; min-width: 0; } + +.agent-sidebar__copy strong, +.agent-sidebar__copy small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.agent-sidebar__copy strong { font-size: 11px; } +.agent-sidebar__copy small { font-size: 10px; color: var(--agent-muted); } + +.agent-sidebar__badge { + display: grid; + flex: none; + place-items: center; + min-width: 18px; + height: 18px; + padding: 0 5px; + font-size: 10px; + font-weight: 750; + color: var(--agent-text); + background: var(--agent-fill); + border-radius: 999px; +} + +.agent-sidebar[data-collapsed='true'] .agent-sidebar__header, +.agent-sidebar[data-collapsed='true'] .agent-sidebar__item { justify-content: center; } + +.agent-context-card__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + border-bottom: 1px solid var(--agent-border); +} + +.agent-context-card__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-context-card__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-context-card__heading small, +.agent-context-card__count { + font-size: 11px; + line-height: 16px; + color: var(--agent-muted); +} + +.agent-context-card__count { + flex: none; + font-variant-numeric: tabular-nums; +} + +.agent-context-card__list { + display: grid; + gap: 8px; + padding: 10px 12px 12px; + margin: 0; + list-style: none; +} + +.agent-context-card__list > li { + display: block; + list-style: none; +} + +.agent-context-card__list > li::marker { + content: none; +} + +.agent-context-card__chunk { + overflow: hidden; + background: var(--agent-surface-strong); + border: 1px solid var(--agent-border); + border-radius: 12px; +} + +.agent-context-card__chunk-head { + display: flex; + gap: 10px; + align-items: center; + min-height: 40px; + padding: 8px 10px 0; +} + +.agent-context-card__index { + display: grid; + flex: none; + place-items: center; + width: 28px; + height: 28px; + font-size: 10px; + font-weight: 800; + color: var(--agent-primary); + background: var(--agent-primary-soft); + border-radius: 9px; +} + +.agent-context-card__chunk-copy { + display: flex; + flex: 1; + gap: 8px; + align-items: center; + min-width: 0; +} + +.agent-context-card__chunk-copy strong { + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + font-weight: 700; + white-space: nowrap; +} + +.agent-context-card__type { + flex: none; + padding: 1px 7px; + font-size: 10px; + font-weight: 750; + color: var(--agent-muted); + background: var(--agent-fill); + border-radius: 999px; +} + +.agent-context-card__quote { + padding: 8px 10px; + margin: 8px 10px 0; + font-size: 12px; + line-height: 1.55; + color: var(--agent-text-regular); + background: var(--agent-surface); + border-left: 2px solid var(--agent-primary); + border-radius: 0 10px 10px 0; +} + +.agent-context-card__chunk-foot { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; + min-height: 36px; + padding: 0 10px 8px; +} + +.agent-context-card__chunk-foot small { + overflow: hidden; + text-overflow: ellipsis; + font-size: 11px; + color: var(--agent-muted); + white-space: nowrap; +} + +.agent-context-card__open { + flex: none; + min-height: 28px; + padding: 0 8px; + font-size: 11px; + font-weight: 750; + color: var(--agent-primary); + background: transparent; + border: 0; +} + +.agent-insight-card { + overflow: hidden; + background: var(--agent-surface); + border: 1px solid var(--agent-border); + border-radius: 16px; +} + +.agent-insight-card[data-tone='success'] { + border-color: color-mix(in srgb, var(--agent-success) 42%, var(--agent-border)); +} + +.agent-insight-card[data-tone='warning'] { + border-color: color-mix(in srgb, var(--agent-warning) 42%, var(--agent-border)); +} + +.agent-insight-card[data-tone='danger'] { + border-color: color-mix(in srgb, var(--agent-danger) 42%, var(--agent-border)); +} + +.agent-insight-card__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + border-bottom: 1px solid var(--agent-border); +} + +.agent-insight-card__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-insight-card__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-insight-card__heading small, +.agent-insight-card__count { + font-size: 11px; + line-height: 16px; + color: var(--agent-muted); +} + +.agent-insight-card__count { + flex: none; + font-variant-numeric: tabular-nums; +} + +.agent-insight-card__body { + display: grid; + gap: 8px; + min-height: 132px; + padding: 18px 16px 16px; +} + +.agent-insight-card[data-tone='success'] .agent-insight-card__body { + background: color-mix(in srgb, var(--agent-success-soft) 70%, transparent); +} + +.agent-insight-card[data-tone='warning'] .agent-insight-card__body { + background: color-mix(in srgb, var(--agent-warning-soft) 70%, transparent); +} + +.agent-insight-card[data-tone='danger'] .agent-insight-card__body { + background: color-mix(in srgb, var(--agent-danger-soft) 70%, transparent); +} + +.agent-insight-card__value { + font-size: 32px; + font-weight: 850; + font-variant-numeric: tabular-nums; + line-height: 1; + color: var(--agent-primary); +} + +.agent-insight-card[data-tone='success'] .agent-insight-card__value { + color: var(--agent-success); +} + +.agent-insight-card[data-tone='warning'] .agent-insight-card__value { + color: var(--agent-warning); +} + +.agent-insight-card[data-tone='danger'] .agent-insight-card__value { + color: var(--agent-danger); +} + +.agent-insight-card__copy, +.agent-insight-card__empty { + margin: 0; + font-size: 13px; + line-height: 1.5; + color: var(--agent-text); +} + +.agent-insight-card__empty { + display: grid; + place-items: center; + min-height: 132px; + color: var(--agent-muted); +} + +.agent-insight-card__footer { + display: flex; + gap: 8px; + align-items: center; + justify-content: space-between; + min-height: 48px; + padding: 0 12px; + border-top: 1px solid var(--agent-border); +} + +.agent-insight-card__nav { + display: flex; + gap: 6px; +} + +.agent-insight-card__nav-btn { + display: grid; + place-items: center; + width: 32px; + height: 32px; + font-size: 16px; + color: var(--agent-muted); + background: var(--agent-surface); + border: 1px solid var(--agent-border); + border-radius: 9px; +} + +.agent-insight-card__action { + min-height: 32px; + padding: 0 11px; + font-size: 11px; + font-weight: 750; + color: var(--agent-primary-foreground); + background: var(--agent-primary); + border: 1px solid var(--agent-primary); + border-radius: 9px; +} + +.agent-selection-actions { + display: grid; + gap: 0; + overflow: hidden; + background: var(--agent-surface); + border: 1px solid var(--agent-border); + border-radius: 16px; +} + +.agent-selection-actions__quote { + padding: 14px 16px; + margin: 0; + font-size: 13px; + line-height: 1.65; + color: var(--agent-text); + background: var(--agent-surface-strong); + border-left: 3px solid var(--agent-primary); +} + +.agent-selection-actions__toolbar { + display: flex; + flex-wrap: wrap; + gap: 7px; + padding: 10px 12px 12px; +} + +.agent-selection-actions__action { + min-height: 32px; + padding: 0 11px; + font-size: 11px; + font-weight: 750; + color: var(--agent-text-regular); + background: var(--agent-surface); + border: 1px solid var(--agent-border); + border-radius: 999px; +} + +.agent-selection-actions__action:hover { + color: var(--agent-primary); + border-color: var(--agent-primary); +} .agent-table { overflow: hidden; background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 15px; } .agent-table > header { display: flex; align-items: center; justify-content: space-between; min-height: 46px; padding: 0 13px; border-bottom: 1px solid var(--agent-border); } .agent-table > header strong { font-size: 12px; } @@ -548,33 +1308,78 @@ .agent-diff-table > footer button { min-height: 34px; padding: 0 10px; font-size: 11px; background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 9px; } .agent-diff-table > footer button:last-child { color: var(--agent-primary-foreground); background: var(--agent-primary); border-color: var(--agent-primary); } .agent-command-search { overflow: hidden; background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 16px; box-shadow: 0 18px 50px rgb(15 23 42 / 10%); } -.agent-command-search > label { display: flex; gap: 9px; align-items: center; min-height: 50px; padding: 0 13px; border-bottom: 1px solid var(--agent-border); } -.agent-command-search > label > span { font-size: 18px; color: var(--agent-muted); } + +.agent-command-search__field { + display: flex; + gap: 9px; + align-items: center; + min-height: 50px; + padding: 0 13px; + border-bottom: 1px solid var(--agent-border); +} + +.agent-command-search__icon { + display: grid; + flex: none; + place-items: center; + width: 18px; + height: 18px; + color: var(--agent-muted); +} +.agent-command-search__icon svg { display: block; width: 16px; height: 16px; } .agent-command-search input { flex: 1; min-width: 0; font: inherit; color: var(--agent-text); outline: 0; background: transparent; border: 0; } -.agent-command-search > div { display: grid; max-height: 280px; padding: 7px; overflow-y: auto; } -.agent-command-search > div button { display: flex; gap: 12px; align-items: center; justify-content: space-between; min-height: 44px; padding: 6px 8px; text-align: left; background: transparent; border: 0; border-radius: 10px; } -.agent-command-search > div button:hover { background: var(--agent-fill); } -.agent-command-search > div button > span { display: grid; } -.agent-command-search > div strong { font-size: 11px; color: var(--agent-text); } -.agent-command-search > div small { font-size: 10px; color: var(--agent-muted); } +.agent-command-search__list { display: grid; max-height: 280px; padding: 8px; overflow-y: auto; } + +.agent-command-search__group { + padding: 8px 8px 4px; + font-size: 10px; + font-weight: 750; + color: var(--agent-muted); + text-transform: uppercase; +} + +.agent-command-search__item { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; + min-height: 44px; + padding: 6px 8px; + text-align: left; + background: transparent; + border: 0; + border-radius: 10px; +} +.agent-command-search__item:hover { background: var(--agent-fill); } +.agent-command-search__copy { display: grid; min-width: 0; } +.agent-command-search__copy strong { font-size: 12px; color: var(--agent-text); } +.agent-command-search__copy small { font-size: 11px; color: var(--agent-muted); } .agent-command-search kbd { padding: 2px 6px; font-size: 10px; color: var(--agent-muted); background: var(--agent-surface-strong); border: 1px solid var(--agent-border); border-radius: 6px; } -.agent-command-search > p { padding: 22px; margin: 0; font-size: 11px; color: var(--agent-muted); text-align: center; } +.agent-command-search__empty { padding: 22px; margin: 0; font-size: 11px; color: var(--agent-muted); text-align: center; } .agent-flowchart { overflow: hidden; background: radial-gradient(var(--agent-border) 1px, transparent 1px); background-size: 14px 14px; border: 1px solid var(--agent-border); border-radius: 18px; } -.agent-flowchart > header { display: flex; align-items: center; justify-content: space-between; min-height: 48px; padding: 0 13px; background: var(--agent-surface); border-bottom: 1px solid var(--agent-border); } -.agent-flowchart > header strong { font-size: 12px; } -.agent-flowchart > header button { min-height: 32px; padding: 0 9px; font-size: 10px; color: var(--agent-primary); background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 9px; } -.agent-flowchart > div { display: grid; justify-items: center; padding: 24px; } -.agent-flowchart__node { display: grid; gap: 4px; width: min(300px, 100%); padding: 12px 14px; text-align: left; background: var(--agent-surface); border: 1px solid var(--agent-border-strong); border-radius: 14px; box-shadow: 0 8px 22px rgb(15 23 42 / 7%); } -.agent-flowchart__node[data-type='trigger'] { border-color: #99f6e4; } -.agent-flowchart__node[data-type='condition'] { border-color: #fde68a; } -.agent-flowchart__node[data-type='result'] { border-color: #bbf7d0; } -.agent-flowchart__node small { font-size: 10px; font-weight: 800; color: var(--agent-muted); text-transform: uppercase; } -.agent-flowchart__node strong { font-size: 11px; color: var(--agent-text); } -.agent-flowchart__node > span { font-size: 10px; color: var(--agent-muted); } -.agent-flowchart__connector { display: grid; place-items: center; width: 24px; height: 36px; font-weight: 900; color: var(--agent-primary); background: linear-gradient(#cbd5e1,#cbd5e1) center/1px 100% no-repeat; border: 0; } -.agent-fine-tune > div { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 12px; } +.agent-flowchart__header { display: flex; gap: 12px; align-items: center; justify-content: space-between; min-height: 52px; padding: 0 14px; background: var(--agent-surface); border-bottom: 1px solid var(--agent-border); } +.agent-flowchart__heading { display: grid; gap: 2px; min-width: 0; } +.agent-flowchart__heading strong { font-size: 13px; font-weight: 760; } +.agent-flowchart__heading small { font-size: 11px; color: var(--agent-muted); } +.agent-flowchart__add { display: inline-flex; gap: 6px; align-items: center; min-height: 32px; padding: 0 9px; font-size: 11px; font-weight: 750; color: var(--agent-primary); background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 9px; } +.agent-flowchart__add svg { width: 13px; height: 13px; } +.agent-flowchart__canvas { display: grid; justify-items: center; padding: 24px; } +.agent-flowchart__node { display: grid; gap: 6px; width: min(320px, 100%); padding: 12px 14px; text-align: left; background: var(--agent-surface); border: 1px solid var(--agent-border-strong); border-radius: 14px; box-shadow: 0 8px 22px rgb(15 23 42 / 7%); } +.agent-flowchart__node[data-type='trigger'] { border-color: color-mix(in srgb, var(--agent-primary) 40%, var(--agent-border)); } +.agent-flowchart__node[data-type='condition'] { border-color: color-mix(in srgb, var(--agent-warning) 50%, var(--agent-border)); } +.agent-flowchart__node[data-type='action'] { border-color: color-mix(in srgb, var(--varo-agent-source-violet, #7c3aed) 40%, var(--agent-border)); } +.agent-flowchart__node[data-type='result'] { border-color: color-mix(in srgb, var(--agent-success) 50%, var(--agent-border)); } +.agent-flowchart__node[data-status='running'] { box-shadow: 0 0 0 2px color-mix(in srgb, var(--agent-primary) 18%, transparent); } +.agent-flowchart__node-head { display: flex; gap: 8px; align-items: center; } +.agent-flowchart__index { display: grid; place-items: center; width: 22px; height: 22px; font-size: 10px; font-weight: 800; color: var(--agent-primary); background: var(--agent-primary-soft); border-radius: 7px; } +.agent-flowchart__type { padding: 1px 7px; font-size: 10px; font-weight: 750; color: var(--agent-muted); text-transform: none; background: var(--agent-fill); border-radius: 999px; } +.agent-flowchart__node strong { font-size: 13px; font-weight: 760; color: var(--agent-text); } +.agent-flowchart__detail { font-size: 11px; line-height: 16px; color: var(--agent-muted); } +.agent-flowchart__connector { display: grid; place-items: center; width: 24px; height: 36px; color: var(--agent-primary); background: linear-gradient(var(--agent-border), var(--agent-border)) center/1px 100% no-repeat; border: 0; } +.agent-fine-tune { overflow: hidden; background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 16px; } +.agent-fine-tune__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 12px; } .agent-fine-tune__field { display: grid; gap: 6px; min-width: 0; padding: 0; margin: 0; border: 0; } -.agent-fine-tune__label { padding: 0; font-size: 10px; font-weight: 650; color: var(--agent-muted); } +.agent-fine-tune__label { padding: 0; font-size: 11px; font-weight: 650; color: var(--agent-muted); } .agent-fine-tune__field > input { box-sizing: border-box; width: 100%; min-height: 40px; padding: 0 10px; font: inherit; color: var(--agent-text); background: var(--agent-surface); border: 1px solid var(--agent-border); border-radius: 10px; } .agent-fine-tune__choices { display: grid; grid-template-columns: repeat(auto-fit, minmax(104px, 1fr)); gap: 6px; } .agent-fine-tune__choice { position: relative; display: flex; gap: 8px; align-items: center; justify-content: space-between; min-height: 40px; padding: 0 11px; color: var(--agent-text-regular); cursor: pointer; background: var(--agent-surface-strong); border: 1px solid var(--agent-border); border-radius: 10px; transition: border-color 160ms ease, background 160ms ease, color 160ms ease, box-shadow 160ms ease; } @@ -583,6 +1388,8 @@ .agent-fine-tune__choice:focus-within { outline: 2px solid var(--agent-primary); outline-offset: 2px; } .agent-fine-tune__choice-check { min-width: 12px; font-size: 12px; font-weight: 850; text-align: center; } .agent-fine-tune__empty { display: grid; place-items: center; min-height: 40px; font-size: 11px; color: var(--agent-muted); background: var(--agent-surface-strong); border: 1px dashed var(--agent-border); border-radius: 10px; } +.agent-fine-tune__footer { display: flex; align-items: center; justify-content: flex-end; min-height: 48px; padding: 0 12px; border-top: 1px solid var(--agent-border); } +.agent-fine-tune__apply { min-height: 34px; padding: 0 11px; font-size: 11px; font-weight: 750; color: var(--agent-primary-foreground); background: var(--agent-primary); border: 1px solid var(--agent-primary); border-radius: 10px; } @media (prefers-reduced-motion: reduce) { .agent-advanced__status i, @@ -610,4 +1417,4 @@ } } -@media (max-width: 600px) { .agent-sidebar { width: 100%; } .agent-sidebar[data-collapsed='true'] { width: 64px; } .agent-fine-tune > div { grid-template-columns: minmax(0, 1fr); } } +@media (max-width: 600px) { .agent-sidebar { width: 100%; } .agent-sidebar[data-collapsed='true'] { width: 64px; } .agent-fine-tune__grid { grid-template-columns: minmax(0, 1fr); } } diff --git a/apps/docs/src/components/agent-ui/agent-ui.css b/apps/docs/src/components/agent-ui/agent-ui.css index 969bc70d..97a20b58 100644 --- a/apps/docs/src/components/agent-ui/agent-ui.css +++ b/apps/docs/src/components/agent-ui/agent-ui.css @@ -1,15 +1,37 @@ @keyframes varo-agent-pulse { - 0%, 100% { opacity: .35; } - 50% { opacity: 1; } + 0%, + 100% { + opacity: 0.35; + } + + 50% { + opacity: 1; + } } @keyframes varo-agent-cursor { - 0%, 48% { opacity: 1; } - 49%, 100% { opacity: 0; } + 0%, + 48% { + opacity: 1; + } + + 49%, + 100% { + opacity: 0; + } +} + +.agent-ui__loading i { + animation: varo-agent-pulse 1s ease-in-out infinite; +} + +.agent-ui__pulse { + animation: varo-agent-pulse 1s ease-in-out infinite; +} + +.agent-ui__cursor { + animation: varo-agent-cursor 0.72s steps(1) infinite; } -.agent-ui__loading i { animation: varo-agent-pulse 1s ease-in-out infinite; } -.agent-ui__pulse { animation: varo-agent-pulse 1s ease-in-out infinite; } -.agent-ui__cursor { animation: varo-agent-cursor .72s steps(1) infinite; } .agent-thinking { --agent-thinking-surface: var(--varo-agent-surface, #fff); @@ -52,10 +74,35 @@ border-radius: 11px; box-shadow: 0 4px 12px color-mix(in srgb, var(--varo-agent-primary, #0f766e) 10%, transparent); } -.agent-thinking__icon svg { display: block; width: 19px; height: 19px; } -.agent-thinking__copy { display: grid; flex: 1; gap: 2px; min-width: 0; } -.agent-thinking__title { overflow: hidden; text-overflow: ellipsis; font-size: 13px; font-weight: 760; line-height: 18px; color: var(--agent-thinking-text); white-space: nowrap; } -.agent-thinking__summary { font-size: 11px; line-height: 16px; color: var(--agent-thinking-muted); } + +.agent-thinking__icon svg { + display: block; + width: 19px; + height: 19px; +} + +.agent-thinking__copy { + display: grid; + flex: 1; + gap: 2px; + min-width: 0; +} + +.agent-thinking__title { + overflow: hidden; + text-overflow: ellipsis; + font-size: 13px; + font-weight: 760; + line-height: 18px; + color: var(--agent-thinking-text); + white-space: nowrap; +} + +.agent-thinking__summary { + font-size: 11px; + line-height: 16px; + color: var(--agent-thinking-muted); +} .agent-thinking__chevron { display: block; @@ -65,7 +112,10 @@ color: var(--agent-thinking-muted); transition: transform 200ms ease; } -.agent-thinking[data-open='true'] .agent-thinking__chevron { transform: rotate(180deg); } + +.agent-thinking[data-open='true'] .agent-thinking__chevron { + transform: rotate(180deg); +} .agent-thinking__body { display: grid; @@ -94,17 +144,57 @@ border: 2px solid var(--agent-thinking-surface-strong); border-radius: 999px; } -.agent-thinking__step[data-status='completed'] .agent-thinking__dot { background: var(--varo-agent-success, #16a34a); } -.agent-thinking__step[data-status='running'] .agent-thinking__dot { background: var(--varo-agent-primary, #0f766e); } -.agent-thinking__step[data-status='failed'] .agent-thinking__dot { background: var(--varo-agent-danger, #dc2626); } -.agent-thinking__step-copy { display: grid; flex: 1; gap: 3px; min-width: 0; } -.agent-thinking__step-header { display: flex; gap: 10px; align-items: center; justify-content: space-between; } -.agent-thinking__step-header strong { font-size: 12px; font-weight: 680; line-height: 17px; color: var(--agent-thinking-text); } -.agent-thinking__step-header small, .agent-thinking__detail { font-size: 11px; line-height: 16px; color: var(--agent-thinking-muted); } + +.agent-thinking__step[data-status='completed'] .agent-thinking__dot { + background: var(--varo-agent-success, #16a34a); +} + +.agent-thinking__step[data-status='running'] .agent-thinking__dot { + background: var(--varo-agent-primary, #0f766e); +} + +.agent-thinking__step[data-status='failed'] .agent-thinking__dot { + background: var(--varo-agent-danger, #dc2626); +} + +.agent-thinking__step-copy { + display: grid; + flex: 1; + gap: 3px; + min-width: 0; +} + +.agent-thinking__step-header { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; +} + +.agent-thinking__step-header strong { + font-size: 12px; + font-weight: 680; + line-height: 17px; + color: var(--agent-thinking-text); +} + +.agent-thinking__step-header small, +.agent-thinking__detail { + font-size: 11px; + line-height: 16px; + color: var(--agent-thinking-muted); +} @media (max-width: 640px) { - .agent-thinking .agent-thinking__trigger { min-height: 60px; padding-inline: 12px; } - .agent-thinking__body { padding-right: 12px; padding-left: 27px; } + .agent-thinking .agent-thinking__trigger { + min-height: 60px; + padding-inline: 12px; + } + + .agent-thinking__body { + padding-right: 12px; + padding-left: 27px; + } } .agent-approval { @@ -290,7 +380,9 @@ } .agent-composer__shell { - transition: border-color 160ms ease, box-shadow 160ms ease; + transition: + border-color 160ms ease, + box-shadow 160ms ease; } .agent-composer__shell:focus-within { @@ -305,11 +397,632 @@ } @media (prefers-reduced-motion: reduce) { - .agent-thinking__chevron { transition: none; } + .agent-thinking__chevron { + transition: none; + } } @media (prefers-reduced-motion: reduce) { .agent-ui__loading i, .agent-ui__pulse, - .agent-ui__cursor { animation: none; } + .agent-ui__cursor, + .agent-task-list__item[data-status='running'] .agent-task-list__marker i, + .agent-workspace-card__row[data-status='reading'] .agent-workspace-card__mark i { + animation: none; + } +} + +.agent-task-list { + overflow: hidden; + color: var(--varo-agent-foreground, #172033); + background: var(--varo-agent-surface, #fff); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 16px; +} + +.agent-task-list__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + border-bottom: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-task-list__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-task-list__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-task-list__heading small, +.agent-task-list__count { + font-size: 11px; + line-height: 16px; + color: var(--varo-agent-muted, #667085); +} + +.agent-task-list__count { + flex: none; + font-variant-numeric: tabular-nums; +} + +.agent-task-list__body { + display: grid; + padding: 6px 14px 12px 18px; + margin: 0; + list-style: none; +} + +.agent-task-list__item { + position: relative; + display: block; + min-height: 52px; + padding: 10px 0 10px 16px; + list-style: none; + border-left: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-task-list__item::marker { + content: none; +} + +.agent-task-list__marker { + position: absolute; + top: 14px; + left: -11px; + display: grid; + place-items: center; + width: 22px; + height: 22px; + font-size: 10px; + font-weight: 800; + color: var(--varo-agent-muted, #667085); + background: var(--varo-agent-surface, #fff); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 999px; +} + +.agent-task-list__item[data-status='completed'] .agent-task-list__marker { + color: var(--varo-agent-success, #16a34a); + background: var(--varo-agent-success-soft, #dcfce7); + border-color: var(--varo-agent-success, #16a34a); +} + +.agent-task-list__item[data-status='running'] .agent-task-list__marker { + color: var(--varo-agent-primary, #0f766e); + background: var(--varo-agent-primary-soft, #ccfbf1); + border-color: var(--varo-agent-primary, #0f766e); +} + +.agent-task-list__item[data-status='failed'] .agent-task-list__marker { + color: var(--varo-agent-danger, #dc2626); + background: var(--varo-agent-danger-soft, #fee2e2); + border-color: var(--varo-agent-danger, #dc2626); +} + +.agent-task-list__item[data-status='running'] .agent-task-list__marker i { + width: 7px; + height: 7px; + background: currentcolor; + border-radius: 999px; + animation: varo-agent-pulse 1s ease-in-out infinite; +} + +.agent-task-list__copy { + display: grid; + gap: 5px; + min-width: 0; +} + +.agent-task-list__row { + display: flex; + gap: 10px; + align-items: flex-start; + justify-content: space-between; +} + +.agent-task-list__copy strong { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + font-weight: 700; + line-height: 18px; + overflow-wrap: normal; + white-space: nowrap; +} + +.agent-task-list__item[data-status='waiting'] .agent-task-list__copy strong, +.agent-task-list__item[data-status='idle'] .agent-task-list__copy strong { + color: var(--varo-agent-muted, #667085); +} + +.agent-task-list__meta { + display: flex; + flex: none; + gap: 6px; + align-items: center; +} + +.agent-task-list__chip, +.agent-task-list__meta > small { + font-size: 10px; + font-variant-numeric: tabular-nums; + line-height: 14px; + color: var(--varo-agent-muted, #667085); +} + +.agent-task-list__chip { + padding: 2px 7px; + font-weight: 750; + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 999px; +} + +.agent-task-list__item[data-status='completed'] .agent-task-list__chip { + color: var(--varo-agent-success, #16a34a); + background: var(--varo-agent-success-soft, #dcfce7); + border-color: color-mix(in srgb, var(--varo-agent-success, #16a34a) 28%, transparent); +} + +.agent-task-list__item[data-status='running'] .agent-task-list__chip { + color: var(--varo-agent-primary, #0f766e); + background: var(--varo-agent-primary-soft, #ccfbf1); + border-color: color-mix(in srgb, var(--varo-agent-primary, #0f766e) 28%, transparent); +} + +.agent-task-list__item[data-status='failed'] .agent-task-list__chip { + color: var(--varo-agent-danger, #dc2626); + background: var(--varo-agent-danger-soft, #fee2e2); + border-color: color-mix(in srgb, var(--varo-agent-danger, #dc2626) 28%, transparent); +} + +.agent-task-list__copy p { + margin: 0; + font-size: 11px; + line-height: 16px; + color: var(--varo-agent-muted, #667085); +} + +.agent-task-list__track { + display: block; + width: 100%; + height: 4px; + overflow: hidden; + background: var(--varo-agent-fill, #f1f5f9); + border-radius: 999px; +} + +.agent-task-list__track i { + display: block; + height: 100%; + background: var(--varo-agent-primary, #0f766e); + border-radius: inherit; +} + +.agent-task-list__item[data-status='completed'] .agent-task-list__track i { + background: var(--varo-agent-success, #16a34a); +} + +.agent-task-list__item[data-status='failed'] .agent-task-list__track i { + background: var(--varo-agent-danger, #dc2626); +} + +.agent-task-list__item[data-status='running'] { + background: color-mix(in srgb, var(--varo-agent-primary-soft, #ccfbf1) 42%, transparent); + border-radius: 0 12px 12px 0; +} + +.agent-composer-scope, +.agent-retrieval, +.agent-source-receipt, +.agent-thread-versions, +.agent-task-runner__controls { + overflow: hidden; + color: var(--varo-agent-foreground, #172033); + background: var(--varo-agent-surface, #fff); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 16px; +} + +.agent-task-runner { + display: grid; + gap: 10px; +} + +.agent-workspace-card__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + border-bottom: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-workspace-card__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-workspace-card__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-workspace-card__heading small, +.agent-workspace-card__count, +.agent-workspace-card__detail, +.agent-workspace-card__meta, +.agent-workspace-card__empty, +.agent-thread-versions__meta, +.agent-task-runner__cancel span { + font-size: 11px; + line-height: 16px; + color: var(--varo-agent-muted, #667085); +} + +.agent-workspace-card__count, +.agent-workspace-card__meta { + flex: none; + font-variant-numeric: tabular-nums; +} + +.agent-workspace-card__body { + display: grid; + padding: 8px 10px 10px; +} + +.agent-workspace-card__row { + display: flex; + gap: 10px; + align-items: center; + min-height: 58px; + padding: 9px 10px; + border-radius: 12px; +} + +.agent-workspace-card__row[data-status='reading'], +.agent-workspace-card__row[data-status='connecting'], +.agent-workspace-card__row[data-enabled='true'] { + background: color-mix(in srgb, var(--varo-agent-primary-soft, #ccfbf1) 42%, transparent); +} + +.agent-workspace-card__row[data-status='failed'] { + background: var(--varo-agent-danger-soft, #fee2e2); +} + +.agent-workspace-card__mark { + display: grid; + flex: none; + place-items: center; + width: 28px; + height: 28px; + font-size: 10px; + font-weight: 800; + color: var(--varo-agent-muted, #667085); + background: var(--varo-agent-fill, #f1f5f9); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 9px; +} + +.agent-workspace-card__row[data-status='available'] .agent-workspace-card__mark, +.agent-workspace-card__row[data-status='read'] .agent-workspace-card__mark { + color: var(--varo-agent-success, #16a34a); + background: var(--varo-agent-success-soft, #dcfce7); + border-color: var(--varo-agent-success, #16a34a); +} + +.agent-workspace-card__row[data-status='reading'] .agent-workspace-card__mark, +.agent-workspace-card__row[data-status='connecting'] .agent-workspace-card__mark { + color: var(--varo-agent-primary, #0f766e); + background: var(--varo-agent-primary-soft, #ccfbf1); + border-color: var(--varo-agent-primary, #0f766e); +} + +.agent-workspace-card__row[data-status='failed'] .agent-workspace-card__mark, +.agent-workspace-card__row[data-status='unavailable'] .agent-workspace-card__mark { + color: var(--varo-agent-danger, #dc2626); + background: var(--varo-agent-danger-soft, #fee2e2); + border-color: var(--varo-agent-danger, #dc2626); +} + +.agent-workspace-card__row[data-status='reading'] .agent-workspace-card__mark i { + width: 7px; + height: 7px; + background: currentcolor; + border-radius: 999px; + animation: varo-agent-pulse 1s ease-in-out infinite; +} + +.agent-workspace-card__copy { + display: grid; + flex: 1; + gap: 4px; + min-width: 0; +} + +.agent-workspace-card__row-main { + display: flex; + gap: 8px; + align-items: center; + min-width: 0; +} + +.agent-workspace-card__copy strong, +.agent-workspace-card__row-main strong { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + font-weight: 700; + line-height: 18px; + white-space: nowrap; +} + +.agent-workspace-card__chip { + flex: none; + padding: 2px 7px; + font-size: 10px; + font-weight: 750; + line-height: 14px; + color: var(--varo-agent-muted, #667085); + background: var(--varo-agent-fill, #f1f5f9); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 999px; +} + +.agent-workspace-card__row[data-status='read'] .agent-workspace-card__chip, +.agent-workspace-card__row[data-status='available'] .agent-workspace-card__chip { + color: var(--varo-agent-success, #16a34a); + background: var(--varo-agent-success-soft, #dcfce7); + border-color: color-mix(in srgb, var(--varo-agent-success, #16a34a) 28%, transparent); +} + +.agent-workspace-card__row[data-status='reading'] .agent-workspace-card__chip, +.agent-workspace-card__row[data-status='connecting'] .agent-workspace-card__chip { + color: var(--varo-agent-primary, #0f766e); + background: var(--varo-agent-primary-soft, #ccfbf1); + border-color: color-mix(in srgb, var(--varo-agent-primary, #0f766e) 28%, transparent); +} + +.agent-workspace-card__row[data-status='failed'] .agent-workspace-card__chip, +.agent-workspace-card__row[data-status='unavailable'] .agent-workspace-card__chip { + color: var(--varo-agent-danger, #dc2626); + background: var(--varo-agent-danger-soft, #fee2e2); + border-color: color-mix(in srgb, var(--varo-agent-danger, #dc2626) 28%, transparent); +} + +.agent-workspace-card__row[data-status='skipped'] .agent-workspace-card__chip { + color: var(--varo-agent-warning, #b45309); + background: var(--varo-agent-warning-soft, #fef3c7); +} + +.agent-workspace-card__empty { + display: grid; + place-items: center; + min-height: 72px; + padding: 12px; + margin: 0; +} + +.agent-workspace-card__action { + position: relative; + box-sizing: border-box; + display: inline-flex; + flex: none; + align-items: center; + justify-content: center; + min-width: 48px; + min-height: 36px; + padding: 0 10px; + margin: 0; + font-size: 11px; + font-weight: 750; + line-height: 1; + color: var(--varo-agent-foreground, #172033); + background: transparent; + border: 1px solid transparent; + border-radius: 9px; +} + +.agent-workspace-card__action::before { + position: absolute; + inset: -4px; + content: ''; +} + +.agent-workspace-card__action:hover { + background: var(--varo-agent-fill, #f1f5f9); +} + +.agent-workspace-card__action--primary { + color: var(--varo-agent-primary, #0f766e); +} + +.agent-workspace-card__action--primary:hover { + background: var(--varo-agent-primary-soft, #ccfbf1); +} + +.agent-workspace-card__action--danger { + color: var(--varo-agent-danger, #dc2626); +} + +.agent-workspace-card__action--danger:hover { + background: var(--varo-agent-danger-soft, #fee2e2); +} + +.agent-workspace-card__action:disabled { + cursor: not-allowed; + opacity: 0.45; +} + +.agent-composer-scope__meter { + display: grid; + gap: 8px; + padding: 10px 14px 12px; + border-top: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-composer-scope__meter strong { + font-size: 11px; + font-weight: 760; + font-variant-numeric: tabular-nums; + color: var(--varo-agent-primary, #0f766e); +} + +.agent-composer-scope__track { + display: block; + height: 6px; + overflow: hidden; + background: var(--varo-agent-fill, #f1f5f9); + border-radius: 999px; +} + +.agent-composer-scope__track i { + display: block; + height: 100%; + background: var(--varo-agent-primary, #0f766e); + border-radius: inherit; +} + +.agent-thread-versions__list { + display: grid; + gap: 8px; + padding: 10px; +} + +.agent-thread-versions__card { + display: grid; + gap: 8px; + min-width: 0; + padding: 12px; + background: var(--varo-agent-surface-strong, #f8fafc); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 14px; +} + +.agent-thread-versions__card[data-active='true'] { + background: var(--varo-agent-primary-soft, #ccfbf1); + border-color: var(--varo-agent-primary, #0f766e); +} + +.agent-thread-versions__meta, +.agent-thread-versions__actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +} + +.agent-thread-versions__actions { + padding-top: 8px; + border-top: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-task-runner__cancel { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; + min-height: 48px; + padding: 8px 14px 10px; + border-top: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-artifact { + overflow: hidden; + color: #dbeafe; + background: #0f172a; + border: 1px solid #1e293b; + border-radius: 16px; +} + +.agent-artifact__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + background: #111c30; + border-bottom: 1px solid #26334a; +} + +.agent-artifact__heading { + display: flex; + flex-wrap: wrap; + gap: 6px 8px; + align-items: center; + min-width: 0; +} + +.agent-artifact__kind { + font-size: 10px; + font-weight: 800; + color: #5eead4; + text-transform: uppercase; + letter-spacing: 0.12em; +} + +.agent-artifact__title { + overflow: hidden; + text-overflow: ellipsis; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + font-weight: 650; + color: #f8fafc; + white-space: nowrap; +} + +.agent-artifact__lang { + padding: 1px 7px; + font-size: 10px; + font-weight: 750; + color: #94a3b8; + background: rgb(148 163 184 / 12%); + border-radius: 999px; +} + +.agent-artifact__open { + flex: none; + min-height: 32px; + padding: 0 10px; + font-size: 11px; + font-weight: 750; + color: #cbd5e1; + background: rgb(148 163 184 / 10%); + border: 1px solid rgb(148 163 184 / 22%); + border-radius: 9px; +} + +.agent-artifact__body { + max-height: 288px; + padding: 14px 16px; + margin: 0; + overflow: auto; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + line-height: 1.7; + color: #cbd5e1; + white-space: pre-wrap; + background: #0f172a; +} + +.agent-artifact__preview { + display: block; + width: 100%; + max-height: 320px; + object-fit: contain; + background: #0b1220; } diff --git a/apps/docs/src/components/agent-ui/index.ts b/apps/docs/src/components/agent-ui/index.ts index a2f7e050..31fcbb24 100644 --- a/apps/docs/src/components/agent-ui/index.ts +++ b/apps/docs/src/components/agent-ui/index.ts @@ -102,6 +102,30 @@ function partMark(status: AgentPartStatus) { return 'bg-[var(--varo-agent-border-strong)]' } +function taskStatusLabel(status: AgentPartStatus) { + if (status === 'completed') { return '已完成' } + if (status === 'failed') { return '失败' } + if (status === 'running') { return '进行中' } + if (status === 'waiting') { return '等待中' } + return '待开始' +} + +function renderCloseMiniIcon() { + return h('svg', { + 'fill': 'none', + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': 2.2, + 'viewBox': '0 0 24 24', + 'width': 12, + 'height': 12, + 'aria-hidden': 'true', + }, [ + h('path', { d: 'M6 6l12 12M18 6 6 18' }), + ]) +} + function renderCheckIcon() { return h('svg', { 'fill': 'none', @@ -292,37 +316,101 @@ export const AgentTaskList = defineComponent({ }, setup(props) { const completed = computed(() => props.tasks.filter(task => task.status === 'completed').length) - return () => h('section', { 'class': 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]', 'aria-live': 'polite' }, [ - h('header', { class: 'flex min-h-11 items-center justify-between border-b border-[var(--varo-agent-border)] px-3.5' }, [ - h('strong', { class: 'text-[13px] text-[var(--varo-agent-foreground)]' }, props.title), - h('span', { class: 'text-[12px] tabular-nums text-[var(--varo-agent-muted)]' }, `${completed.value}/${props.tasks.length}`), - ]), - ...props.tasks.map((task, index) => h('div', { class: 'flex min-h-[50px] items-center gap-2.5 border-b border-[var(--varo-agent-border)] px-3.5 py-2 last:border-0', key: task.id }, [ - h('span', { class: ['grid h-6 w-6 flex-none place-items-center rounded-full border text-[11px] font-bold', task.status === 'completed' ? 'border-[var(--varo-agent-success)] bg-[var(--varo-agent-success-soft)] text-[var(--varo-agent-success)]' : 'border-[var(--varo-agent-border)] text-[var(--varo-agent-text)]'] }, task.status === 'completed' ? renderCheckIcon() : String(index + 1)), - h('span', { class: 'grid min-w-0 flex-1 gap-1' }, [ - h('span', { class: 'flex justify-between gap-2 text-xs font-semibold text-[var(--varo-agent-foreground)]' }, [task.title, task.meta ? h('small', { class: 'text-[var(--varo-agent-muted)]' }, task.meta) : null]), - task.progress === undefined ? null : h('span', { class: 'h-1 overflow-hidden rounded-full bg-[var(--varo-agent-fill)]' }, [h('i', { class: 'block h-full bg-[var(--varo-agent-primary)]', style: { width: `${Math.min(100, Math.max(0, task.progress))}%` } })]), + const current = computed(() => props.tasks.find(task => task.status === 'running' || task.status === 'failed')) + return () => h('section', { 'class': 'agent-task-list', 'aria-live': 'polite' }, [ + h('header', { class: 'agent-task-list__header' }, [ + h('span', { class: 'agent-task-list__heading' }, [ + h('strong', props.title), + current.value + ? h('small', current.value.status === 'failed' ? `阻塞于 ${current.value.title}` : `正在执行 ${current.value.title}`) + : h('small', completed.value === props.tasks.length && props.tasks.length ? '全部完成' : '等待开始'), ]), - ])), + h('span', { class: 'agent-task-list__count' }, `${completed.value}/${props.tasks.length}`), + ]), + h('ol', { class: 'agent-task-list__body' }, props.tasks.map((task, index) => { + const progress = task.progress === undefined ? undefined : Math.min(100, Math.max(0, task.progress)) + return h('li', { + 'class': 'agent-task-list__item', + 'data-status': task.status, + 'aria-current': task.status === 'running' ? 'step' : undefined, + 'key': task.id, + }, [ + h('span', { 'class': 'agent-task-list__marker', 'aria-hidden': 'true' }, [ + task.status === 'completed' + ? renderCheckIcon() + : task.status === 'failed' + ? renderCloseMiniIcon() + : task.status === 'running' + ? h('i') + : String(index + 1), + ]), + h('span', { class: 'agent-task-list__copy' }, [ + h('span', { class: 'agent-task-list__row' }, [ + h('strong', task.title), + h('span', { class: 'agent-task-list__meta' }, [ + h('small', { class: 'agent-task-list__chip' }, taskStatusLabel(task.status)), + task.meta ? h('small', task.meta) : null, + ]), + ]), + task.description ? h('p', task.description) : null, + progress === undefined + ? null + : h('span', { 'class': 'agent-task-list__track', 'aria-hidden': 'true' }, [ + h('i', { style: { width: `${progress}%` } }), + ]), + ]), + ]) + })), ]) }, }) -const workspaceActionButton = 'relative m-0 inline-flex min-h-9 min-w-12 items-center justify-center rounded-lg px-2.5 text-[11px] font-bold transition-colors before:absolute before:-inset-1 before:content-[\'\'] motion-reduce:transition-none disabled:cursor-not-allowed disabled:opacity-45' -const workspacePrimaryTextButton = `${workspaceActionButton} border border-transparent bg-transparent text-[var(--varo-agent-primary)] hover:bg-[var(--varo-agent-primary-soft)]` -const workspaceQuietButton = `${workspaceActionButton} border border-transparent bg-transparent text-[var(--varo-agent-foreground)] hover:bg-[var(--varo-agent-fill)]` -const workspaceDangerTextButton = `${workspaceActionButton} border border-transparent bg-transparent text-[var(--varo-agent-danger)] hover:bg-[var(--varo-agent-danger-soft)]` +function renderWorkspaceIcon(kind: 'plug' | 'receipt' | 'branch') { + const common = { + 'aria-hidden': 'true' as const, + 'fill': 'none', + 'height': 14, + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': 1.8, + 'viewBox': '0 0 24 24', + 'width': 14, + } + if (kind === 'receipt') { + return h('svg', common, [ + h('path', { d: 'M7 3h8l4 4v14H7V3Z' }), + h('path', { d: 'M15 3v4h4M9 12h6M9 16h4' }), + ]) + } + if (kind === 'branch') { + return h('svg', common, [ + h('circle', { cx: '6', cy: '6', r: '2.2' }), + h('circle', { cx: '18', cy: '12', r: '2.2' }), + h('circle', { cx: '6', cy: '18', r: '2.2' }), + h('path', { d: 'M8.1 7.6v8.8M8.2 12H16' }), + ]) + } + return h('svg', common, [ + h('path', { d: 'M9 7V3M15 7V3' }), + h('path', { d: 'M8 7h8v4a4 4 0 0 1-4 4h0a4 4 0 0 1-4-4V7Z' }), + h('path', { d: 'M12 15v6' }), + ]) +} + +function workspaceActionClass(kind: 'quiet' | 'primary' | 'danger' = 'quiet') { + return cn( + 'agent-workspace-card__action', + kind === 'primary' && 'agent-workspace-card__action--primary', + kind === 'danger' && 'agent-workspace-card__action--danger', + ) +} const contextSourceStatusLabels = { available: '可用', connecting: '连接中', unavailable: '不可用', } as const -const contextSourceStatusClasses = { - available: 'bg-[var(--varo-agent-success-soft)] text-[var(--varo-agent-success)]', - connecting: 'bg-[var(--varo-agent-primary-soft)] text-[var(--varo-agent-primary)]', - unavailable: 'bg-[var(--varo-agent-danger-soft)] text-[var(--varo-agent-danger)]', -} as const const retrievalStatusLabels = { failed: '读取失败', queued: '排队中', @@ -330,23 +418,11 @@ const retrievalStatusLabels = { reading: '读取中', skipped: '已跳过', } as const -const retrievalStatusClasses = { - failed: 'bg-[var(--varo-agent-danger-soft)] text-[var(--varo-agent-danger)]', - queued: 'bg-[var(--varo-agent-fill)] text-[var(--varo-agent-muted)]', - read: 'bg-[var(--varo-agent-success-soft)] text-[var(--varo-agent-success)]', - reading: 'bg-[var(--varo-agent-primary-soft)] text-[var(--varo-agent-primary)]', - skipped: 'bg-[var(--varo-agent-warning-soft)] text-[var(--varo-agent-warning)]', -} as const const receiptStatusLabels = { failed: '读取失败', read: '已读取', skipped: '已跳过', } as const -const receiptStatusClasses = { - failed: 'bg-[var(--varo-agent-danger-soft)] text-[var(--varo-agent-danger)]', - read: 'bg-[var(--varo-agent-success-soft)] text-[var(--varo-agent-success)]', - skipped: 'bg-[var(--varo-agent-warning-soft)] text-[var(--varo-agent-warning)]', -} as const function renderCloseIcon() { return h('svg', { @@ -381,30 +457,38 @@ export const AgentComposerScope = defineComponent({ return Math.min(100, Math.max(0, value)) }) const enabledCount = computed(() => props.sources.filter(source => source.enabled).length) - return () => h('section', { 'class': 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]', 'aria-label': props.title }, [ - h('header', { class: 'flex min-h-12 items-center justify-between gap-3 border-b border-[var(--varo-agent-border)] px-3.5' }, [ - h('strong', { class: 'text-[13px] text-[var(--varo-agent-foreground)]' }, props.title), - h('span', { class: 'text-[11px] tabular-nums text-[var(--varo-agent-muted)]' }, `${enabledCount.value} 个已启用`), + const headingHint = computed(() => { + if (!props.sources.length) { return '暂无可用来源' } + if (enabledCount.value === 0) { return '尚未授权任何来源' } + return `${enabledCount.value} 个来源已加入上下文` + }) + return () => h('section', { 'class': 'agent-composer-scope', 'aria-label': props.title }, [ + h('header', { class: 'agent-workspace-card__header' }, [ + h('span', { class: 'agent-workspace-card__heading' }, [ + h('strong', props.title), + h('small', headingHint.value), + ]), + h('span', { class: 'agent-workspace-card__count' }, `${enabledCount.value}/${props.sources.length}`), ]), props.sources.length - ? h('div', { class: 'grid gap-1 p-2' }, props.sources.map((source) => { + ? h('div', { class: 'agent-workspace-card__body' }, props.sources.map((source) => { const status = source.status ?? 'available' - return h('div', { 'class': 'flex min-h-[58px] items-center gap-2.5 rounded-xl bg-[var(--varo-agent-surface-strong)] px-2.5 py-2', 'data-status': status, 'key': source.id }, [ - h('i', { 'class': ['h-3 w-3 flex-none rounded-full', contextSourceStatusClasses[status]], 'aria-hidden': 'true' }), - h('span', { class: 'grid min-w-0 flex-1 gap-0.5' }, [ - h('span', { class: 'flex min-w-0 items-center gap-2' }, [ - h('strong', { class: 'truncate text-[12px] text-[var(--varo-agent-foreground)]' }, source.label), - h('small', { class: 'flex-none text-[10px] font-semibold text-[var(--varo-agent-muted)]' }, contextSourceStatusLabels[status]), + return h('article', { 'class': 'agent-workspace-card__row', 'data-status': status, 'data-enabled': String(source.enabled), 'key': source.id }, [ + h('span', { 'class': 'agent-workspace-card__mark', 'aria-hidden': 'true' }, renderWorkspaceIcon('plug')), + h('span', { class: 'agent-workspace-card__copy' }, [ + h('span', { class: 'agent-workspace-card__row-main' }, [ + h('strong', source.label), + h('small', { class: 'agent-workspace-card__chip' }, contextSourceStatusLabels[status]), ]), source.description || source.meta - ? h('small', { class: 'truncate text-[11px] text-[var(--varo-agent-muted)]' }, source.description || source.meta) + ? h('small', { class: 'agent-workspace-card__detail' }, source.description || source.meta) : null, ]), status === 'available' ? h('button', { 'aria-label': `${source.enabled ? '停用' : '启用'}${source.label}`, 'aria-pressed': source.enabled, - 'class': workspaceQuietButton, + 'class': workspaceActionClass(source.enabled ? 'quiet' : 'primary'), 'disabled': props.disabled, 'type': 'button', 'onClick': () => emit('toggle', source, !source.enabled), @@ -412,29 +496,29 @@ export const AgentComposerScope = defineComponent({ : status === 'unavailable' ? h('button', { 'aria-label': `连接${source.label}`, - 'class': workspacePrimaryTextButton, + 'class': workspaceActionClass('primary'), 'disabled': props.disabled, 'type': 'button', 'onClick': () => emit('connect', source), }, '连接') - : null, + : h('small', { class: 'agent-workspace-card__chip' }, '连接中'), ]) })) - : h('p', { class: 'm-0 grid min-h-20 place-items-center px-3 text-[12px] text-[var(--varo-agent-muted)]' }, '暂无来源'), - h('footer', { class: 'grid gap-2 border-t border-[var(--varo-agent-border)] px-3.5 py-3' }, [ - h('span', { class: 'flex items-center justify-between gap-3' }, [ - h('span', { class: 'text-[11px] font-semibold text-[var(--varo-agent-text)]' }, '上下文使用'), - h('strong', { class: 'text-[11px] tabular-nums text-[var(--varo-agent-primary)]' }, `${usage.value}%`), + : h('p', { class: 'agent-workspace-card__empty' }, '暂无来源'), + h('footer', { class: 'agent-composer-scope__meter' }, [ + h('span', { class: 'agent-workspace-card__row-main' }, [ + h('span', '上下文使用'), + h('strong', `${usage.value}%`), ]), h('span', { - 'class': 'block h-1.5 overflow-hidden rounded-full bg-[var(--varo-agent-fill)]', + 'class': 'agent-composer-scope__track', 'role': 'progressbar', 'aria-label': '上下文使用量', 'aria-valuemax': 100, 'aria-valuemin': 0, 'aria-valuenow': usage.value, }, [ - h('i', { class: 'block h-full rounded-full bg-[var(--varo-agent-primary)]', style: { width: `${usage.value}%` } }), + h('i', { style: { width: `${usage.value}%` } }), ]), ]), ]) @@ -452,31 +536,41 @@ export const AgentRetrievalProgress = defineComponent({ }, setup(props, { emit }) { const settled = computed(() => props.items.filter(item => item.status === 'read' || item.status === 'skipped' || item.status === 'failed').length) - return () => h('section', { 'class': 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]', 'aria-atomic': 'false', 'aria-live': 'polite' }, [ - h('header', { class: 'flex min-h-12 items-center justify-between gap-3 border-b border-[var(--varo-agent-border)] px-3.5' }, [ - h('strong', { class: 'text-[13px] text-[var(--varo-agent-foreground)]' }, props.title), - h('span', { class: 'text-[11px] tabular-nums text-[var(--varo-agent-muted)]' }, `${settled.value}/${props.items.length} 已处理`), + const current = computed(() => props.items.find(item => item.status === 'reading' || item.status === 'failed')) + const headingHint = computed(() => { + if (current.value?.status === 'failed') { return `阻塞于 ${current.value.title}` } + if (current.value) { return `正在读取 ${current.value.title}` } + if (props.items.length && settled.value === props.items.length) { return '检索已完成' } + return '等待检索' + }) + return () => h('section', { 'class': 'agent-retrieval', 'aria-atomic': 'false', 'aria-live': 'polite' }, [ + h('header', { class: 'agent-workspace-card__header' }, [ + h('span', { class: 'agent-workspace-card__heading' }, [ + h('strong', props.title), + h('small', headingHint.value), + ]), + h('span', { class: 'agent-workspace-card__count' }, `${settled.value}/${props.items.length}`), ]), props.items.length - ? h('div', { class: 'grid gap-1 p-2' }, props.items.map(item => h('div', { 'class': 'flex min-h-[56px] items-center gap-2.5 rounded-xl px-2.5 py-2', 'data-status': item.status, 'key': item.id }, [ - h('i', { 'class': ['h-3 w-3 flex-none rounded-full', retrievalStatusClasses[item.status]], 'aria-hidden': 'true' }), - h('span', { class: 'grid min-w-0 flex-1 gap-0.5' }, [ - h('strong', { class: 'truncate text-[12px] text-[var(--varo-agent-foreground)]' }, item.title), - item.detail ? h('small', { class: 'text-[11px] leading-4 text-[var(--varo-agent-muted)]' }, item.detail) : null, - ]), - h('span', { class: 'flex flex-none items-center gap-2' }, [ - h('small', { class: 'text-[10px] font-semibold text-[var(--varo-agent-text)]' }, retrievalStatusLabels[item.status]), - item.status === 'failed' && item.retryable - ? h('button', { - 'aria-label': `重试${item.title}`, - 'class': workspaceQuietButton, - 'type': 'button', - 'onClick': () => emit('retry', item), - }, '重试') - : null, + ? h('div', { class: 'agent-workspace-card__body' }, props.items.map((item, index) => h('article', { 'class': 'agent-workspace-card__row', 'data-status': item.status, 'key': item.id }, [ + h('span', { 'class': 'agent-workspace-card__mark', 'aria-hidden': 'true' }, item.status === 'reading' ? h('i') : String(index + 1).padStart(2, '0')), + h('span', { class: 'agent-workspace-card__copy' }, [ + h('span', { class: 'agent-workspace-card__row-main' }, [ + h('strong', item.title), + h('small', { class: 'agent-workspace-card__chip' }, retrievalStatusLabels[item.status]), + ]), + item.detail ? h('small', { class: 'agent-workspace-card__detail' }, item.detail) : null, ]), + item.status === 'failed' && item.retryable + ? h('button', { + 'aria-label': `重试${item.title}`, + 'class': workspaceActionClass('primary'), + 'type': 'button', + 'onClick': () => emit('retry', item), + }, '重试') + : null, ]))) - : h('p', { class: 'm-0 grid min-h-20 place-items-center px-3 text-[12px] text-[var(--varo-agent-muted)]' }, '暂无检索项'), + : h('p', { class: 'agent-workspace-card__empty' }, '暂无检索项'), ]) }, }) @@ -494,44 +588,49 @@ export const AgentSourceReceipt = defineComponent({ }, setup(props, { emit }) { const readCount = computed(() => props.items.filter(item => item.status === 'read').length) - return () => h('section', { 'class': 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]', 'aria-label': props.title }, [ - h('header', { class: 'grid min-h-12 gap-0.5 border-b border-[var(--varo-agent-border)] px-3.5 py-2.5' }, [ - h('span', { class: 'flex items-center justify-between gap-3' }, [ - h('strong', { class: 'text-[13px] text-[var(--varo-agent-foreground)]' }, props.title), - h('small', { class: 'text-[11px] tabular-nums text-[var(--varo-agent-muted)]' }, `${readCount.value}/${props.items.length} 已读取`), + const failedCount = computed(() => props.items.filter(item => item.status === 'failed').length) + const headingHint = computed(() => { + if (failedCount.value) { return `${failedCount.value} 个来源读取失败` } + if (props.summary) { return props.summary } + if (props.items.length && readCount.value === props.items.length) { return '全部来源已核对' } + return '回答完成后核对来源' + }) + return () => h('section', { 'class': 'agent-source-receipt', 'aria-label': props.title }, [ + h('header', { class: 'agent-workspace-card__header' }, [ + h('span', { class: 'agent-workspace-card__heading' }, [ + h('strong', props.title), + h('small', headingHint.value), ]), - props.summary ? h('small', { class: 'text-[11px] leading-4 text-[var(--varo-agent-muted)]' }, props.summary) : null, + h('span', { class: 'agent-workspace-card__count' }, `${readCount.value}/${props.items.length}`), ]), props.items.length - ? h('div', { class: 'grid gap-1 p-2' }, props.items.map(item => h('div', { 'class': 'flex min-h-[56px] items-center gap-2.5 rounded-xl bg-[var(--varo-agent-surface-strong)] px-2.5 py-2', 'data-status': item.status, 'key': item.id }, [ - h('i', { 'class': ['h-3 w-3 flex-none rounded-full', receiptStatusClasses[item.status]], 'aria-hidden': 'true' }), - h('span', { class: 'grid min-w-0 flex-1 gap-0.5' }, [ - h('span', { class: 'flex min-w-0 items-baseline gap-2' }, [ - h('strong', { class: 'truncate text-[12px] text-[var(--varo-agent-foreground)]' }, item.label), - item.itemCount === undefined - ? null - : h('small', { class: 'flex-none text-[10px] tabular-nums text-[var(--varo-agent-muted)]' }, `${item.itemCount} 项`), + ? h('div', { class: 'agent-workspace-card__body' }, props.items.map(item => h('article', { 'class': 'agent-workspace-card__row', 'data-status': item.status, 'key': item.id }, [ + h('span', { 'class': 'agent-workspace-card__mark', 'aria-hidden': 'true' }, renderWorkspaceIcon('receipt')), + h('span', { class: 'agent-workspace-card__copy' }, [ + h('span', { class: 'agent-workspace-card__row-main' }, [ + h('strong', item.label), + item.itemCount === undefined ? null : h('small', { class: 'agent-workspace-card__meta' }, `${item.itemCount} 项`), ]), - item.detail ? h('small', { class: 'text-[11px] leading-4 text-[var(--varo-agent-muted)]' }, item.detail) : null, - h('small', { class: 'text-[10px] font-semibold text-[var(--varo-agent-text)]' }, receiptStatusLabels[item.status]), + item.detail ? h('small', { class: 'agent-workspace-card__detail' }, item.detail) : null, + h('small', { class: 'agent-workspace-card__chip' }, receiptStatusLabels[item.status]), ]), item.status === 'read' ? h('button', { 'aria-label': `查看${item.label}`, - 'class': workspaceQuietButton, + 'class': workspaceActionClass(), 'type': 'button', 'onClick': () => emit('open', item), }, '查看') : item.status === 'failed' ? h('button', { 'aria-label': `连接${item.label}`, - 'class': workspacePrimaryTextButton, + 'class': workspaceActionClass('primary'), 'type': 'button', 'onClick': () => emit('connect', item), }, '连接') : null, ]))) - : h('p', { class: 'm-0 grid min-h-20 place-items-center px-3 text-[12px] text-[var(--varo-agent-muted)]' }, '暂无来源回执'), + : h('p', { class: 'agent-workspace-card__empty' }, '暂无来源回执'), ]) }, }) @@ -557,38 +656,38 @@ export const AgentTaskRunner = defineComponent({ ) }) const canCancel = computed(() => props.busy || props.tasks.some(task => task.status === 'running')) - return () => h('section', { class: 'grid gap-2.5' }, [ + return () => h('section', { class: 'agent-task-runner' }, [ h(AgentTaskList, { tasks: props.tasks, title: props.title }), actionableTasks.value.length || canCancel.value - ? h('div', { class: 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]' }, [ + ? h('div', { class: 'agent-task-runner__controls' }, [ actionableTasks.value.length - ? h('div', { class: 'grid gap-1 p-2' }, actionableTasks.value.map(task => h('div', { class: 'flex min-h-[56px] items-center gap-2.5 rounded-xl bg-[var(--varo-agent-surface-strong)] px-2.5 py-2', key: task.id }, [ - h('span', { class: 'grid min-w-0 flex-1 gap-0.5' }, [ - h('strong', { class: 'truncate text-[12px] text-[var(--varo-agent-foreground)]' }, task.title), - task.description ? h('small', { class: 'text-[11px] leading-4 text-[var(--varo-agent-muted)]' }, task.description) : null, - h('small', { class: 'text-[10px] font-semibold text-[var(--varo-agent-text)]' }, task.status === 'failed' ? '执行失败' : '等待确认'), + ? h('div', { class: 'agent-workspace-card__body' }, actionableTasks.value.map(task => h('article', { 'class': 'agent-workspace-card__row', 'data-status': task.status, 'key': task.id }, [ + h('span', { class: 'agent-workspace-card__copy' }, [ + h('strong', task.title), + task.description ? h('small', { class: 'agent-workspace-card__detail' }, task.description) : null, + h('small', { class: 'agent-workspace-card__chip' }, task.status === 'failed' ? '执行失败' : '等待确认'), ]), task.status === 'failed' ? h('button', { 'aria-label': `重试${task.title}`, - 'class': workspaceQuietButton, + 'class': workspaceActionClass('primary'), 'type': 'button', 'onClick': () => emit('retry', task), }, '重试') : h('button', { 'aria-label': `批准${task.title}`, - 'class': workspacePrimaryTextButton, + 'class': workspaceActionClass('primary'), 'type': 'button', 'onClick': () => emit('approve', task), }, '批准'), ]))) : null, canCancel.value - ? h('div', { class: 'flex min-h-[60px] items-center justify-between gap-3 border-t border-[var(--varo-agent-border)] px-3.5 py-2' }, [ - h('span', { class: 'text-[11px] text-[var(--varo-agent-muted)]' }, '任务正在执行'), + ? h('div', { class: 'agent-task-runner__cancel' }, [ + h('span', '任务正在执行'), h('button', { 'aria-label': '取消当前任务', - 'class': workspaceDangerTextButton, + 'class': workspaceActionClass('danger'), 'type': 'button', 'onClick': () => emit('cancel'), }, '取消'), @@ -625,52 +724,49 @@ export const AgentThreadVersions = defineComponent({ version, })) }) - return () => h('section', { 'class': 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]', 'aria-label': props.title }, [ - h('header', { class: 'flex min-h-12 items-center justify-between gap-3 border-b border-[var(--varo-agent-border)] px-3.5' }, [ - h('strong', { class: 'text-[13px] text-[var(--varo-agent-foreground)]' }, props.title), - h('span', { class: 'text-[11px] tabular-nums text-[var(--varo-agent-muted)]' }, `${props.versions.length} 个版本`), + const active = computed(() => displayVersions.value.find(entry => entry.active)) + return () => h('section', { 'class': 'agent-thread-versions', 'aria-label': props.title }, [ + h('header', { class: 'agent-workspace-card__header' }, [ + h('span', { class: 'agent-workspace-card__heading' }, [ + h('strong', props.title), + h('small', active.value ? `当前 ${active.value.label}` : '选择一个会话版本'), + ]), + h('span', { class: 'agent-workspace-card__count' }, `${props.versions.length} 个版本`), ]), displayVersions.value.length - ? h('div', { 'class': 'flex max-w-full gap-2 overflow-x-auto p-2.5', 'role': 'list', 'aria-label': '会话版本列表' }, displayVersions.value.map(entry => + ? h('div', { 'class': 'agent-thread-versions__list', 'role': 'list', 'aria-label': '会话版本列表' }, displayVersions.value.map(entry => h('article', { - 'class': [ - 'grid w-[252px] flex-none gap-2 rounded-xl border bg-[var(--varo-agent-surface-strong)] p-3', - entry.active - ? 'border-[var(--varo-agent-primary)] shadow-[0_0_0_2px_var(--varo-agent-primary-soft)]' - : 'border-[var(--varo-agent-border)]', - ], + 'class': 'agent-thread-versions__card', 'data-active': String(entry.active), 'key': entry.version.id, 'role': 'listitem', }, [ - h('span', { class: 'flex min-w-0 items-center justify-between gap-2' }, [ - h('span', { class: 'flex min-w-0 items-center gap-2' }, [ - h('i', { 'class': ['h-2 w-2 flex-none rounded-full', entry.active ? 'bg-[var(--varo-agent-primary)]' : 'bg-[var(--varo-agent-border-strong)]'], 'aria-hidden': 'true' }), - h('strong', { class: 'truncate text-[12px] text-[var(--varo-agent-foreground)]' }, entry.label), - ]), + h('span', { class: 'agent-workspace-card__row-main' }, [ + h('span', { 'class': 'agent-workspace-card__mark', 'aria-hidden': 'true' }, renderWorkspaceIcon('branch')), + h('strong', entry.label), entry.active - ? h('small', { class: 'flex-none text-[10px] font-bold text-[var(--varo-agent-primary)]' }, '当前') + ? h('small', { class: 'agent-workspace-card__chip' }, '当前') : entry.version.pinned - ? h('small', { class: 'flex-none text-[10px] font-bold text-[var(--varo-agent-text)]' }, '已固定') + ? h('small', { class: 'agent-workspace-card__chip' }, '已固定') : null, ]), - h('p', { class: 'm-0 min-h-8 text-[11px] leading-4 text-[var(--varo-agent-muted)]' }, entry.version.summary || '暂无版本摘要'), - h('span', { class: 'grid gap-0.5 text-[10px] text-[var(--varo-agent-muted)]' }, [ - h('small', { class: 'truncate' }, `来源:${entry.parentLabel}`), - entry.version.createdAt ? h('time', { class: 'truncate' }, `创建:${entry.version.createdAt}`) : null, + h('p', { class: 'agent-workspace-card__detail' }, entry.version.summary || '暂无版本摘要'), + h('span', { class: 'agent-thread-versions__meta' }, [ + h('small', `来源:${entry.parentLabel}`), + entry.version.createdAt ? h('time', `创建:${entry.version.createdAt}`) : null, ]), - h('footer', { class: 'flex flex-wrap gap-2 border-t border-[var(--varo-agent-border)] pt-2' }, [ + h('footer', { class: 'agent-thread-versions__actions' }, [ entry.active ? null : h('button', { 'aria-label': `选择${entry.label}`, - 'class': workspacePrimaryTextButton, + 'class': workspaceActionClass('primary'), 'type': 'button', 'onClick': () => emit('select', entry.version), }, '选择'), h('button', { 'aria-label': `从${entry.label}创建分支`, - 'class': workspaceQuietButton, + 'class': workspaceActionClass(), 'type': 'button', 'onClick': () => emit('branch', entry.version), }, '分支'), @@ -678,14 +774,14 @@ export const AgentThreadVersions = defineComponent({ ? null : h('button', { 'aria-label': `固定${entry.label}`, - 'class': workspaceQuietButton, + 'class': workspaceActionClass(), 'type': 'button', 'onClick': () => emit('pin', entry.version), }, '固定'), ]), ]), )) - : h('p', { class: 'm-0 grid min-h-20 place-items-center px-3 text-[12px] text-[var(--varo-agent-muted)]' }, '暂无会话版本'), + : h('p', { class: 'agent-workspace-card__empty' }, '暂无会话版本'), ]) }, }) @@ -949,11 +1045,19 @@ export const AgentRecommendation = defineComponent({ emits: { accept: () => true }, setup(props, { emit, slots }) { const confidence = computed(() => Math.min(100, Math.max(0, props.confidence))) + const confidenceLabel = computed(() => ( + confidence.value >= 80 ? '高置信度' : confidence.value >= 55 ? '中等置信度' : '需要复核' + )) return () => h('section', { class: 'agent-recommendation grid gap-3 rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)] p-4 shadow-[var(--varo-agent-shadow)]' }, [ - h('header', { class: 'flex justify-between text-[11px] font-extrabold tracking-widest text-[var(--varo-agent-primary)]' }, [h('span', 'AGENT 建议'), h('span', `${confidence.value}%`)]), + h('header', { class: 'flex justify-between gap-3 text-[11px] font-extrabold tracking-widest text-[var(--varo-agent-primary)]' }, [ + h('span', 'AGENT 建议'), + h('span', { class: 'tabular-nums' }, `${confidenceLabel.value} · ${confidence.value}%`), + ]), h('strong', { class: 'text-[15px] text-[var(--varo-agent-foreground)]' }, props.title), props.description ? h('p', { class: 'm-0 text-xs leading-5 text-[var(--varo-agent-text)]' }, props.description) : null, - h('span', { class: 'h-1.5 overflow-hidden rounded-full bg-blue-100' }, [h('i', { class: 'block h-full rounded-full bg-blue-600', style: { width: `${confidence.value}%` } })]), + h('span', { 'class': 'h-1.5 overflow-hidden rounded-full bg-[var(--varo-agent-primary-soft)]', 'aria-hidden': 'true' }, [ + h('i', { class: 'block h-full rounded-full bg-[var(--varo-agent-primary)]', style: { width: `${confidence.value}%` } }), + ]), slots.default?.(), h('footer', { class: 'flex justify-end gap-2' }, [slots.secondary?.(), h('button', { class: primaryButton, type: 'button', onClick: () => emit('accept') }, props.acceptText)]), ]) @@ -1105,32 +1209,31 @@ export const AgentArtifact = defineComponent({ props: { artifact: { type: Object as PropType, required: true } }, emits: { open: (_artifact: AgentArtifactItem) => true }, setup(props, { emit }) { + const kindLabel = computed(() => { + if (props.artifact.kind === 'code') { return '代码产物' } + if (props.artifact.kind === 'image') { return '图像产物' } + if (props.artifact.kind === 'file') { return '文件产物' } + return '文档产物' + }) return () => - h('article', { class: 'overflow-hidden rounded-2xl border border-slate-800 bg-slate-950 shadow-lg' }, [ - h('header', { class: 'flex min-h-14 items-center justify-between gap-3 border-b border-slate-800 bg-slate-900 px-4' }, [ - h('span', { class: 'grid min-w-0 gap-0.5' }, [ - h('small', { class: 'text-[10px] font-black uppercase tracking-[0.16em] text-teal-300' }, props.artifact.kind || 'artifact'), - h('strong', { class: 'truncate text-xs text-slate-100' }, props.artifact.title), + h('article', { 'class': 'agent-artifact', 'data-kind': props.artifact.kind || 'document' }, [ + h('header', { class: 'agent-artifact__header' }, [ + h('span', { class: 'agent-artifact__heading' }, [ + h('small', { class: 'agent-artifact__kind' }, kindLabel.value), + h('strong', { class: 'agent-artifact__title' }, props.artifact.title), + props.artifact.language ? h('span', { class: 'agent-artifact__lang' }, props.artifact.language) : null, ]), - h( - 'button', - { - class: 'min-h-8 flex-none rounded-lg border border-slate-700 bg-slate-800 px-3 text-[11px] font-bold text-slate-200 transition-colors hover:border-teal-500 hover:text-white', - type: 'button', - onClick: () => emit('open', props.artifact), - }, - '打开', - ), + h('button', { + class: 'agent-artifact__open', + type: 'button', + onClick: () => emit('open', props.artifact), + }, '打开'), ]), props.artifact.content - ? h( - 'pre', - { class: 'm-0 max-h-72 overflow-auto whitespace-pre-wrap break-words bg-slate-950 px-4 py-3.5 font-mono text-[12px] leading-5 text-slate-200' }, - props.artifact.content, - ) + ? h('pre', { class: 'agent-artifact__body' }, props.artifact.content) : null, props.artifact.previewUrl - ? h('img', { alt: props.artifact.title, class: 'block max-h-80 w-full bg-slate-950 object-contain', src: props.artifact.previewUrl }) + ? h('img', { alt: props.artifact.title, class: 'agent-artifact__preview', src: props.artifact.previewUrl }) : null, ]) }, @@ -1275,4 +1378,13 @@ export const AgentEventRenderer = defineComponent({ export { AgentMarkdown } export * from './advanced' +export { default as AgentRagPipeline } from './AgentRagPipeline.vue' +export type { + AgentRagAnswerPart, + AgentRagPipelineProps, + AgentRagSource, + AgentRagSourceTone, + AgentRagStageId, + AgentRagStep, +} from './rag-pipeline' export type { AgentStreamSnapshot, AgentStreamStatus, AgentToolPart } from '@varo-ui/ai' diff --git a/apps/docs/src/components/agent-ui/rag-icons.ts b/apps/docs/src/components/agent-ui/rag-icons.ts new file mode 100644 index 00000000..d7336904 --- /dev/null +++ b/apps/docs/src/components/agent-ui/rag-icons.ts @@ -0,0 +1,9 @@ +import type { AgentRagStageId } from './rag-pipeline' + +export const agentRagIcons: Record = { + query: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIxLjgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTcgNGgxMGEzIDMgMCAwIDEgMyAzdjdhMyAzIDAgMCAxLTMgM2gtNmwtNCAzdi0zYTMgMyAwIDAgMS0zLTNWN2EzIDMgMCAwIDEgMy0zWiIvPjxwYXRoIGQ9Ik05LjUgOWEyLjUgMi41IDAgMCAxIDUgLjVjMCAxLjUtMi41IDEuNS0yLjUgM00xMiAxNC41aC4wMSIvPjwvc3ZnPg==', + embed: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIxLjgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTQgOGgxNk00IDE2aDE2TTkgNCA3IDIwTTE3IDRsLTIgMTYiLz48L3N2Zz4=', + retrieve: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIxLjgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGNpcmNsZSBjeD0iMTAuNSIgY3k9IjEwLjUiIHI9IjYuNSIvPjxwYXRoIGQ9Im0xNiAxNiA0IDQiLz48L3N2Zz4=', + assemble: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIxLjgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTcgM2g3bDUgNXYxM0g1VjNoMlpNMTQgM3Y2aDVNOCAxM2g4TTggMTdoNiIvPjwvc3ZnPg==', + generate: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIxLjgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTEyIDMgMi4zIDYuN0wyMSAxMmwtNi43IDIuM0wxMiAyMWwtMi4zLTYuN0wzIDEybDYuNy0yLjNMMTIgM1pNMjAgMnY0TTE4IDRoNCIvPjwvc3ZnPg==', +} diff --git a/apps/docs/src/components/agent-ui/rag-pipeline.ts b/apps/docs/src/components/agent-ui/rag-pipeline.ts new file mode 100644 index 00000000..c5be7a0e --- /dev/null +++ b/apps/docs/src/components/agent-ui/rag-pipeline.ts @@ -0,0 +1,81 @@ +import type { ClassValue } from '../../lib/cn' +import type { AgentAdvancedStatus } from './advanced-types' + +export type AgentRagStageId = 'query' | 'embed' | 'retrieve' | 'assemble' | 'generate' +export type AgentRagSourceTone = 'blue' | 'violet' | 'rose' + +export interface AgentRagStep { + id: AgentRagStageId + status: AgentAdvancedStatus + detail?: string + durationMs?: number +} + +export interface AgentRagSource { + id: string + title: string + excerpt?: string + score?: number + tokens?: number + tone?: AgentRagSourceTone +} + +export type AgentRagAnswerPart + = | { id: string, type: 'text', text: string } + | { id: string, type: 'citation', sourceId: string } + +export interface AgentRagPipelineProps { + className?: ClassValue + title?: string + query?: string + steps?: readonly AgentRagStep[] + sources?: readonly AgentRagSource[] + answer?: readonly AgentRagAnswerPart[] + elapsedMs?: number + reducedMotion?: boolean +} + +export const agentRagStages = [ + { id: 'query', label: '理解问题', description: '确认本次查询与来源范围' }, + { id: 'embed', label: '向量编码', description: '将问题转换为检索表示' }, + { id: 'retrieve', label: '检索来源', description: '查找与问题相关的片段' }, + { id: 'assemble', label: '组装上下文', description: '将来源片段组织为上下文' }, + { id: 'generate', label: '引用回答', description: '生成可追溯到来源的回答' }, +] as const + +const statusLabels: Record = { + waiting: '等待', + running: '处理中', + completed: '已完成', + failed: '失败', +} + +export function resolveRagSteps(steps: readonly AgentRagStep[]) { + const byId = new Map() + for (const step of steps) { + if (byId.has(step.id)) { throw new Error(`Duplicate RAG stage: ${step.id}`) } + byId.set(step.id, step) + } + return agentRagStages.map((definition) => { + const step = byId.get(definition.id) + const status = step?.status ?? 'waiting' + return { + ...definition, + status, + detail: step?.detail ?? definition.description, + statusLabel: statusLabels[status], + durationLabel: step?.durationMs === undefined ? '' : `${Math.round(step.durationMs)} ms`, + } + }) +} + +const sourceTones = ['blue', 'violet', 'rose'] as const + +export function getRagSourceTone(source: AgentRagSource): AgentRagSourceTone { + if (source.tone) { return source.tone } + let hash = 0 + for (let index = 0; index < source.id.length; index += 1) { + hash = (Math.imul(hash, 31) + source.id.charCodeAt(index)) | 0 + } + return sourceTones[(hash >>> 0) % 3]! +} diff --git a/apps/docs/src/components/tencent-map.ts b/apps/docs/src/components/tencent-map.ts new file mode 100644 index 00000000..9fb07f77 --- /dev/null +++ b/apps/docs/src/components/tencent-map.ts @@ -0,0 +1,78 @@ +export const tencentMapDemoKey = 'OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77' + +export interface TencentLatLng { + getLat: () => number + getLng: () => number +} + +export interface TencentMap { + destroy: () => void + on: (event: string, listener: (event?: unknown) => void) => void + setCenter: (center: TencentLatLng) => void + setZoom: (zoom: number) => void +} + +export interface TencentMultiMarker { + destroy?: () => void + setGeometries: (geometries: unknown[]) => void +} + +export interface TencentMapApi { + LatLng: new (latitude: number, longitude: number) => TencentLatLng + Map: new (container: HTMLElement, options: { + center: TencentLatLng + pitch?: number + rotation?: number + zoom: number + }) => TencentMap + MultiMarker: new (options: { geometries?: unknown[], map: TencentMap }) => TencentMultiMarker +} + +let pending: Promise | undefined + +function tencentMapApi(): TencentMapApi | undefined { + const api = Reflect.get(globalThis, 'TMap') + if (!api || typeof api !== 'object') { return undefined } + const candidate = api as TencentMapApi + if (typeof candidate.LatLng !== 'function' || typeof candidate.Map !== 'function' || typeof candidate.MultiMarker !== 'function') { + return undefined + } + return candidate +} + +export function tencentRasterTileUrl(x: number, y: number, z: number) { + const size = 2 ** z + const tmsY = size - 1 - y + const server = Math.abs(x + tmsY) % 4 + return `https://rt${server}.map.gtimg.com/tile?z=${z}&x=${x}&y=${tmsY}&type=vector&styleid=3` +} + +export function loadTencentMapApi(key = tencentMapDemoKey): Promise { + const loaded = tencentMapApi() + if (loaded) { return Promise.resolve(loaded) } + if (pending) { return pending } + pending = new Promise((resolve, reject) => { + const callback = `__varoTencentMapReady_${Math.random().toString(36).slice(2)}` + const script = document.createElement('script') + const fail = (error: Error) => { + pending = undefined + Reflect.deleteProperty(globalThis, callback) + script.remove() + reject(error) + } + Reflect.set(globalThis, callback, () => { + Reflect.deleteProperty(globalThis, callback) + const api = tencentMapApi() + if (!api) { + fail(new Error('Tencent Map GL loaded without TMap')) + return + } + resolve(api) + }) + script.async = true + script.src = `https://map.qq.com/api/gljs?v=1.exp&key=${encodeURIComponent(key)}&callback=${callback}` + script.addEventListener('error', () => fail(new Error('Tencent Map GL script failed to load'))) + document.head.append(script) + }) + return pending +} diff --git a/apps/docs/src/composables/useRagPipelineDemo.ts b/apps/docs/src/composables/useRagPipelineDemo.ts new file mode 100644 index 00000000..b5d638ac --- /dev/null +++ b/apps/docs/src/composables/useRagPipelineDemo.ts @@ -0,0 +1,126 @@ +import type { AgentRagAnswerPart, AgentRagSource, AgentRagStageId, AgentRagStep } from '../components/agent-ui/rag-pipeline' +import { onBeforeUnmount, shallowRef } from 'vue' +import { agentRagStages } from '../components/agent-ui/rag-pipeline' + +export interface RagPipelineDemoSnapshot { + answer: AgentRagAnswerPart[] + elapsedMs: number + query: string + sources: AgentRagSource[] + steps: AgentRagStep[] +} + +const demoSources: AgentRagSource[] = [ + { excerpt: '验证链接过期是反复出现的失败原因。', id: 'support', score: 0.94, title: '客服工单', tokens: 126, tone: 'blue' }, + { excerpt: '用户反复重试,却找不到明确的恢复入口。', id: 'replay', score: 0.88, title: '会话回放', tokens: 98, tone: 'violet' }, +] + +const completedDurations: Record = { + query: 380, + embed: 650, + retrieve: 540, + assemble: 580, + generate: 720, +} + +function completedSteps(): AgentRagStep[] { + return agentRagStages.map(stage => ({ + id: stage.id, + status: 'completed', + durationMs: completedDurations[stage.id], + })) +} + +function waitingSteps(): AgentRagStep[] { + return agentRagStages.map(stage => ({ id: stage.id, status: 'waiting' })) +} + +function completedAnswer(): AgentRagAnswerPart[] { + return [ + { id: 'text-a', type: 'text', text: '验证链接过期后应提供新链接。' }, + { id: 'citation-a', type: 'citation', sourceId: 'support' }, + { id: 'text-b', type: 'text', text: ' 会话回放也显示缺少恢复入口。' }, + { id: 'citation-b', type: 'citation', sourceId: 'replay' }, + ] +} + +export function useRagPipelineDemo(locale: 'en' | 'zh' = 'zh') { + const query = locale === 'en' + ? 'How can we make signup failures recoverable?' + : '如何让注册失败的用户顺利恢复流程?' + const snapshot = shallowRef({ + answer: completedAnswer(), + elapsedMs: 2870, + query, + sources: demoSources, + steps: completedSteps(), + }) + let runId = 0 + let cancelDelay: (() => void) | undefined + let startedAt = 0 + + function invalidate() { + runId += 1 + cancelDelay?.() + cancelDelay = undefined + } + + function delay(milliseconds: number, currentRun: number) { + return new Promise((resolve) => { + const timer = setTimeout(() => { + cancelDelay = undefined + resolve(currentRun === runId) + }, milliseconds) + cancelDelay = () => { + clearTimeout(timer) + resolve(false) + } + }) + } + + function setStep(id: AgentRagStageId, status: AgentRagStep['status'], durationMs?: number) { + snapshot.value = { + ...snapshot.value, + elapsedMs: Date.now() - startedAt, + steps: snapshot.value.steps.map(item => item.id === id ? { id, status, durationMs } : item), + } + } + + function cancel() { + invalidate() + snapshot.value = { + ...snapshot.value, + steps: snapshot.value.steps.map(item => item.status === 'running' + ? { ...item, status: 'waiting' } + : item), + } + } + + async function run() { + invalidate() + const currentRun = runId + startedAt = Date.now() + snapshot.value = { + answer: [], + elapsedMs: 0, + query, + sources: [], + steps: waitingSteps(), + } + + for (const stage of agentRagStages) { + setStep(stage.id, 'running') + if (stage.id === 'retrieve') { + snapshot.value = { ...snapshot.value, sources: demoSources } + } + if (stage.id === 'generate') { + snapshot.value = { ...snapshot.value, answer: completedAnswer() } + } + if (!await delay(completedDurations[stage.id], currentRun)) { return } + setStep(stage.id, 'completed', completedDurations[stage.id]) + } + } + + onBeforeUnmount(invalidate) + return { snapshot, run, cancel } +} diff --git a/apps/playground-h5/src/components/agent-ui/AgentRagPipeline.vue b/apps/playground-h5/src/components/agent-ui/AgentRagPipeline.vue index 6bd149f3..83179b2d 100644 --- a/apps/playground-h5/src/components/agent-ui/AgentRagPipeline.vue +++ b/apps/playground-h5/src/components/agent-ui/AgentRagPipeline.vue @@ -500,7 +500,7 @@ onBeforeUnmount(clearAllPings) {{ entry.number }} - + {{ entry.source.title }} {{ entry.source.excerpt }} @@ -708,11 +708,8 @@ onBeforeUnmount(clearAllPings) height: 16px; background: currentcolor; mask-repeat: no-repeat; - mask-repeat: no-repeat; - mask-position: center; mask-position: center; mask-size: contain; - mask-size: contain; } .agent-rag__panel { @@ -816,11 +813,30 @@ onBeforeUnmount(clearAllPings) .agent-rag__source.is-highlighted, .agent-rag__source.is-pinging { + color: #172033; background: var(--agent-rag-tone-soft); border-color: var(--agent-rag-tone); box-shadow: 0 0 0 2px color-mix(in srgb, var(--agent-rag-tone) 18%, transparent); } +.agent-rag__source-title { + color: var(--varo-agent-foreground); +} + +.agent-rag__source-excerpt { + color: var(--varo-agent-muted); +} + +.agent-rag__source.is-highlighted strong.agent-rag__source-title, +.agent-rag__source.is-pinging strong.agent-rag__source-title { + color: #172033; +} + +.agent-rag__source.is-highlighted .agent-rag__source-excerpt, +.agent-rag__source.is-pinging .agent-rag__source-excerpt { + color: #475569; +} + .agent-rag__source:focus-visible, .agent-rag__citation:focus-visible { outline: 2px solid var(--agent-rag-tone); diff --git a/apps/playground-h5/src/components/agent-ui/advanced.ts b/apps/playground-h5/src/components/agent-ui/advanced.ts index ece5f632..fed091f8 100644 --- a/apps/playground-h5/src/components/agent-ui/advanced.ts +++ b/apps/playground-h5/src/components/agent-ui/advanced.ts @@ -78,6 +78,75 @@ function renderStatus(status: AgentAdvancedStatus) { ]) } +function activityKindLabel(kind: AgentActivityItem['kind']) { + if (kind === 'reasoning') { return '推理' } + if (kind === 'search') { return '检索' } + if (kind === 'tool') { return '工具' } + return '轨迹' +} + +function renderActivityKindIcon(kind: AgentActivityItem['kind']) { + const children = kind === 'search' + ? [h('circle', { cx: '11', cy: '11', r: '6' }), h('path', { d: 'm16 16 4 4' })] + : [h('path', { + d: kind === 'reasoning' + ? 'M12 3.5 13.4 8 18 9.2 13.4 10.5 12 15l-1.4-4.5L6 9.2 10.6 8 12 3.5zM18 13.5l.8 2.4 2.4.8-2.4.8-.8 2.4-.8-2.4-2.4-.8 2.4-.8.8-2.4z' + : kind === 'tool' + ? 'M8 7h8M8 12h8M8 17h5' + : 'M7 7h10M7 12h10M7 17h6', + })] + return h('svg', { + 'aria-hidden': 'true', + 'fill': 'none', + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': 1.8, + 'viewBox': '0 0 24 24', + }, children) +} + +function citationMark(item: AgentCitationItem) { + const source = item.domain || item.title + return source.trim().charAt(0).toUpperCase() || '#' +} + +function renderPlusIcon() { + return h('svg', { + 'fill': 'none', + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-width': 2, + 'viewBox': '0 0 24 24', + 'width': 15, + 'height': 15, + 'aria-hidden': 'true', + }, [h('path', { d: 'M12 5v14M5 12h14' })]) +} +function renderChatIcon() { + return h('svg', { + 'aria-hidden': 'true', + 'fill': 'none', + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': 1.8, + 'viewBox': '0 0 24 24', + }, [h('path', { d: 'M5 6h14a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H9l-4 3v-3H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z' })]) +} + +function renderSearchIcon() { + return h('svg', { + 'aria-hidden': 'true', + 'fill': 'none', + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': 1.8, + 'viewBox': '0 0 24 24', + }, [h('circle', { cx: '11', cy: '11', r: '6' }), h('path', { d: 'm16 16 4 4' })]) +} + export const AgentRadioGroup = defineComponent({ name: 'AgentRadioGroup', props: { @@ -475,7 +544,8 @@ export const AgentToolResult = defineComponent({ emits: { 'retry': () => true, 'update:open': (_value: boolean) => true }, setup(props, { emit, slots }) { const open = shallowRef(props.defaultOpen || props.status === 'running' || props.status === 'failed') - return () => h('section', { 'class': 'agent-tool-result', 'data-status': props.status }, [ + const meta = computed(() => [props.duration, props.summary].filter(Boolean).join(' · ')) + return () => h('section', { 'class': 'agent-tool-result', 'data-open': String(open.value), 'data-status': props.status }, [ h('button', { 'class': 'agent-tool-result__header', 'type': 'button', @@ -485,8 +555,14 @@ export const AgentToolResult = defineComponent({ emit('update:open', open.value) }, }, [ - h('span', [renderStatus(props.status), h('strong', props.name)]), - h('small', props.duration || props.summary), + h('span', { class: 'agent-tool-result__command' }, [ + h('span', { 'class': 'agent-tool-result__prompt', 'aria-hidden': 'true' }, '$'), + h('strong', props.name), + ]), + h('span', { class: 'agent-tool-result__meta' }, [ + renderStatus(props.status), + meta.value ? h('small', meta.value) : null, + ]), ]), open.value ? h('div', { class: 'agent-tool-result__body' }, slots.default?.() ?? [h('pre', props.output)]) @@ -658,12 +734,22 @@ export const AgentCitations = defineComponent({ open.value = !open.value emit('update:open', open.value) }, - }, [h('strong', props.title), h('span', `${props.items.length}`)]), + }, [ + h('span', { class: 'agent-citations__heading' }, [ + h('strong', props.title), + h('small', open.value ? '点击来源打开原文' : '展开查看引用来源'), + ]), + h('span', { class: 'agent-citations__count' }, `${props.items.length}`), + ]), open.value - ? h('ol', props.items.map((item, index) => h('li', { key: item.id }, [ - h('button', { type: 'button', onClick: () => emit('open', item) }, [ - h('span', String(index + 1)), - h('span', [h('strong', item.title), h('small', item.domain || item.description || item.url)]), + ? h('ol', { class: 'agent-citations__list' }, props.items.map(item => h('li', { key: item.id }, [ + h('button', { class: 'agent-citations__item', type: 'button', onClick: () => emit('open', item) }, [ + h('span', { 'class': 'agent-citations__mark', 'aria-hidden': 'true' }, citationMark(item)), + h('span', { class: 'agent-citations__copy' }, [ + h('strong', item.title), + h('small', item.domain || item.url || ''), + item.description ? h('p', item.description) : null, + ]), ]), ]))) : null, @@ -678,12 +764,39 @@ export const AgentActivity = defineComponent({ title: { type: String, default: 'Agent activity' }, }, setup(props) { + const completed = computed(() => props.items.filter(item => item.status === 'completed').length) + const current = computed(() => props.items.find(item => item.status === 'running' || item.status === 'failed')) return () => h('section', { 'class': 'agent-activity', 'aria-live': 'polite' }, [ - h('header', [h('strong', props.title), h('span', `${props.items.filter(item => item.status === 'completed').length}/${props.items.length}`)]), - h('ol', props.items.map(item => h('li', { 'data-kind': item.kind, 'data-status': item.status, 'key': item.id }, [ - h('i', { 'aria-hidden': 'true' }), - h('div', [h('span', [h('strong', item.title), item.duration ? h('small', item.duration) : null]), item.detail ? h('p', item.detail) : null]), - renderStatus(item.status), + h('header', { class: 'agent-activity__header' }, [ + h('span', { class: 'agent-activity__heading' }, [ + h('strong', props.title), + current.value + ? h('small', current.value.status === 'failed' ? `阻塞于 ${current.value.title}` : `正在执行 ${current.value.title}`) + : h('small', completed.value === props.items.length && props.items.length ? '全部完成' : '等待开始'), + ]), + h('span', { class: 'agent-activity__count' }, `${completed.value}/${props.items.length}`), + ]), + h('ol', { class: 'agent-activity__body' }, props.items.map(item => h('li', { + 'class': 'agent-activity__item', + 'data-kind': item.kind, + 'data-status': item.status, + 'aria-current': item.status === 'running' ? 'step' : undefined, + 'key': item.id, + }, [ + h('span', { 'class': 'agent-activity__kind', 'aria-hidden': 'true' }, [ + renderActivityKindIcon(item.kind), + ]), + h('span', { class: 'agent-activity__copy' }, [ + h('span', { class: 'agent-activity__row' }, [ + h('strong', item.title), + renderStatus(item.status), + ]), + h('span', { class: 'agent-activity__meta' }, [ + h('small', { class: 'agent-activity__kind-label' }, activityKindLabel(item.kind)), + item.duration ? h('small', item.duration) : null, + ]), + item.detail ? h('p', item.detail) : null, + ]), ]))), ]) }, @@ -705,13 +818,26 @@ export const AgentSidebar = defineComponent({ }, setup(props, { emit, slots }) { return () => h('aside', { 'class': 'agent-sidebar', 'data-collapsed': String(props.collapsed) }, [ - h('header', [ - h('strong', props.collapsed ? 'AI' : props.title), - h('button', { 'type': 'button', 'aria-label': 'Toggle sidebar', 'onClick': () => emit('update:collapsed', !props.collapsed) }, props.collapsed ? '›' : '‹'), + h('header', { class: 'agent-sidebar__header' }, [ + h('span', { class: 'agent-sidebar__heading' }, [ + h('span', { 'class': 'agent-sidebar__brand', 'aria-hidden': 'true' }, [renderChatIcon()]), + !props.collapsed ? h('strong', props.title) : null, + ]), + h('button', { + 'class': 'agent-sidebar__toggle', + 'type': 'button', + 'aria-label': 'Toggle sidebar', + 'onClick': () => emit('update:collapsed', !props.collapsed), + }, props.collapsed ? '›' : '‹'), ]), - !props.collapsed ? h('button', { class: 'agent-sidebar__create', type: 'button', onClick: () => emit('create') }, '+ New chat') : null, - h('nav', props.groups.map(group => h('section', { key: group.id }, [ - !props.collapsed ? h('small', group.label) : null, + !props.collapsed + ? h('button', { class: 'agent-sidebar__create', type: 'button', onClick: () => emit('create') }, [ + renderPlusIcon(), + 'New chat', + ]) + : null, + h('nav', { class: 'agent-sidebar__nav', role: 'navigation' }, props.groups.map(group => h('section', { class: 'agent-sidebar__group', key: group.id }, [ + !props.collapsed ? h('small', { class: 'agent-sidebar__group-label' }, group.label) : null, ...group.items.map(item => h('button', { 'class': 'agent-sidebar__item', 'data-active': String(item.id === props.activeId), @@ -723,9 +849,14 @@ export const AgentSidebar = defineComponent({ emit('select', item) }, }, [ - h('i', { 'aria-hidden': 'true' }, item.label.charAt(0).toUpperCase()), - !props.collapsed ? h('span', [h('strong', item.label), item.meta ? h('small', item.meta) : null]) : null, - !props.collapsed && item.badge !== undefined ? h('b', String(item.badge)) : null, + h('span', { 'class': 'agent-sidebar__mark', 'aria-hidden': 'true' }, [renderChatIcon()]), + !props.collapsed + ? h('span', { class: 'agent-sidebar__copy' }, [ + h('strong', item.label), + item.meta ? h('small', item.meta) : null, + ]) + : null, + !props.collapsed && item.badge !== undefined ? h('b', { class: 'agent-sidebar__badge' }, String(item.badge)) : null, ])), ]))), slots.footer?.(), @@ -742,11 +873,28 @@ export const AgentContextCard = defineComponent({ emits: { open: (_chunk: AgentContextChunk) => true }, setup(props, { emit }) { return () => h('section', { class: 'agent-context-card' }, [ - h('header', [h('strong', props.title), h('span', `${props.chunks.length} chunks`)]), - h('div', props.chunks.map(chunk => h('article', { key: chunk.id }, [ - h('header', [h('strong', chunk.label || chunk.source || 'Context'), chunk.content.length ? h('small', `${chunk.content.length} chars`) : null]), - h('p', chunk.content), - h('footer', [h('span', chunk.sourceType || 'Source'), h('button', { type: 'button', onClick: () => emit('open', chunk) }, chunk.source || 'Open')]), + h('header', { class: 'agent-context-card__header' }, [ + h('span', { class: 'agent-context-card__heading' }, [ + h('strong', props.title), + h('small', props.chunks.length ? '来自检索结果的上下文片段' : '暂无检索上下文'), + ]), + h('span', { class: 'agent-context-card__count' }, `${props.chunks.length}`), + ]), + h('ol', { class: 'agent-context-card__list' }, props.chunks.map((chunk, index) => h('li', { key: chunk.id }, [ + h('article', { class: 'agent-context-card__chunk' }, [ + h('header', { class: 'agent-context-card__chunk-head' }, [ + h('span', { 'class': 'agent-context-card__index', 'aria-hidden': 'true' }, String(index + 1).padStart(2, '0')), + h('span', { class: 'agent-context-card__chunk-copy' }, [ + h('strong', chunk.label || chunk.source || 'Context'), + chunk.sourceType ? h('small', { class: 'agent-context-card__type' }, chunk.sourceType) : null, + ]), + ]), + h('p', { class: 'agent-context-card__quote' }, chunk.content), + h('footer', { class: 'agent-context-card__chunk-foot' }, [ + h('small', chunk.source || 'Source'), + h('button', { class: 'agent-context-card__open', type: 'button', onClick: () => emit('open', chunk) }, '打开'), + ]), + ]), ]))), ]) }, @@ -767,11 +915,27 @@ export const AgentInsightCard = defineComponent({ emit('update:current', (props.current + delta + props.insights.length) % props.insights.length) } return () => h('article', { 'class': 'agent-insight-card', 'data-tone': active.value?.tone || 'default' }, [ - h('header', [h('strong', props.title), h('span', `${props.current + 1}/${props.insights.length}`)]), - active.value ? h('div', [active.value.label ? h('small', active.value.label) : null, h('h3', active.value.description), active.value.value ? h('b', active.value.value) : null]) : h('p', 'No insights'), - h('footer', [ - h('span', [h('button', { type: 'button', onClick: () => move(-1) }, '‹'), h('button', { type: 'button', onClick: () => move(1) }, '›')]), - active.value?.action ? h('button', { type: 'button', onClick: () => emit('action', active.value!) }, active.value.action) : null, + h('header', { class: 'agent-insight-card__header' }, [ + h('span', { class: 'agent-insight-card__heading' }, [ + h('strong', props.title), + h('small', active.value?.label || '运行指标'), + ]), + h('span', { class: 'agent-insight-card__count' }, `${props.insights.length ? props.current + 1 : 0}/${props.insights.length}`), + ]), + active.value + ? h('div', { class: 'agent-insight-card__body' }, [ + active.value.value ? h('b', { class: 'agent-insight-card__value' }, active.value.value) : null, + h('p', { class: 'agent-insight-card__copy' }, active.value.description), + ]) + : h('p', { class: 'agent-insight-card__empty' }, '暂无洞察'), + h('footer', { class: 'agent-insight-card__footer' }, [ + h('span', { class: 'agent-insight-card__nav' }, [ + h('button', { 'class': 'agent-insight-card__nav-btn', 'type': 'button', 'aria-label': 'Previous insight', 'onClick': () => move(-1) }, '‹'), + h('button', { 'class': 'agent-insight-card__nav-btn', 'type': 'button', 'aria-label': 'Next insight', 'onClick': () => move(1) }, '›'), + ]), + active.value?.action + ? h('button', { class: 'agent-insight-card__action', type: 'button', onClick: () => emit('action', active.value!) }, active.value.action) + : null, ]), ]) }, @@ -786,8 +950,9 @@ export const AgentSelectionActions = defineComponent({ emits: { select: (_payload: { action: AgentSelectionAction, text: string }) => true }, setup(props, { emit }) { return () => h('section', { class: 'agent-selection-actions' }, [ - h('blockquote', props.text), - h('div', props.actions.map(action => h('button', { + h('p', { class: 'agent-selection-actions__quote' }, props.text), + h('div', { class: 'agent-selection-actions__toolbar' }, props.actions.map(action => h('button', { + class: 'agent-selection-actions__action', key: action.id, type: 'button', onClick: () => emit('select', { action, text: props.text }), @@ -907,14 +1072,38 @@ export const AgentCommandSearch = defineComponent({ const query = props.modelValue.trim().toLowerCase() return query ? props.items.filter(item => `${item.label} ${item.description ?? ''} ${item.group ?? ''}`.toLowerCase().includes(query)) : props.items }) + const groupedItems = computed(() => { + const groups = new Map() + for (const item of visibleItems.value) { + const key = item.group || 'Commands' + const list = groups.get(key) + if (list) { list.push(item) } + else { groups.set(key, [item]) } + } + return [...groups.entries()].map(([label, items]) => ({ label, items })) + }) return () => h('section', { class: 'agent-command-search' }, [ - h('label', [h('span', '⌕'), h('input', { value: props.modelValue, placeholder: props.placeholder, onInput: (event: Event) => emit('update:modelValue', eventValue(event)) })]), - visibleItems.value.length - ? h('div', visibleItems.value.map(item => h('button', { key: item.id, type: 'button', onClick: () => emit('select', item) }, [ - h('span', [h('strong', item.label), item.description ? h('small', item.description) : null]), - item.shortcut ? h('kbd', item.shortcut) : null, - ]))) - : h('p', props.emptyText), + h('label', { class: 'agent-command-search__field' }, [ + h('span', { 'class': 'agent-command-search__icon', 'aria-hidden': 'true' }, [renderSearchIcon()]), + h('input', { value: props.modelValue, placeholder: props.placeholder, onInput: (event: Event) => emit('update:modelValue', eventValue(event)) }), + ]), + groupedItems.value.length + ? h('div', { class: 'agent-command-search__list' }, groupedItems.value.flatMap(group => [ + h('small', { class: 'agent-command-search__group', key: `group-${group.label}` }, group.label), + ...group.items.map(item => h('button', { + class: 'agent-command-search__item', + key: item.id, + type: 'button', + onClick: () => emit('select', item), + }, [ + h('span', { class: 'agent-command-search__copy' }, [ + h('strong', item.label), + item.description ? h('small', item.description) : null, + ]), + item.shortcut ? h('kbd', item.shortcut) : null, + ])), + ])) + : h('p', { class: 'agent-command-search__empty' }, props.emptyText), ]) }, }) @@ -927,16 +1116,46 @@ export const AgentFlowchart = defineComponent({ }, emits: { add: (_afterId?: string) => true, select: (_node: AgentFlowNode) => true }, setup(props, { emit }) { + const typeLabel = (type: AgentFlowNode['type']) => { + if (type === 'trigger') { return '触发' } + if (type === 'condition') { return '条件' } + if (type === 'action') { return '动作' } + return '结果' + } return () => h('section', { class: 'agent-flowchart' }, [ - h('header', [h('strong', props.title), h('button', { type: 'button', onClick: () => emit('add', undefined) }, '+ Step')]), - h('div', props.nodes.flatMap((node, index): VNodeChild[] => [ - h('button', { 'class': 'agent-flowchart__node', 'data-type': node.type, 'key': node.id, 'type': 'button', 'onClick': () => emit('select', node) }, [ - h('small', node.type), + h('header', { class: 'agent-flowchart__header' }, [ + h('span', { class: 'agent-flowchart__heading' }, [ + h('strong', props.title), + h('small', `${props.nodes.length} 个步骤`), + ]), + h('button', { class: 'agent-flowchart__add', type: 'button', onClick: () => emit('add', undefined) }, [renderPlusIcon(), 'Step']), + ]), + h('div', { class: 'agent-flowchart__canvas' }, props.nodes.flatMap((node, index): VNodeChild[] => [ + h('button', { + 'class': 'agent-flowchart__node', + 'data-type': node.type, + 'data-status': node.status || 'waiting', + 'key': node.id, + 'type': 'button', + 'onClick': () => emit('select', node), + }, [ + h('span', { class: 'agent-flowchart__node-head' }, [ + h('span', { 'class': 'agent-flowchart__index', 'aria-hidden': 'true' }, String(index + 1).padStart(2, '0')), + h('small', { class: 'agent-flowchart__type' }, typeLabel(node.type)), + node.status ? renderStatus(node.status) : null, + ]), h('strong', node.label), - node.detail ? h('span', node.detail) : null, - node.status ? renderStatus(node.status) : null, + node.detail ? h('span', { class: 'agent-flowchart__detail' }, node.detail) : null, ]), - index < props.nodes.length - 1 ? h('button', { 'class': 'agent-flowchart__connector', 'key': `${node.id}-connector`, 'type': 'button', 'aria-label': `Add after ${node.label}`, 'onClick': () => emit('add', node.id) }, '+') : null, + index < props.nodes.length - 1 + ? h('button', { + 'class': 'agent-flowchart__connector', + 'key': `${node.id}-connector`, + 'type': 'button', + 'aria-label': `Add after ${node.label}`, + 'onClick': () => emit('add', node.id), + }, renderPlusIcon()) + : null, ])), ]) }, @@ -998,9 +1217,17 @@ export const AgentFineTune = defineComponent({ } return () => h('section', { class: 'agent-fine-tune' }, [ - h('header', [h('strong', props.title), h('span', 'Adjust')]), - h('div', props.controls.map(renderControl)), - h('footer', [h('button', { type: 'button', onClick: () => emit('apply', props.controls) }, 'Apply changes')]), + h('header', { class: 'agent-fine-tune__header' }, [ + h('span', { class: 'agent-fine-tune__heading' }, [ + h('strong', props.title), + h('small', '调整生成参数后立即生效'), + ]), + h('span', { class: 'agent-fine-tune__count' }, `${props.controls.length} 项`), + ]), + h('div', { class: 'agent-fine-tune__grid' }, props.controls.map(renderControl)), + h('footer', { class: 'agent-fine-tune__footer' }, [ + h('button', { class: 'agent-fine-tune__apply', type: 'button', onClick: () => emit('apply', props.controls) }, 'Apply changes'), + ]), ]) }, }) diff --git a/apps/playground-h5/src/components/agent-ui/agent-advanced.css b/apps/playground-h5/src/components/agent-ui/agent-advanced.css index 14ac3dd3..f70a46f6 100644 --- a/apps/playground-h5/src/components/agent-ui/agent-advanced.css +++ b/apps/playground-h5/src/components/agent-ui/agent-advanced.css @@ -97,10 +97,88 @@ .agent-code-block__cursor { position: absolute; right: 12px; bottom: 12px; width: 6px; height: 14px; background: #2dd4bf; animation: varo-agent-code-cursor .75s steps(1) infinite; } @keyframes varo-agent-code-cursor { 50% { opacity: 0; } } -.agent-tool-result, .agent-citations, .agent-activity, .agent-context-card, .agent-insight-card, .agent-tool-approval, .agent-fine-tune { overflow: hidden; background: #fff; border: 1px solid #e2e8f0; border-radius: 16px; } -.agent-tool-result__header, .agent-citations__trigger { display: flex; gap: 12px; align-items: center; justify-content: space-between; width: 100%; min-height: 48px; padding: 0 13px; text-align: left; background: #fff; border: 0; } -.agent-tool-result__header > span { display: flex; gap: 9px; align-items: center; min-width: 0; } -.agent-tool-result__header strong { overflow: hidden; text-overflow: ellipsis; font-size: 12px; color: #1e293b; white-space: nowrap; } +.agent-citations, .agent-activity, .agent-context-card, .agent-insight-card, .agent-tool-approval, .agent-fine-tune { overflow: hidden; background: #fff; border: 1px solid #e2e8f0; border-radius: 16px; } + + +.agent-tool-result { + overflow: hidden; + color: #dbeafe; + background: #0f172a; + border: 1px solid #1e293b; + border-radius: 16px; +} + +.agent-tool-result__header { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; + width: 100%; + min-height: 42px; + padding: 0 12px; + color: inherit; + text-align: left; + background: #111c30; + border: 0; + border-bottom: 1px solid #26334a; +} + +.agent-tool-result[data-open='false'] .agent-tool-result__header { + border-bottom-color: transparent; +} + +.agent-tool-result__command { + display: flex; + gap: 8px; + align-items: baseline; + min-width: 0; +} + +.agent-tool-result__prompt { + flex: none; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + font-weight: 700; + color: #2dd4bf; +} + +.agent-tool-result__command strong { + overflow: hidden; + text-overflow: ellipsis; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + font-weight: 650; + color: #f8fafc; + white-space: nowrap; +} + +.agent-tool-result__meta { + display: flex; + flex: none; + gap: 8px; + align-items: center; +} + +.agent-tool-result__meta .agent-advanced__status { + padding: 3px 8px; + color: #cbd5e1; + background: rgb(148 163 184 / 12%); + border: 1px solid rgb(148 163 184 / 18%); + border-radius: 999px; +} + +.agent-tool-result__meta .agent-advanced__status[data-status='completed'], +.agent-tool-result__meta .agent-advanced__status[data-status='running'] { + color: #5eead4; + background: rgb(45 212 191 / 12%); + border-color: rgb(45 212 191 / 24%); +} + +.agent-tool-result__meta .agent-advanced__status[data-status='failed'] { + color: #fca5a5; + background: rgb(248 113 113 / 12%); + border-color: rgb(248 113 113 / 24%); +} .agent-file-diff { --agent-diff-surface: var(--varo-surface, #fff); @@ -333,9 +411,9 @@ .agent-file-diff__action:hover { color: var(--agent-diff-text); border-color: #99a7b6; } .agent-file-diff__action:last-child { color: #fff; background: #0f766e; border-color: #0f766e; box-shadow: 0 5px 12px rgb(15 118 110 / 20%); } .agent-file-diff__action:disabled { cursor: not-allowed; opacity: .5; } -.agent-tool-approval > footer, .agent-insight-card > footer, .agent-fine-tune > footer, .agent-image-generation > footer { display: flex; gap: 8px; align-items: center; justify-content: flex-end; min-height: 48px; padding: 0 12px; border-top: 1px solid #f1f5f9; } -.agent-tool-approval button, .agent-insight-card button, .agent-fine-tune button, .agent-image-generation button { min-height: 34px; padding: 0 11px; font-size: 10px; font-weight: 750; color: #475569; background: #fff; border: 1px solid #e2e8f0; border-radius: 10px; } -.agent-tool-approval > footer button:last-child, .agent-fine-tune > footer button { color: #fff; background: #0f766e; border-color: #0f766e; } +.agent-tool-approval > footer, .agent-image-generation > footer { display: flex; gap: 8px; align-items: center; justify-content: flex-end; min-height: 48px; padding: 0 12px; border-top: 1px solid #f1f5f9; } +.agent-tool-approval button, .agent-image-generation button { min-height: 34px; padding: 0 11px; font-size: 10px; font-weight: 750; color: #475569; background: #fff; border: 1px solid #e2e8f0; border-radius: 10px; } +.agent-tool-approval > footer button:last-child { color: #fff; background: #0f766e; border-color: #0f766e; } @media (max-width: 600px) { .agent-file-diff__header { gap: 7px; padding-inline: 9px; } @@ -347,10 +425,10 @@ .agent-file-diff__hunk-count { display: none; } .agent-file-diff__footer { padding-inline: 9px; } } -.agent-tool-result__header small { font-size: 10px; color: #94a3b8; } -.agent-tool-result__body { padding: 12px; color: #cbd5e1; background: #0f172a; border-top: 1px solid #f1f5f9; } -.agent-tool-result__body pre { margin: 0; overflow-x: auto; font-family: ui-monospace, monospace; font-size: 10px; white-space: pre-wrap; } -.agent-tool-result__retry { min-height: 32px; padding: 0 10px; margin: 0 12px 12px; font-size: 10px; color: #dc2626; background: #fff; border: 1px solid #fecaca; border-radius: 9px; } +.agent-tool-result__header small { font-size: 10px; font-variant-numeric: tabular-nums; color: #94a3b8; } +.agent-tool-result__body { padding: 12px; color: #cbd5e1; background: #0f172a; } +.agent-tool-result__body pre { margin: 0; overflow-x: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; line-height: 1.7; white-space: pre-wrap; } +.agent-tool-result__retry { min-height: 32px; padding: 0 10px; margin: 0 12px 12px; font-size: 10px; font-weight: 750; color: #fecaca; background: rgb(248 113 113 / 10%); border: 1px solid rgb(248 113 113 / 28%); border-radius: 9px; } .agent-image-generation { margin: 0; } .agent-image-generation__canvas { @@ -407,65 +485,508 @@ .agent-tool-approval dt { color: #92400e; } .agent-tool-approval dd { margin: 0; font-weight: 650; color: #451a03; text-align: right; } .agent-tool-approval > label { display: flex; gap: 7px; align-items: center; padding: 0 14px 12px; font-size: 10px; color: #92400e; } -.agent-citations__trigger strong { font-size: 12px; } -.agent-citations__trigger span { display: grid; place-items: center; width: 24px; height: 24px; font-size: 10px; color: #64748b; background: #f1f5f9; border-radius: 999px; } -.agent-citations ol { display: grid; gap: 7px; padding: 0 12px 12px; margin: 0; list-style: none; } -.agent-citations li button { display: flex; gap: 9px; align-items: center; width: 100%; min-height: 42px; padding: 6px 9px; text-align: left; background: #f8fafc; border: 1px solid #f1f5f9; border-radius: 11px; } -.agent-citations li button > span:first-child { display: grid; flex: none; place-items: center; width: 22px; height: 22px; font-size: 9px; color: #64748b; background: #fff; border-radius: 999px; } -.agent-citations li button > span:last-child { display: grid; min-width: 0; } -.agent-citations li strong, .agent-citations li small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.agent-citations li strong { font-size: 10px; color: #334155; } -.agent-citations li small { font-size: 9px; color: #94a3b8; } -.agent-activity > header, .agent-context-card > header, .agent-insight-card > header, .agent-fine-tune > header { display: flex; align-items: center; justify-content: space-between; min-height: 46px; padding: 0 13px; border-bottom: 1px solid #f1f5f9; } -.agent-activity > header strong, .agent-context-card > header strong, .agent-insight-card > header strong, .agent-fine-tune > header strong { font-size: 12px; } -.agent-activity > header span, .agent-context-card > header span, .agent-insight-card > header span, .agent-fine-tune > header span { font-size: 10px; color: #64748b; } -.agent-activity ol { display: grid; gap: 0; padding: 10px 13px 13px; margin: 0; list-style: none; } -.agent-activity li { position: relative; display: grid; grid-template-columns: 12px minmax(0, 1fr) auto; gap: 9px; padding: 8px 0; } -.agent-activity li > i { width: 9px; height: 9px; margin-top: 4px; background: #94a3b8; border: 2px solid #fff; border-radius: 999px; box-shadow: 0 0 0 1px #cbd5e1; } -.agent-activity li[data-status='running'] > i { background: #0f766e; } -.agent-activity li[data-status='completed'] > i { background: #22c55e; } -.agent-activity li > div { display: grid; min-width: 0; } -.agent-activity li > div > span { display: flex; gap: 8px; justify-content: space-between; } -.agent-activity li strong { font-size: 11px; color: #334155; } -.agent-activity li small, .agent-activity li p { font-size: 9px; color: #94a3b8; } -.agent-activity li p { margin: 2px 0 0; } + +.agent-citations__trigger { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + width: 100%; + min-height: 52px; + padding: 12px 14px 11px; + text-align: left; + background: #fff; + border: 0; +} + +.agent-citations__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-citations__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-citations__heading small, +.agent-citations__count { + font-size: 11px; + line-height: 16px; + color: #64748b; +} + +.agent-citations__count { + display: grid; + flex: none; + place-items: center; + min-width: 24px; + height: 24px; + padding: 0 7px; + font-variant-numeric: tabular-nums; + background: #f1f5f9; + border-radius: 999px; +} + +.agent-citations__list { + display: grid; + gap: 8px; + padding: 0 12px 12px; + margin: 0; + list-style: none; +} + +.agent-citations__list > li { + display: block; + list-style: none; +} + +.agent-citations__list > li::marker { + content: none; +} + +.agent-citations__item { + display: grid; + grid-template-columns: 28px minmax(0, 1fr); + gap: 10px; + align-items: start; + width: 100%; + min-height: 58px; + padding: 9px 10px; + text-align: left; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 12px; +} + +.agent-citations__mark { + display: grid; + flex: none; + place-items: center; + width: 28px; + height: 28px; + font-size: 12px; + font-weight: 800; + color: #0f766e; + background: #ccfbf1; + border-radius: 9px; +} + +.agent-citations__copy { + display: grid; + gap: 3px; + min-width: 0; +} + +.agent-citations__copy strong { + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + font-weight: 700; + line-height: 18px; + color: #1e293b; + white-space: nowrap; +} + +.agent-citations__copy small { + overflow: hidden; + text-overflow: ellipsis; + font-size: 11px; + line-height: 16px; + color: #64748b; + white-space: nowrap; +} + +.agent-citations__copy p { + margin: 0; + font-size: 11px; + line-height: 16px; + color: #64748b; +} +.agent-fine-tune__header { display: flex; gap: 12px; align-items: flex-start; justify-content: space-between; min-height: 52px; padding: 12px 14px 11px; border-bottom: 1px solid #e2e8f0; } +.agent-fine-tune__heading { display: grid; gap: 2px; min-width: 0; } +.agent-fine-tune__heading strong { font-size: 13px; font-weight: 760; line-height: 18px; } + +.agent-fine-tune__heading small, +.agent-fine-tune__count { font-size: 11px; line-height: 16px; color: #64748b; } +.agent-fine-tune__count { flex: none; font-variant-numeric: tabular-nums; } + +.agent-activity__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + border-bottom: 1px solid #e2e8f0; +} + +.agent-activity__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-activity__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-activity__heading small, +.agent-activity__count, +.agent-activity__meta small { + font-size: 11px; + line-height: 16px; + color: #64748b; +} + +.agent-activity__count { + flex: none; + font-variant-numeric: tabular-nums; +} + +.agent-activity__body { + display: grid; + padding: 8px 10px 12px; + margin: 0; + list-style: none; +} + +.agent-activity__item { + display: grid; + grid-template-columns: 28px minmax(0, 1fr); + gap: 10px; + min-height: 58px; + padding: 9px 10px; + list-style: none; + border-radius: 12px; +} + +.agent-activity__item::marker { + content: none; +} + +.agent-activity__item[data-status='running'] { + background: #f0fdfa; +} + +.agent-activity__item[data-status='failed'] { + background: #fef2f2; +} + +.agent-activity__kind { + display: grid; + place-items: center; + width: 28px; + height: 28px; + color: #64748b; + background: #f1f5f9; + border: 1px solid #e2e8f0; + border-radius: 9px; +} + +.agent-activity__kind svg { + display: block; + width: 14px; + height: 14px; +} + +.agent-activity__item[data-kind='reasoning'] .agent-activity__kind { + color: #0f766e; + background: #ccfbf1; + border-color: #99f6e4; +} + +.agent-activity__item[data-kind='search'] .agent-activity__kind { + color: #2563eb; + background: #eff6ff; + border-color: #bfdbfe; +} + +.agent-activity__item[data-kind='tool'] .agent-activity__kind { + color: #7c3aed; + background: #f5f3ff; + border-color: #ddd6fe; +} + +.agent-activity__item[data-status='running'] .agent-activity__kind { + animation: varo-agent-advanced-pulse 1s ease-in-out infinite; +} + +.agent-activity__copy { + display: grid; + gap: 4px; + min-width: 0; +} + +.agent-activity__row { + display: flex; + gap: 10px; + align-items: flex-start; + justify-content: space-between; +} + +.agent-activity__copy strong { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + font-weight: 700; + line-height: 18px; + white-space: nowrap; +} + +.agent-activity__meta { + display: flex; + gap: 8px; + align-items: center; +} + +.agent-activity__kind-label { + padding: 1px 7px; + font-size: 10px; + font-weight: 750; + color: #64748b; + background: #f1f5f9; + border-radius: 999px; +} + +.agent-activity__copy p { + margin: 0; + font-size: 11px; + line-height: 16px; + color: #64748b; +} + +@media (prefers-reduced-motion: reduce) { + .agent-activity__item[data-status='running'] .agent-activity__kind { + animation: none; + } +} .agent-sidebar { display: grid; align-content: start; width: 248px; min-height: 360px; overflow: hidden; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 18px; transition: width .2s ease; } .agent-sidebar[data-collapsed='true'] { width: 64px; } -.agent-sidebar > header { display: flex; align-items: center; justify-content: space-between; min-height: 54px; padding: 0 12px; background: #fff; border-bottom: 1px solid #e2e8f0; } -.agent-sidebar > header strong { font-size: 12px; } -.agent-sidebar > header button { display: grid; place-items: center; width: 28px; height: 28px; color: #64748b; background: #f1f5f9; border: 0; border-radius: 9px; } -.agent-sidebar__create { min-height: 36px; margin: 10px 10px 5px; font-size: 10px; font-weight: 750; color: #0f766e; background: #f0fdfa; border: 1px solid #99f6e4; border-radius: 10px; } -.agent-sidebar nav { display: grid; gap: 10px; padding: 7px; } -.agent-sidebar nav section { display: grid; gap: 3px; } -.agent-sidebar nav section > small { padding: 2px 7px; font-size: 9px; font-weight: 750; color: #94a3b8; text-transform: uppercase; } -.agent-sidebar__item { display: flex; gap: 8px; align-items: center; width: 100%; min-height: 38px; padding: 0 7px; color: #64748b; text-align: left; background: transparent; border: 0; border-radius: 10px; } +.agent-sidebar__header { display: flex; gap: 8px; align-items: center; justify-content: space-between; min-height: 54px; padding: 0 12px; background: #fff; border-bottom: 1px solid #e2e8f0; } +.agent-sidebar__heading { display: flex; gap: 8px; align-items: center; min-width: 0; } + +.agent-sidebar__brand, +.agent-sidebar__mark { display: grid; flex: none; place-items: center; width: 24px; height: 24px; color: #0f766e; background: #ccfbf1; border-radius: 8px; } + +.agent-sidebar__brand svg, +.agent-sidebar__mark svg { display: block; width: 13px; height: 13px; } +.agent-sidebar__heading strong { overflow: hidden; text-overflow: ellipsis; font-size: 12px; font-weight: 760; white-space: nowrap; } +.agent-sidebar__toggle { display: grid; place-items: center; width: 28px; height: 28px; color: #64748b; background: #f1f5f9; border: 0; border-radius: 9px; } +.agent-sidebar__create { display: flex; gap: 6px; align-items: center; justify-content: center; min-height: 36px; margin: 10px 10px 5px; font-size: 11px; font-weight: 750; color: #0f766e; background: #f0fdfa; border: 1px solid #99f6e4; border-radius: 10px; } +.agent-sidebar__create svg { width: 13px; height: 13px; } +.agent-sidebar__nav { display: grid; gap: 10px; padding: 7px; } +.agent-sidebar__group { display: grid; gap: 3px; } +.agent-sidebar__group-label { padding: 2px 7px; font-size: 10px; font-weight: 750; color: #94a3b8; text-transform: uppercase; } +.agent-sidebar__item { display: flex; gap: 8px; align-items: center; width: 100%; min-height: 42px; padding: 0 7px; color: #64748b; text-align: left; background: transparent; border: 0; border-radius: 10px; } .agent-sidebar__item[data-active='true'] { color: #0f766e; background: #fff; box-shadow: 0 3px 12px rgb(15 23 42 / 6%); } -.agent-sidebar__item > i { display: grid; flex: none; place-items: center; width: 24px; height: 24px; font-size: 9px; font-style: normal; font-weight: 800; background: #e2e8f0; border-radius: 8px; } -.agent-sidebar__item > span { display: grid; flex: 1; min-width: 0; } -.agent-sidebar__item strong, .agent-sidebar__item small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.agent-sidebar__item strong { font-size: 10px; } -.agent-sidebar__item small { font-size: 8px; color: #94a3b8; } -.agent-sidebar__item > b { font-size: 9px; } -.agent-context-card > div { display: grid; gap: 9px; padding: 11px; } -.agent-context-card article { overflow: hidden; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; } -.agent-context-card article > header, .agent-context-card article > footer { display: flex; align-items: center; justify-content: space-between; min-height: 34px; padding: 0 10px; } -.agent-context-card article strong { font-size: 10px; } -.agent-context-card article small { font-size: 8px; color: #94a3b8; } -.agent-context-card article p { padding: 9px 10px; margin: 0; font-size: 10px; line-height: 1.55; color: #475569; border-top: 1px solid #f1f5f9; border-bottom: 1px solid #f1f5f9; } -.agent-context-card article footer { font-size: 8px; color: #64748b; } -.agent-context-card article footer button { font-size: 9px; font-weight: 750; color: #0f766e; background: transparent; border: 0; } -.agent-insight-card > div { display: grid; gap: 8px; align-content: center; min-height: 150px; padding: 16px; background: linear-gradient(145deg, #fff, #f8fafc); } -.agent-insight-card[data-tone='danger'] > div { background: linear-gradient(145deg, #fff, #fef2f2); } -.agent-insight-card[data-tone='success'] > div { background: linear-gradient(145deg, #fff, #f0fdf4); } -.agent-insight-card > div small { font-size: 9px; font-weight: 750; color: #64748b; text-transform: uppercase; } -.agent-insight-card > div h3 { margin: 0; font-size: 16px; line-height: 1.4; color: #0f172a; } -.agent-insight-card > div b { font-size: 24px; color: #0f766e; } -.agent-insight-card > footer { justify-content: space-between; } -.agent-insight-card > footer span { display: flex; gap: 5px; } -.agent-selection-actions { display: grid; gap: 10px; } -.agent-selection-actions blockquote { padding: 12px 14px; margin: 0; font-size: 12px; line-height: 1.65; color: #334155; background: #f0fdfa; border-left: 3px solid #14b8a6; border-radius: 0 12px 12px 0; } -.agent-selection-actions > div { display: flex; flex-wrap: wrap; gap: 7px; } -.agent-selection-actions button { min-height: 34px; padding: 0 11px; font-size: 10px; font-weight: 750; color: #475569; background: #fff; border: 1px solid #e2e8f0; border-radius: 999px; } +.agent-sidebar__item[data-active='true'] .agent-sidebar__mark { color: #fff; background: #0f766e; } +.agent-sidebar__copy { display: grid; flex: 1; min-width: 0; } + +.agent-sidebar__copy strong, +.agent-sidebar__copy small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.agent-sidebar__copy strong { font-size: 11px; } +.agent-sidebar__copy small { font-size: 10px; color: #94a3b8; } +.agent-sidebar__badge { display: grid; flex: none; place-items: center; min-width: 18px; height: 18px; padding: 0 5px; font-size: 10px; font-weight: 750; color: #334155; background: #f1f5f9; border-radius: 999px; } + +.agent-sidebar[data-collapsed='true'] .agent-sidebar__header, +.agent-sidebar[data-collapsed='true'] .agent-sidebar__item { justify-content: center; } + +.agent-context-card__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + border-bottom: 1px solid #e2e8f0; +} + +.agent-context-card__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-context-card__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-context-card__heading small, +.agent-context-card__count { + font-size: 11px; + line-height: 16px; + color: #64748b; +} + +.agent-context-card__count { + flex: none; + font-variant-numeric: tabular-nums; +} + +.agent-context-card__list { + display: grid; + gap: 8px; + padding: 10px 12px 12px; + margin: 0; + list-style: none; +} + +.agent-context-card__list > li { + display: block; + list-style: none; +} + +.agent-context-card__list > li::marker { + content: none; +} + +.agent-context-card__chunk { + overflow: hidden; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 12px; +} + +.agent-context-card__chunk-head { + display: flex; + gap: 10px; + align-items: center; + min-height: 40px; + padding: 8px 10px 0; +} + +.agent-context-card__index { + display: grid; + flex: none; + place-items: center; + width: 28px; + height: 28px; + font-size: 10px; + font-weight: 800; + color: #0f766e; + background: #ccfbf1; + border-radius: 9px; +} + +.agent-context-card__chunk-copy { + display: flex; + flex: 1; + gap: 8px; + align-items: center; + min-width: 0; +} + +.agent-context-card__chunk-copy strong { + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + font-weight: 700; + white-space: nowrap; +} + +.agent-context-card__type { + flex: none; + padding: 1px 7px; + font-size: 10px; + font-weight: 750; + color: #64748b; + background: #f1f5f9; + border-radius: 999px; +} + +.agent-context-card__quote { + padding: 8px 10px; + margin: 8px 10px 0; + font-size: 12px; + line-height: 1.55; + color: #475569; + background: #fff; + border-left: 2px solid #0f766e; + border-radius: 0 10px 10px 0; +} + +.agent-context-card__chunk-foot { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; + min-height: 36px; + padding: 0 10px 8px; +} + +.agent-context-card__chunk-foot small { + overflow: hidden; + text-overflow: ellipsis; + font-size: 11px; + color: #64748b; + white-space: nowrap; +} + +.agent-context-card__open { + flex: none; + min-height: 28px; + padding: 0 8px; + font-size: 11px; + font-weight: 750; + color: #0f766e; + background: transparent; + border: 0; +} + +.agent-insight-card { + overflow: hidden; + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 16px; +} +.agent-insight-card[data-tone='success'] { border-color: #86efac; } +.agent-insight-card[data-tone='warning'] { border-color: #fcd34d; } +.agent-insight-card[data-tone='danger'] { border-color: #fca5a5; } + +.agent-insight-card__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + border-bottom: 1px solid #e2e8f0; +} +.agent-insight-card__heading { display: grid; gap: 2px; min-width: 0; } +.agent-insight-card__heading strong { font-size: 13px; font-weight: 760; line-height: 18px; } + +.agent-insight-card__heading small, +.agent-insight-card__count { font-size: 11px; line-height: 16px; color: #64748b; } +.agent-insight-card__count { flex: none; font-variant-numeric: tabular-nums; } +.agent-insight-card__body { display: grid; gap: 8px; min-height: 132px; padding: 18px 16px 16px; } +.agent-insight-card[data-tone='success'] .agent-insight-card__body { background: #f0fdf4; } +.agent-insight-card[data-tone='warning'] .agent-insight-card__body { background: #fffbeb; } +.agent-insight-card[data-tone='danger'] .agent-insight-card__body { background: #fef2f2; } +.agent-insight-card__value { font-size: 32px; font-weight: 850; font-variant-numeric: tabular-nums; line-height: 1; color: #0f766e; } +.agent-insight-card[data-tone='success'] .agent-insight-card__value { color: #16a34a; } +.agent-insight-card[data-tone='warning'] .agent-insight-card__value { color: #d97706; } +.agent-insight-card[data-tone='danger'] .agent-insight-card__value { color: #dc2626; } + +.agent-insight-card__copy, +.agent-insight-card__empty { margin: 0; font-size: 13px; line-height: 1.5; color: #1e293b; } +.agent-insight-card__empty { display: grid; place-items: center; min-height: 132px; color: #64748b; } +.agent-insight-card__footer { display: flex; gap: 8px; align-items: center; justify-content: space-between; min-height: 48px; padding: 0 12px; border-top: 1px solid #e2e8f0; } +.agent-insight-card__nav { display: flex; gap: 6px; } +.agent-insight-card__nav-btn { display: grid; place-items: center; width: 32px; height: 32px; font-size: 16px; color: #64748b; background: #fff; border: 1px solid #e2e8f0; border-radius: 9px; } +.agent-insight-card__action { min-height: 32px; padding: 0 11px; font-size: 11px; font-weight: 750; color: #fff; background: #0f766e; border: 1px solid #0f766e; border-radius: 9px; } +.agent-selection-actions { display: grid; gap: 0; overflow: hidden; background: #fff; border: 1px solid #e2e8f0; border-radius: 16px; } +.agent-selection-actions__quote { padding: 14px 16px; margin: 0; font-size: 13px; line-height: 1.65; color: #334155; background: #f8fafc; border-left: 3px solid #0f766e; } +.agent-selection-actions__toolbar { display: flex; flex-wrap: wrap; gap: 7px; padding: 10px 12px 12px; } +.agent-selection-actions__action { min-height: 32px; padding: 0 11px; font-size: 11px; font-weight: 750; color: #475569; background: #fff; border: 1px solid #e2e8f0; border-radius: 999px; } +.agent-selection-actions__action:hover { color: #0f766e; border-color: #0f766e; } .agent-table { overflow: hidden; background: #fff; border: 1px solid #e2e8f0; border-radius: 15px; } .agent-table > header { display: flex; align-items: center; justify-content: space-between; min-height: 46px; padding: 0 13px; border-bottom: 1px solid #f1f5f9; } .agent-table > header strong { font-size: 12px; } @@ -484,41 +1005,53 @@ .agent-diff-table > footer button { min-height: 34px; padding: 0 10px; font-size: 10px; background: #fff; border: 1px solid #e2e8f0; border-radius: 9px; } .agent-diff-table > footer button:last-child { color: #fff; background: #0f766e; border-color: #0f766e; } .agent-command-search { overflow: hidden; background: #fff; border: 1px solid #e2e8f0; border-radius: 16px; box-shadow: 0 18px 50px rgb(15 23 42 / 10%); } -.agent-command-search > label { display: flex; gap: 9px; align-items: center; min-height: 50px; padding: 0 13px; border-bottom: 1px solid #e2e8f0; } -.agent-command-search > label > span { font-size: 18px; color: #64748b; } +.agent-command-search__field { display: flex; gap: 9px; align-items: center; min-height: 50px; padding: 0 13px; border-bottom: 1px solid #e2e8f0; } +.agent-command-search__icon { display: grid; flex: none; place-items: center; width: 18px; height: 18px; color: #64748b; } +.agent-command-search__icon svg { display: block; width: 16px; height: 16px; } .agent-command-search input { flex: 1; min-width: 0; font: inherit; color: #172033; outline: 0; background: transparent; border: 0; } -.agent-command-search > div { display: grid; max-height: 280px; padding: 7px; overflow-y: auto; } -.agent-command-search > div button { display: flex; gap: 12px; align-items: center; justify-content: space-between; min-height: 44px; padding: 6px 8px; text-align: left; background: transparent; border: 0; border-radius: 10px; } -.agent-command-search > div button:hover { background: #f1f5f9; } -.agent-command-search > div button > span { display: grid; } -.agent-command-search > div strong { font-size: 11px; color: #334155; } -.agent-command-search > div small { font-size: 9px; color: #94a3b8; } -.agent-command-search kbd { padding: 2px 6px; font-size: 8px; color: #64748b; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; } -.agent-command-search > p { padding: 22px; margin: 0; font-size: 11px; color: #94a3b8; text-align: center; } +.agent-command-search__list { display: grid; max-height: 280px; padding: 8px; overflow-y: auto; } +.agent-command-search__group { padding: 8px 8px 4px; font-size: 10px; font-weight: 750; color: #94a3b8; text-transform: uppercase; } +.agent-command-search__item { display: flex; gap: 12px; align-items: center; justify-content: space-between; min-height: 44px; padding: 6px 8px; text-align: left; background: transparent; border: 0; border-radius: 10px; } +.agent-command-search__item:hover { background: #f1f5f9; } +.agent-command-search__copy { display: grid; min-width: 0; } +.agent-command-search__copy strong { font-size: 12px; color: #334155; } +.agent-command-search__copy small { font-size: 11px; color: #94a3b8; } +.agent-command-search kbd { padding: 2px 6px; font-size: 10px; color: #64748b; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; } +.agent-command-search__empty { padding: 22px; margin: 0; font-size: 11px; color: #94a3b8; text-align: center; } .agent-flowchart { overflow: hidden; background: radial-gradient(#dbe4ee 1px, transparent 1px); background-size: 14px 14px; border: 1px solid #e2e8f0; border-radius: 18px; } -.agent-flowchart > header { display: flex; align-items: center; justify-content: space-between; min-height: 48px; padding: 0 13px; background: rgb(255 255 255 / 92%); border-bottom: 1px solid #e2e8f0; } -.agent-flowchart > header strong { font-size: 12px; } -.agent-flowchart > header button { min-height: 32px; padding: 0 9px; font-size: 9px; color: #0f766e; background: #fff; border: 1px solid #e2e8f0; border-radius: 9px; } -.agent-flowchart > div { display: grid; justify-items: center; padding: 24px; } -.agent-flowchart__node { display: grid; gap: 4px; width: min(300px, 100%); padding: 12px 14px; text-align: left; background: #fff; border: 1px solid #cbd5e1; border-radius: 14px; box-shadow: 0 8px 22px rgb(15 23 42 / 7%); } +.agent-flowchart__header { display: flex; gap: 12px; align-items: center; justify-content: space-between; min-height: 52px; padding: 0 14px; background: #fff; border-bottom: 1px solid #e2e8f0; } +.agent-flowchart__heading { display: grid; gap: 2px; min-width: 0; } +.agent-flowchart__heading strong { font-size: 13px; font-weight: 760; } +.agent-flowchart__heading small { font-size: 11px; color: #64748b; } +.agent-flowchart__add { display: inline-flex; gap: 6px; align-items: center; min-height: 32px; padding: 0 9px; font-size: 11px; font-weight: 750; color: #0f766e; background: #fff; border: 1px solid #e2e8f0; border-radius: 9px; } +.agent-flowchart__add svg { width: 13px; height: 13px; } +.agent-flowchart__canvas { display: grid; justify-items: center; padding: 24px; } +.agent-flowchart__node { display: grid; gap: 6px; width: min(320px, 100%); padding: 12px 14px; text-align: left; background: #fff; border: 1px solid #cbd5e1; border-radius: 14px; box-shadow: 0 8px 22px rgb(15 23 42 / 7%); } .agent-flowchart__node[data-type='trigger'] { border-color: #99f6e4; } .agent-flowchart__node[data-type='condition'] { border-color: #fde68a; } +.agent-flowchart__node[data-type='action'] { border-color: #ddd6fe; } .agent-flowchart__node[data-type='result'] { border-color: #bbf7d0; } -.agent-flowchart__node small { font-size: 8px; font-weight: 800; color: #64748b; text-transform: uppercase; } -.agent-flowchart__node strong { font-size: 11px; color: #172033; } -.agent-flowchart__node > span { font-size: 9px; color: #64748b; } -.agent-flowchart__connector { display: grid; place-items: center; width: 24px; height: 36px; font-weight: 900; color: #0f766e; background: linear-gradient(#cbd5e1,#cbd5e1) center/1px 100% no-repeat; border: 0; } -.agent-fine-tune > div { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 12px; } +.agent-flowchart__node[data-status='running'] { box-shadow: 0 0 0 2px rgb(15 118 110 / 18%); } +.agent-flowchart__node-head { display: flex; gap: 8px; align-items: center; } +.agent-flowchart__index { display: grid; place-items: center; width: 22px; height: 22px; font-size: 10px; font-weight: 800; color: #0f766e; background: #ccfbf1; border-radius: 7px; } +.agent-flowchart__type { padding: 1px 7px; font-size: 10px; font-weight: 750; color: #64748b; text-transform: none; background: #f1f5f9; border-radius: 999px; } +.agent-flowchart__node strong { font-size: 13px; font-weight: 760; color: #172033; } +.agent-flowchart__detail { font-size: 11px; line-height: 16px; color: #64748b; } +.agent-flowchart__connector { display: grid; place-items: center; width: 24px; height: 36px; color: #0f766e; background: linear-gradient(#cbd5e1,#cbd5e1) center/1px 100% no-repeat; border: 0; } +.agent-fine-tune { overflow: hidden; background: #fff; border: 1px solid #e2e8f0; border-radius: 16px; } +.agent-fine-tune__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 12px; } .agent-fine-tune__field { display: grid; gap: 6px; min-width: 0; padding: 0; margin: 0; border: 0; } -.agent-fine-tune__label { padding: 0; font-size: 10px; font-weight: 650; color: var(--varo-agent-muted, #64748b); } -.agent-fine-tune__field > input { box-sizing: border-box; width: 100%; min-height: 40px; padding: 0 10px; font: inherit; color: var(--varo-agent-text, #172033); background: var(--varo-agent-surface, #fff); border: 1px solid var(--varo-agent-border, #e2e8f0); border-radius: 10px; } +.agent-fine-tune__label { padding: 0; font-size: 11px; font-weight: 650; color: #64748b; } +.agent-fine-tune__field > input { box-sizing: border-box; width: 100%; min-height: 40px; padding: 0 10px; font: inherit; color: #172033; background: #fff; border: 1px solid #e2e8f0; border-radius: 10px; } .agent-fine-tune__choices { display: grid; grid-template-columns: repeat(auto-fit, minmax(104px, 1fr)); gap: 6px; } -.agent-fine-tune__choice { position: relative; display: flex; gap: 8px; align-items: center; justify-content: space-between; min-height: 40px; padding: 0 11px; color: var(--varo-agent-text, #475569); cursor: pointer; background: var(--varo-agent-surface-strong, #f8fafc); border: 1px solid var(--varo-agent-border, #e2e8f0); border-radius: 10px; transition: border-color 160ms ease, background 160ms ease, color 160ms ease, box-shadow 160ms ease; } -.agent-fine-tune__choice[data-selected='true'] { color: var(--varo-agent-primary, #0f766e); background: var(--varo-agent-primary-soft, #f0fdfa); border-color: var(--varo-agent-primary, #0f766e); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--varo-agent-primary, #0f766e) 18%, transparent); } +.agent-fine-tune__choice { position: relative; display: flex; gap: 8px; align-items: center; justify-content: space-between; min-height: 40px; padding: 0 11px; color: #475569; cursor: pointer; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 10px; transition: border-color 160ms ease, background 160ms ease, color 160ms ease, box-shadow 160ms ease; } +.agent-fine-tune__choice[data-selected='true'] { color: #0f766e; background: #f0fdfa; border-color: #0f766e; box-shadow: inset 0 0 0 1px rgb(15 118 110 / 18%); } .agent-fine-tune__choice input { position: absolute; inset: 0; z-index: 1; margin: 0; cursor: pointer; opacity: 0; } -.agent-fine-tune__choice:focus-within { outline: 2px solid var(--varo-agent-primary, #0f766e); outline-offset: 2px; } +.agent-fine-tune__choice:focus-within { outline: 2px solid #0f766e; outline-offset: 2px; } .agent-fine-tune__choice-check { min-width: 12px; font-size: 12px; font-weight: 850; text-align: center; } -.agent-fine-tune__empty { display: grid; place-items: center; min-height: 40px; font-size: 11px; color: var(--varo-agent-muted, #64748b); background: var(--varo-agent-surface-strong, #f8fafc); border: 1px dashed var(--varo-agent-border, #e2e8f0); border-radius: 10px; } +.agent-fine-tune__empty { display: grid; place-items: center; min-height: 40px; font-size: 11px; color: #64748b; background: #f8fafc; border: 1px dashed #e2e8f0; border-radius: 10px; } +.agent-fine-tune__footer { display: flex; align-items: center; justify-content: flex-end; min-height: 48px; padding: 0 12px; border-top: 1px solid #e2e8f0; } +.agent-fine-tune__apply { min-height: 34px; padding: 0 11px; font-size: 11px; font-weight: 750; color: #fff; background: #0f766e; border: 1px solid #0f766e; border-radius: 10px; } @media (prefers-reduced-motion: reduce) { .agent-advanced__status i, @@ -546,4 +1079,4 @@ } } -@media (max-width: 600px) { .agent-sidebar { width: 100%; } .agent-sidebar[data-collapsed='true'] { width: 64px; } .agent-fine-tune > div { grid-template-columns: minmax(0, 1fr); } } +@media (max-width: 600px) { .agent-sidebar { width: 100%; } .agent-sidebar[data-collapsed='true'] { width: 64px; } .agent-fine-tune__grid { grid-template-columns: minmax(0, 1fr); } } diff --git a/apps/playground-h5/src/components/agent-ui/agent-ui.css b/apps/playground-h5/src/components/agent-ui/agent-ui.css index f3958133..97a20b58 100644 --- a/apps/playground-h5/src/components/agent-ui/agent-ui.css +++ b/apps/playground-h5/src/components/agent-ui/agent-ui.css @@ -405,7 +405,624 @@ @media (prefers-reduced-motion: reduce) { .agent-ui__loading i, .agent-ui__pulse, - .agent-ui__cursor { + .agent-ui__cursor, + .agent-task-list__item[data-status='running'] .agent-task-list__marker i, + .agent-workspace-card__row[data-status='reading'] .agent-workspace-card__mark i { animation: none; } } + +.agent-task-list { + overflow: hidden; + color: var(--varo-agent-foreground, #172033); + background: var(--varo-agent-surface, #fff); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 16px; +} + +.agent-task-list__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + border-bottom: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-task-list__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-task-list__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-task-list__heading small, +.agent-task-list__count { + font-size: 11px; + line-height: 16px; + color: var(--varo-agent-muted, #667085); +} + +.agent-task-list__count { + flex: none; + font-variant-numeric: tabular-nums; +} + +.agent-task-list__body { + display: grid; + padding: 6px 14px 12px 18px; + margin: 0; + list-style: none; +} + +.agent-task-list__item { + position: relative; + display: block; + min-height: 52px; + padding: 10px 0 10px 16px; + list-style: none; + border-left: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-task-list__item::marker { + content: none; +} + +.agent-task-list__marker { + position: absolute; + top: 14px; + left: -11px; + display: grid; + place-items: center; + width: 22px; + height: 22px; + font-size: 10px; + font-weight: 800; + color: var(--varo-agent-muted, #667085); + background: var(--varo-agent-surface, #fff); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 999px; +} + +.agent-task-list__item[data-status='completed'] .agent-task-list__marker { + color: var(--varo-agent-success, #16a34a); + background: var(--varo-agent-success-soft, #dcfce7); + border-color: var(--varo-agent-success, #16a34a); +} + +.agent-task-list__item[data-status='running'] .agent-task-list__marker { + color: var(--varo-agent-primary, #0f766e); + background: var(--varo-agent-primary-soft, #ccfbf1); + border-color: var(--varo-agent-primary, #0f766e); +} + +.agent-task-list__item[data-status='failed'] .agent-task-list__marker { + color: var(--varo-agent-danger, #dc2626); + background: var(--varo-agent-danger-soft, #fee2e2); + border-color: var(--varo-agent-danger, #dc2626); +} + +.agent-task-list__item[data-status='running'] .agent-task-list__marker i { + width: 7px; + height: 7px; + background: currentcolor; + border-radius: 999px; + animation: varo-agent-pulse 1s ease-in-out infinite; +} + +.agent-task-list__copy { + display: grid; + gap: 5px; + min-width: 0; +} + +.agent-task-list__row { + display: flex; + gap: 10px; + align-items: flex-start; + justify-content: space-between; +} + +.agent-task-list__copy strong { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + font-weight: 700; + line-height: 18px; + overflow-wrap: normal; + white-space: nowrap; +} + +.agent-task-list__item[data-status='waiting'] .agent-task-list__copy strong, +.agent-task-list__item[data-status='idle'] .agent-task-list__copy strong { + color: var(--varo-agent-muted, #667085); +} + +.agent-task-list__meta { + display: flex; + flex: none; + gap: 6px; + align-items: center; +} + +.agent-task-list__chip, +.agent-task-list__meta > small { + font-size: 10px; + font-variant-numeric: tabular-nums; + line-height: 14px; + color: var(--varo-agent-muted, #667085); +} + +.agent-task-list__chip { + padding: 2px 7px; + font-weight: 750; + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 999px; +} + +.agent-task-list__item[data-status='completed'] .agent-task-list__chip { + color: var(--varo-agent-success, #16a34a); + background: var(--varo-agent-success-soft, #dcfce7); + border-color: color-mix(in srgb, var(--varo-agent-success, #16a34a) 28%, transparent); +} + +.agent-task-list__item[data-status='running'] .agent-task-list__chip { + color: var(--varo-agent-primary, #0f766e); + background: var(--varo-agent-primary-soft, #ccfbf1); + border-color: color-mix(in srgb, var(--varo-agent-primary, #0f766e) 28%, transparent); +} + +.agent-task-list__item[data-status='failed'] .agent-task-list__chip { + color: var(--varo-agent-danger, #dc2626); + background: var(--varo-agent-danger-soft, #fee2e2); + border-color: color-mix(in srgb, var(--varo-agent-danger, #dc2626) 28%, transparent); +} + +.agent-task-list__copy p { + margin: 0; + font-size: 11px; + line-height: 16px; + color: var(--varo-agent-muted, #667085); +} + +.agent-task-list__track { + display: block; + width: 100%; + height: 4px; + overflow: hidden; + background: var(--varo-agent-fill, #f1f5f9); + border-radius: 999px; +} + +.agent-task-list__track i { + display: block; + height: 100%; + background: var(--varo-agent-primary, #0f766e); + border-radius: inherit; +} + +.agent-task-list__item[data-status='completed'] .agent-task-list__track i { + background: var(--varo-agent-success, #16a34a); +} + +.agent-task-list__item[data-status='failed'] .agent-task-list__track i { + background: var(--varo-agent-danger, #dc2626); +} + +.agent-task-list__item[data-status='running'] { + background: color-mix(in srgb, var(--varo-agent-primary-soft, #ccfbf1) 42%, transparent); + border-radius: 0 12px 12px 0; +} + +.agent-composer-scope, +.agent-retrieval, +.agent-source-receipt, +.agent-thread-versions, +.agent-task-runner__controls { + overflow: hidden; + color: var(--varo-agent-foreground, #172033); + background: var(--varo-agent-surface, #fff); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 16px; +} + +.agent-task-runner { + display: grid; + gap: 10px; +} + +.agent-workspace-card__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + border-bottom: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-workspace-card__heading { + display: grid; + gap: 2px; + min-width: 0; +} + +.agent-workspace-card__heading strong { + font-size: 13px; + font-weight: 760; + line-height: 18px; +} + +.agent-workspace-card__heading small, +.agent-workspace-card__count, +.agent-workspace-card__detail, +.agent-workspace-card__meta, +.agent-workspace-card__empty, +.agent-thread-versions__meta, +.agent-task-runner__cancel span { + font-size: 11px; + line-height: 16px; + color: var(--varo-agent-muted, #667085); +} + +.agent-workspace-card__count, +.agent-workspace-card__meta { + flex: none; + font-variant-numeric: tabular-nums; +} + +.agent-workspace-card__body { + display: grid; + padding: 8px 10px 10px; +} + +.agent-workspace-card__row { + display: flex; + gap: 10px; + align-items: center; + min-height: 58px; + padding: 9px 10px; + border-radius: 12px; +} + +.agent-workspace-card__row[data-status='reading'], +.agent-workspace-card__row[data-status='connecting'], +.agent-workspace-card__row[data-enabled='true'] { + background: color-mix(in srgb, var(--varo-agent-primary-soft, #ccfbf1) 42%, transparent); +} + +.agent-workspace-card__row[data-status='failed'] { + background: var(--varo-agent-danger-soft, #fee2e2); +} + +.agent-workspace-card__mark { + display: grid; + flex: none; + place-items: center; + width: 28px; + height: 28px; + font-size: 10px; + font-weight: 800; + color: var(--varo-agent-muted, #667085); + background: var(--varo-agent-fill, #f1f5f9); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 9px; +} + +.agent-workspace-card__row[data-status='available'] .agent-workspace-card__mark, +.agent-workspace-card__row[data-status='read'] .agent-workspace-card__mark { + color: var(--varo-agent-success, #16a34a); + background: var(--varo-agent-success-soft, #dcfce7); + border-color: var(--varo-agent-success, #16a34a); +} + +.agent-workspace-card__row[data-status='reading'] .agent-workspace-card__mark, +.agent-workspace-card__row[data-status='connecting'] .agent-workspace-card__mark { + color: var(--varo-agent-primary, #0f766e); + background: var(--varo-agent-primary-soft, #ccfbf1); + border-color: var(--varo-agent-primary, #0f766e); +} + +.agent-workspace-card__row[data-status='failed'] .agent-workspace-card__mark, +.agent-workspace-card__row[data-status='unavailable'] .agent-workspace-card__mark { + color: var(--varo-agent-danger, #dc2626); + background: var(--varo-agent-danger-soft, #fee2e2); + border-color: var(--varo-agent-danger, #dc2626); +} + +.agent-workspace-card__row[data-status='reading'] .agent-workspace-card__mark i { + width: 7px; + height: 7px; + background: currentcolor; + border-radius: 999px; + animation: varo-agent-pulse 1s ease-in-out infinite; +} + +.agent-workspace-card__copy { + display: grid; + flex: 1; + gap: 4px; + min-width: 0; +} + +.agent-workspace-card__row-main { + display: flex; + gap: 8px; + align-items: center; + min-width: 0; +} + +.agent-workspace-card__copy strong, +.agent-workspace-card__row-main strong { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + font-weight: 700; + line-height: 18px; + white-space: nowrap; +} + +.agent-workspace-card__chip { + flex: none; + padding: 2px 7px; + font-size: 10px; + font-weight: 750; + line-height: 14px; + color: var(--varo-agent-muted, #667085); + background: var(--varo-agent-fill, #f1f5f9); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 999px; +} + +.agent-workspace-card__row[data-status='read'] .agent-workspace-card__chip, +.agent-workspace-card__row[data-status='available'] .agent-workspace-card__chip { + color: var(--varo-agent-success, #16a34a); + background: var(--varo-agent-success-soft, #dcfce7); + border-color: color-mix(in srgb, var(--varo-agent-success, #16a34a) 28%, transparent); +} + +.agent-workspace-card__row[data-status='reading'] .agent-workspace-card__chip, +.agent-workspace-card__row[data-status='connecting'] .agent-workspace-card__chip { + color: var(--varo-agent-primary, #0f766e); + background: var(--varo-agent-primary-soft, #ccfbf1); + border-color: color-mix(in srgb, var(--varo-agent-primary, #0f766e) 28%, transparent); +} + +.agent-workspace-card__row[data-status='failed'] .agent-workspace-card__chip, +.agent-workspace-card__row[data-status='unavailable'] .agent-workspace-card__chip { + color: var(--varo-agent-danger, #dc2626); + background: var(--varo-agent-danger-soft, #fee2e2); + border-color: color-mix(in srgb, var(--varo-agent-danger, #dc2626) 28%, transparent); +} + +.agent-workspace-card__row[data-status='skipped'] .agent-workspace-card__chip { + color: var(--varo-agent-warning, #b45309); + background: var(--varo-agent-warning-soft, #fef3c7); +} + +.agent-workspace-card__empty { + display: grid; + place-items: center; + min-height: 72px; + padding: 12px; + margin: 0; +} + +.agent-workspace-card__action { + position: relative; + box-sizing: border-box; + display: inline-flex; + flex: none; + align-items: center; + justify-content: center; + min-width: 48px; + min-height: 36px; + padding: 0 10px; + margin: 0; + font-size: 11px; + font-weight: 750; + line-height: 1; + color: var(--varo-agent-foreground, #172033); + background: transparent; + border: 1px solid transparent; + border-radius: 9px; +} + +.agent-workspace-card__action::before { + position: absolute; + inset: -4px; + content: ''; +} + +.agent-workspace-card__action:hover { + background: var(--varo-agent-fill, #f1f5f9); +} + +.agent-workspace-card__action--primary { + color: var(--varo-agent-primary, #0f766e); +} + +.agent-workspace-card__action--primary:hover { + background: var(--varo-agent-primary-soft, #ccfbf1); +} + +.agent-workspace-card__action--danger { + color: var(--varo-agent-danger, #dc2626); +} + +.agent-workspace-card__action--danger:hover { + background: var(--varo-agent-danger-soft, #fee2e2); +} + +.agent-workspace-card__action:disabled { + cursor: not-allowed; + opacity: 0.45; +} + +.agent-composer-scope__meter { + display: grid; + gap: 8px; + padding: 10px 14px 12px; + border-top: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-composer-scope__meter strong { + font-size: 11px; + font-weight: 760; + font-variant-numeric: tabular-nums; + color: var(--varo-agent-primary, #0f766e); +} + +.agent-composer-scope__track { + display: block; + height: 6px; + overflow: hidden; + background: var(--varo-agent-fill, #f1f5f9); + border-radius: 999px; +} + +.agent-composer-scope__track i { + display: block; + height: 100%; + background: var(--varo-agent-primary, #0f766e); + border-radius: inherit; +} + +.agent-thread-versions__list { + display: grid; + gap: 8px; + padding: 10px; +} + +.agent-thread-versions__card { + display: grid; + gap: 8px; + min-width: 0; + padding: 12px; + background: var(--varo-agent-surface-strong, #f8fafc); + border: 1px solid var(--varo-agent-border, #dbe3ea); + border-radius: 14px; +} + +.agent-thread-versions__card[data-active='true'] { + background: var(--varo-agent-primary-soft, #ccfbf1); + border-color: var(--varo-agent-primary, #0f766e); +} + +.agent-thread-versions__meta, +.agent-thread-versions__actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +} + +.agent-thread-versions__actions { + padding-top: 8px; + border-top: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-task-runner__cancel { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; + min-height: 48px; + padding: 8px 14px 10px; + border-top: 1px solid var(--varo-agent-border, #dbe3ea); +} + +.agent-artifact { + overflow: hidden; + color: #dbeafe; + background: #0f172a; + border: 1px solid #1e293b; + border-radius: 16px; +} + +.agent-artifact__header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + min-height: 52px; + padding: 12px 14px 11px; + background: #111c30; + border-bottom: 1px solid #26334a; +} + +.agent-artifact__heading { + display: flex; + flex-wrap: wrap; + gap: 6px 8px; + align-items: center; + min-width: 0; +} + +.agent-artifact__kind { + font-size: 10px; + font-weight: 800; + color: #5eead4; + text-transform: uppercase; + letter-spacing: 0.12em; +} + +.agent-artifact__title { + overflow: hidden; + text-overflow: ellipsis; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + font-weight: 650; + color: #f8fafc; + white-space: nowrap; +} + +.agent-artifact__lang { + padding: 1px 7px; + font-size: 10px; + font-weight: 750; + color: #94a3b8; + background: rgb(148 163 184 / 12%); + border-radius: 999px; +} + +.agent-artifact__open { + flex: none; + min-height: 32px; + padding: 0 10px; + font-size: 11px; + font-weight: 750; + color: #cbd5e1; + background: rgb(148 163 184 / 10%); + border: 1px solid rgb(148 163 184 / 22%); + border-radius: 9px; +} + +.agent-artifact__body { + max-height: 288px; + padding: 14px 16px; + margin: 0; + overflow: auto; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + line-height: 1.7; + color: #cbd5e1; + white-space: pre-wrap; + background: #0f172a; +} + +.agent-artifact__preview { + display: block; + width: 100%; + max-height: 320px; + object-fit: contain; + background: #0b1220; +} diff --git a/apps/playground-h5/src/components/agent-ui/index.ts b/apps/playground-h5/src/components/agent-ui/index.ts index 59e6df17..31fcbb24 100644 --- a/apps/playground-h5/src/components/agent-ui/index.ts +++ b/apps/playground-h5/src/components/agent-ui/index.ts @@ -102,6 +102,30 @@ function partMark(status: AgentPartStatus) { return 'bg-[var(--varo-agent-border-strong)]' } +function taskStatusLabel(status: AgentPartStatus) { + if (status === 'completed') { return '已完成' } + if (status === 'failed') { return '失败' } + if (status === 'running') { return '进行中' } + if (status === 'waiting') { return '等待中' } + return '待开始' +} + +function renderCloseMiniIcon() { + return h('svg', { + 'fill': 'none', + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': 2.2, + 'viewBox': '0 0 24 24', + 'width': 12, + 'height': 12, + 'aria-hidden': 'true', + }, [ + h('path', { d: 'M6 6l12 12M18 6 6 18' }), + ]) +} + function renderCheckIcon() { return h('svg', { 'fill': 'none', @@ -292,37 +316,101 @@ export const AgentTaskList = defineComponent({ }, setup(props) { const completed = computed(() => props.tasks.filter(task => task.status === 'completed').length) - return () => h('section', { 'class': 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]', 'aria-live': 'polite' }, [ - h('header', { class: 'flex min-h-11 items-center justify-between border-b border-[var(--varo-agent-border)] px-3.5' }, [ - h('strong', { class: 'text-[13px] text-[var(--varo-agent-foreground)]' }, props.title), - h('span', { class: 'text-[12px] tabular-nums text-[var(--varo-agent-muted)]' }, `${completed.value}/${props.tasks.length}`), - ]), - ...props.tasks.map((task, index) => h('div', { class: 'flex min-h-[50px] items-center gap-2.5 border-b border-[var(--varo-agent-border)] px-3.5 py-2 last:border-0', key: task.id }, [ - h('span', { class: ['grid h-6 w-6 flex-none place-items-center rounded-full border text-[11px] font-bold', task.status === 'completed' ? 'border-[var(--varo-agent-success)] bg-[var(--varo-agent-success-soft)] text-[var(--varo-agent-success)]' : 'border-[var(--varo-agent-border)] text-[var(--varo-agent-text)]'] }, task.status === 'completed' ? renderCheckIcon() : String(index + 1)), - h('span', { class: 'grid min-w-0 flex-1 gap-1' }, [ - h('span', { class: 'flex justify-between gap-2 text-xs font-semibold text-[var(--varo-agent-foreground)]' }, [task.title, task.meta ? h('small', { class: 'text-[var(--varo-agent-muted)]' }, task.meta) : null]), - task.progress === undefined ? null : h('span', { class: 'h-1 overflow-hidden rounded-full bg-[var(--varo-agent-fill)]' }, [h('i', { class: 'block h-full bg-[var(--varo-agent-primary)]', style: { width: `${Math.min(100, Math.max(0, task.progress))}%` } })]), + const current = computed(() => props.tasks.find(task => task.status === 'running' || task.status === 'failed')) + return () => h('section', { 'class': 'agent-task-list', 'aria-live': 'polite' }, [ + h('header', { class: 'agent-task-list__header' }, [ + h('span', { class: 'agent-task-list__heading' }, [ + h('strong', props.title), + current.value + ? h('small', current.value.status === 'failed' ? `阻塞于 ${current.value.title}` : `正在执行 ${current.value.title}`) + : h('small', completed.value === props.tasks.length && props.tasks.length ? '全部完成' : '等待开始'), ]), - ])), + h('span', { class: 'agent-task-list__count' }, `${completed.value}/${props.tasks.length}`), + ]), + h('ol', { class: 'agent-task-list__body' }, props.tasks.map((task, index) => { + const progress = task.progress === undefined ? undefined : Math.min(100, Math.max(0, task.progress)) + return h('li', { + 'class': 'agent-task-list__item', + 'data-status': task.status, + 'aria-current': task.status === 'running' ? 'step' : undefined, + 'key': task.id, + }, [ + h('span', { 'class': 'agent-task-list__marker', 'aria-hidden': 'true' }, [ + task.status === 'completed' + ? renderCheckIcon() + : task.status === 'failed' + ? renderCloseMiniIcon() + : task.status === 'running' + ? h('i') + : String(index + 1), + ]), + h('span', { class: 'agent-task-list__copy' }, [ + h('span', { class: 'agent-task-list__row' }, [ + h('strong', task.title), + h('span', { class: 'agent-task-list__meta' }, [ + h('small', { class: 'agent-task-list__chip' }, taskStatusLabel(task.status)), + task.meta ? h('small', task.meta) : null, + ]), + ]), + task.description ? h('p', task.description) : null, + progress === undefined + ? null + : h('span', { 'class': 'agent-task-list__track', 'aria-hidden': 'true' }, [ + h('i', { style: { width: `${progress}%` } }), + ]), + ]), + ]) + })), ]) }, }) -const workspaceActionButton = 'relative m-0 inline-flex min-h-9 min-w-12 items-center justify-center rounded-lg px-2.5 text-[11px] font-bold transition-colors before:absolute before:-inset-1 before:content-[\'\'] motion-reduce:transition-none disabled:cursor-not-allowed disabled:opacity-45' -const workspacePrimaryTextButton = `${workspaceActionButton} border border-transparent bg-transparent text-[var(--varo-agent-primary)] hover:bg-[var(--varo-agent-primary-soft)]` -const workspaceQuietButton = `${workspaceActionButton} border border-transparent bg-transparent text-[var(--varo-agent-foreground)] hover:bg-[var(--varo-agent-fill)]` -const workspaceDangerTextButton = `${workspaceActionButton} border border-transparent bg-transparent text-[var(--varo-agent-danger)] hover:bg-[var(--varo-agent-danger-soft)]` +function renderWorkspaceIcon(kind: 'plug' | 'receipt' | 'branch') { + const common = { + 'aria-hidden': 'true' as const, + 'fill': 'none', + 'height': 14, + 'stroke': 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': 1.8, + 'viewBox': '0 0 24 24', + 'width': 14, + } + if (kind === 'receipt') { + return h('svg', common, [ + h('path', { d: 'M7 3h8l4 4v14H7V3Z' }), + h('path', { d: 'M15 3v4h4M9 12h6M9 16h4' }), + ]) + } + if (kind === 'branch') { + return h('svg', common, [ + h('circle', { cx: '6', cy: '6', r: '2.2' }), + h('circle', { cx: '18', cy: '12', r: '2.2' }), + h('circle', { cx: '6', cy: '18', r: '2.2' }), + h('path', { d: 'M8.1 7.6v8.8M8.2 12H16' }), + ]) + } + return h('svg', common, [ + h('path', { d: 'M9 7V3M15 7V3' }), + h('path', { d: 'M8 7h8v4a4 4 0 0 1-4 4h0a4 4 0 0 1-4-4V7Z' }), + h('path', { d: 'M12 15v6' }), + ]) +} + +function workspaceActionClass(kind: 'quiet' | 'primary' | 'danger' = 'quiet') { + return cn( + 'agent-workspace-card__action', + kind === 'primary' && 'agent-workspace-card__action--primary', + kind === 'danger' && 'agent-workspace-card__action--danger', + ) +} const contextSourceStatusLabels = { available: '可用', connecting: '连接中', unavailable: '不可用', } as const -const contextSourceStatusClasses = { - available: 'bg-[var(--varo-agent-success-soft)] text-[var(--varo-agent-success)]', - connecting: 'bg-[var(--varo-agent-primary-soft)] text-[var(--varo-agent-primary)]', - unavailable: 'bg-[var(--varo-agent-danger-soft)] text-[var(--varo-agent-danger)]', -} as const const retrievalStatusLabels = { failed: '读取失败', queued: '排队中', @@ -330,23 +418,11 @@ const retrievalStatusLabels = { reading: '读取中', skipped: '已跳过', } as const -const retrievalStatusClasses = { - failed: 'bg-[var(--varo-agent-danger-soft)] text-[var(--varo-agent-danger)]', - queued: 'bg-[var(--varo-agent-fill)] text-[var(--varo-agent-muted)]', - read: 'bg-[var(--varo-agent-success-soft)] text-[var(--varo-agent-success)]', - reading: 'bg-[var(--varo-agent-primary-soft)] text-[var(--varo-agent-primary)]', - skipped: 'bg-[var(--varo-agent-warning-soft)] text-[var(--varo-agent-warning)]', -} as const const receiptStatusLabels = { failed: '读取失败', read: '已读取', skipped: '已跳过', } as const -const receiptStatusClasses = { - failed: 'bg-[var(--varo-agent-danger-soft)] text-[var(--varo-agent-danger)]', - read: 'bg-[var(--varo-agent-success-soft)] text-[var(--varo-agent-success)]', - skipped: 'bg-[var(--varo-agent-warning-soft)] text-[var(--varo-agent-warning)]', -} as const function renderCloseIcon() { return h('svg', { @@ -381,30 +457,38 @@ export const AgentComposerScope = defineComponent({ return Math.min(100, Math.max(0, value)) }) const enabledCount = computed(() => props.sources.filter(source => source.enabled).length) - return () => h('section', { 'class': 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]', 'aria-label': props.title }, [ - h('header', { class: 'flex min-h-12 items-center justify-between gap-3 border-b border-[var(--varo-agent-border)] px-3.5' }, [ - h('strong', { class: 'text-[13px] text-[var(--varo-agent-foreground)]' }, props.title), - h('span', { class: 'text-[11px] tabular-nums text-[var(--varo-agent-muted)]' }, `${enabledCount.value} 个已启用`), + const headingHint = computed(() => { + if (!props.sources.length) { return '暂无可用来源' } + if (enabledCount.value === 0) { return '尚未授权任何来源' } + return `${enabledCount.value} 个来源已加入上下文` + }) + return () => h('section', { 'class': 'agent-composer-scope', 'aria-label': props.title }, [ + h('header', { class: 'agent-workspace-card__header' }, [ + h('span', { class: 'agent-workspace-card__heading' }, [ + h('strong', props.title), + h('small', headingHint.value), + ]), + h('span', { class: 'agent-workspace-card__count' }, `${enabledCount.value}/${props.sources.length}`), ]), props.sources.length - ? h('div', { class: 'grid gap-1 p-2' }, props.sources.map((source) => { + ? h('div', { class: 'agent-workspace-card__body' }, props.sources.map((source) => { const status = source.status ?? 'available' - return h('div', { 'class': 'flex min-h-[58px] items-center gap-2.5 rounded-xl bg-[var(--varo-agent-surface-strong)] px-2.5 py-2', 'data-status': status, 'key': source.id }, [ - h('i', { 'class': ['h-3 w-3 flex-none rounded-full', contextSourceStatusClasses[status]], 'aria-hidden': 'true' }), - h('span', { class: 'grid min-w-0 flex-1 gap-0.5' }, [ - h('span', { class: 'flex min-w-0 items-center gap-2' }, [ - h('strong', { class: 'truncate text-[12px] text-[var(--varo-agent-foreground)]' }, source.label), - h('small', { class: 'flex-none text-[10px] font-semibold text-[var(--varo-agent-muted)]' }, contextSourceStatusLabels[status]), + return h('article', { 'class': 'agent-workspace-card__row', 'data-status': status, 'data-enabled': String(source.enabled), 'key': source.id }, [ + h('span', { 'class': 'agent-workspace-card__mark', 'aria-hidden': 'true' }, renderWorkspaceIcon('plug')), + h('span', { class: 'agent-workspace-card__copy' }, [ + h('span', { class: 'agent-workspace-card__row-main' }, [ + h('strong', source.label), + h('small', { class: 'agent-workspace-card__chip' }, contextSourceStatusLabels[status]), ]), source.description || source.meta - ? h('small', { class: 'truncate text-[11px] text-[var(--varo-agent-muted)]' }, source.description || source.meta) + ? h('small', { class: 'agent-workspace-card__detail' }, source.description || source.meta) : null, ]), status === 'available' ? h('button', { 'aria-label': `${source.enabled ? '停用' : '启用'}${source.label}`, 'aria-pressed': source.enabled, - 'class': workspaceQuietButton, + 'class': workspaceActionClass(source.enabled ? 'quiet' : 'primary'), 'disabled': props.disabled, 'type': 'button', 'onClick': () => emit('toggle', source, !source.enabled), @@ -412,29 +496,29 @@ export const AgentComposerScope = defineComponent({ : status === 'unavailable' ? h('button', { 'aria-label': `连接${source.label}`, - 'class': workspacePrimaryTextButton, + 'class': workspaceActionClass('primary'), 'disabled': props.disabled, 'type': 'button', 'onClick': () => emit('connect', source), }, '连接') - : null, + : h('small', { class: 'agent-workspace-card__chip' }, '连接中'), ]) })) - : h('p', { class: 'm-0 grid min-h-20 place-items-center px-3 text-[12px] text-[var(--varo-agent-muted)]' }, '暂无来源'), - h('footer', { class: 'grid gap-2 border-t border-[var(--varo-agent-border)] px-3.5 py-3' }, [ - h('span', { class: 'flex items-center justify-between gap-3' }, [ - h('span', { class: 'text-[11px] font-semibold text-[var(--varo-agent-text)]' }, '上下文使用'), - h('strong', { class: 'text-[11px] tabular-nums text-[var(--varo-agent-primary)]' }, `${usage.value}%`), + : h('p', { class: 'agent-workspace-card__empty' }, '暂无来源'), + h('footer', { class: 'agent-composer-scope__meter' }, [ + h('span', { class: 'agent-workspace-card__row-main' }, [ + h('span', '上下文使用'), + h('strong', `${usage.value}%`), ]), h('span', { - 'class': 'block h-1.5 overflow-hidden rounded-full bg-[var(--varo-agent-fill)]', + 'class': 'agent-composer-scope__track', 'role': 'progressbar', 'aria-label': '上下文使用量', 'aria-valuemax': 100, 'aria-valuemin': 0, 'aria-valuenow': usage.value, }, [ - h('i', { class: 'block h-full rounded-full bg-[var(--varo-agent-primary)]', style: { width: `${usage.value}%` } }), + h('i', { style: { width: `${usage.value}%` } }), ]), ]), ]) @@ -452,31 +536,41 @@ export const AgentRetrievalProgress = defineComponent({ }, setup(props, { emit }) { const settled = computed(() => props.items.filter(item => item.status === 'read' || item.status === 'skipped' || item.status === 'failed').length) - return () => h('section', { 'class': 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]', 'aria-atomic': 'false', 'aria-live': 'polite' }, [ - h('header', { class: 'flex min-h-12 items-center justify-between gap-3 border-b border-[var(--varo-agent-border)] px-3.5' }, [ - h('strong', { class: 'text-[13px] text-[var(--varo-agent-foreground)]' }, props.title), - h('span', { class: 'text-[11px] tabular-nums text-[var(--varo-agent-muted)]' }, `${settled.value}/${props.items.length} 已处理`), + const current = computed(() => props.items.find(item => item.status === 'reading' || item.status === 'failed')) + const headingHint = computed(() => { + if (current.value?.status === 'failed') { return `阻塞于 ${current.value.title}` } + if (current.value) { return `正在读取 ${current.value.title}` } + if (props.items.length && settled.value === props.items.length) { return '检索已完成' } + return '等待检索' + }) + return () => h('section', { 'class': 'agent-retrieval', 'aria-atomic': 'false', 'aria-live': 'polite' }, [ + h('header', { class: 'agent-workspace-card__header' }, [ + h('span', { class: 'agent-workspace-card__heading' }, [ + h('strong', props.title), + h('small', headingHint.value), + ]), + h('span', { class: 'agent-workspace-card__count' }, `${settled.value}/${props.items.length}`), ]), props.items.length - ? h('div', { class: 'grid gap-1 p-2' }, props.items.map(item => h('div', { 'class': 'flex min-h-[56px] items-center gap-2.5 rounded-xl px-2.5 py-2', 'data-status': item.status, 'key': item.id }, [ - h('i', { 'class': ['h-3 w-3 flex-none rounded-full', retrievalStatusClasses[item.status]], 'aria-hidden': 'true' }), - h('span', { class: 'grid min-w-0 flex-1 gap-0.5' }, [ - h('strong', { class: 'truncate text-[12px] text-[var(--varo-agent-foreground)]' }, item.title), - item.detail ? h('small', { class: 'text-[11px] leading-4 text-[var(--varo-agent-muted)]' }, item.detail) : null, - ]), - h('span', { class: 'flex flex-none items-center gap-2' }, [ - h('small', { class: 'text-[10px] font-semibold text-[var(--varo-agent-text)]' }, retrievalStatusLabels[item.status]), - item.status === 'failed' && item.retryable - ? h('button', { - 'aria-label': `重试${item.title}`, - 'class': workspaceQuietButton, - 'type': 'button', - 'onClick': () => emit('retry', item), - }, '重试') - : null, + ? h('div', { class: 'agent-workspace-card__body' }, props.items.map((item, index) => h('article', { 'class': 'agent-workspace-card__row', 'data-status': item.status, 'key': item.id }, [ + h('span', { 'class': 'agent-workspace-card__mark', 'aria-hidden': 'true' }, item.status === 'reading' ? h('i') : String(index + 1).padStart(2, '0')), + h('span', { class: 'agent-workspace-card__copy' }, [ + h('span', { class: 'agent-workspace-card__row-main' }, [ + h('strong', item.title), + h('small', { class: 'agent-workspace-card__chip' }, retrievalStatusLabels[item.status]), + ]), + item.detail ? h('small', { class: 'agent-workspace-card__detail' }, item.detail) : null, ]), + item.status === 'failed' && item.retryable + ? h('button', { + 'aria-label': `重试${item.title}`, + 'class': workspaceActionClass('primary'), + 'type': 'button', + 'onClick': () => emit('retry', item), + }, '重试') + : null, ]))) - : h('p', { class: 'm-0 grid min-h-20 place-items-center px-3 text-[12px] text-[var(--varo-agent-muted)]' }, '暂无检索项'), + : h('p', { class: 'agent-workspace-card__empty' }, '暂无检索项'), ]) }, }) @@ -494,44 +588,49 @@ export const AgentSourceReceipt = defineComponent({ }, setup(props, { emit }) { const readCount = computed(() => props.items.filter(item => item.status === 'read').length) - return () => h('section', { 'class': 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]', 'aria-label': props.title }, [ - h('header', { class: 'grid min-h-12 gap-0.5 border-b border-[var(--varo-agent-border)] px-3.5 py-2.5' }, [ - h('span', { class: 'flex items-center justify-between gap-3' }, [ - h('strong', { class: 'text-[13px] text-[var(--varo-agent-foreground)]' }, props.title), - h('small', { class: 'text-[11px] tabular-nums text-[var(--varo-agent-muted)]' }, `${readCount.value}/${props.items.length} 已读取`), + const failedCount = computed(() => props.items.filter(item => item.status === 'failed').length) + const headingHint = computed(() => { + if (failedCount.value) { return `${failedCount.value} 个来源读取失败` } + if (props.summary) { return props.summary } + if (props.items.length && readCount.value === props.items.length) { return '全部来源已核对' } + return '回答完成后核对来源' + }) + return () => h('section', { 'class': 'agent-source-receipt', 'aria-label': props.title }, [ + h('header', { class: 'agent-workspace-card__header' }, [ + h('span', { class: 'agent-workspace-card__heading' }, [ + h('strong', props.title), + h('small', headingHint.value), ]), - props.summary ? h('small', { class: 'text-[11px] leading-4 text-[var(--varo-agent-muted)]' }, props.summary) : null, + h('span', { class: 'agent-workspace-card__count' }, `${readCount.value}/${props.items.length}`), ]), props.items.length - ? h('div', { class: 'grid gap-1 p-2' }, props.items.map(item => h('div', { 'class': 'flex min-h-[56px] items-center gap-2.5 rounded-xl bg-[var(--varo-agent-surface-strong)] px-2.5 py-2', 'data-status': item.status, 'key': item.id }, [ - h('i', { 'class': ['h-3 w-3 flex-none rounded-full', receiptStatusClasses[item.status]], 'aria-hidden': 'true' }), - h('span', { class: 'grid min-w-0 flex-1 gap-0.5' }, [ - h('span', { class: 'flex min-w-0 items-baseline gap-2' }, [ - h('strong', { class: 'truncate text-[12px] text-[var(--varo-agent-foreground)]' }, item.label), - item.itemCount === undefined - ? null - : h('small', { class: 'flex-none text-[10px] tabular-nums text-[var(--varo-agent-muted)]' }, `${item.itemCount} 项`), + ? h('div', { class: 'agent-workspace-card__body' }, props.items.map(item => h('article', { 'class': 'agent-workspace-card__row', 'data-status': item.status, 'key': item.id }, [ + h('span', { 'class': 'agent-workspace-card__mark', 'aria-hidden': 'true' }, renderWorkspaceIcon('receipt')), + h('span', { class: 'agent-workspace-card__copy' }, [ + h('span', { class: 'agent-workspace-card__row-main' }, [ + h('strong', item.label), + item.itemCount === undefined ? null : h('small', { class: 'agent-workspace-card__meta' }, `${item.itemCount} 项`), ]), - item.detail ? h('small', { class: 'text-[11px] leading-4 text-[var(--varo-agent-muted)]' }, item.detail) : null, - h('small', { class: 'text-[10px] font-semibold text-[var(--varo-agent-text)]' }, receiptStatusLabels[item.status]), + item.detail ? h('small', { class: 'agent-workspace-card__detail' }, item.detail) : null, + h('small', { class: 'agent-workspace-card__chip' }, receiptStatusLabels[item.status]), ]), item.status === 'read' ? h('button', { 'aria-label': `查看${item.label}`, - 'class': workspaceQuietButton, + 'class': workspaceActionClass(), 'type': 'button', 'onClick': () => emit('open', item), }, '查看') : item.status === 'failed' ? h('button', { 'aria-label': `连接${item.label}`, - 'class': workspacePrimaryTextButton, + 'class': workspaceActionClass('primary'), 'type': 'button', 'onClick': () => emit('connect', item), }, '连接') : null, ]))) - : h('p', { class: 'm-0 grid min-h-20 place-items-center px-3 text-[12px] text-[var(--varo-agent-muted)]' }, '暂无来源回执'), + : h('p', { class: 'agent-workspace-card__empty' }, '暂无来源回执'), ]) }, }) @@ -557,38 +656,38 @@ export const AgentTaskRunner = defineComponent({ ) }) const canCancel = computed(() => props.busy || props.tasks.some(task => task.status === 'running')) - return () => h('section', { class: 'grid gap-2.5' }, [ + return () => h('section', { class: 'agent-task-runner' }, [ h(AgentTaskList, { tasks: props.tasks, title: props.title }), actionableTasks.value.length || canCancel.value - ? h('div', { class: 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]' }, [ + ? h('div', { class: 'agent-task-runner__controls' }, [ actionableTasks.value.length - ? h('div', { class: 'grid gap-1 p-2' }, actionableTasks.value.map(task => h('div', { class: 'flex min-h-[56px] items-center gap-2.5 rounded-xl bg-[var(--varo-agent-surface-strong)] px-2.5 py-2', key: task.id }, [ - h('span', { class: 'grid min-w-0 flex-1 gap-0.5' }, [ - h('strong', { class: 'truncate text-[12px] text-[var(--varo-agent-foreground)]' }, task.title), - task.description ? h('small', { class: 'text-[11px] leading-4 text-[var(--varo-agent-muted)]' }, task.description) : null, - h('small', { class: 'text-[10px] font-semibold text-[var(--varo-agent-text)]' }, task.status === 'failed' ? '执行失败' : '等待确认'), + ? h('div', { class: 'agent-workspace-card__body' }, actionableTasks.value.map(task => h('article', { 'class': 'agent-workspace-card__row', 'data-status': task.status, 'key': task.id }, [ + h('span', { class: 'agent-workspace-card__copy' }, [ + h('strong', task.title), + task.description ? h('small', { class: 'agent-workspace-card__detail' }, task.description) : null, + h('small', { class: 'agent-workspace-card__chip' }, task.status === 'failed' ? '执行失败' : '等待确认'), ]), task.status === 'failed' ? h('button', { 'aria-label': `重试${task.title}`, - 'class': workspaceQuietButton, + 'class': workspaceActionClass('primary'), 'type': 'button', 'onClick': () => emit('retry', task), }, '重试') : h('button', { 'aria-label': `批准${task.title}`, - 'class': workspacePrimaryTextButton, + 'class': workspaceActionClass('primary'), 'type': 'button', 'onClick': () => emit('approve', task), }, '批准'), ]))) : null, canCancel.value - ? h('div', { class: 'flex min-h-[60px] items-center justify-between gap-3 border-t border-[var(--varo-agent-border)] px-3.5 py-2' }, [ - h('span', { class: 'text-[11px] text-[var(--varo-agent-muted)]' }, '任务正在执行'), + ? h('div', { class: 'agent-task-runner__cancel' }, [ + h('span', '任务正在执行'), h('button', { 'aria-label': '取消当前任务', - 'class': workspaceDangerTextButton, + 'class': workspaceActionClass('danger'), 'type': 'button', 'onClick': () => emit('cancel'), }, '取消'), @@ -625,52 +724,49 @@ export const AgentThreadVersions = defineComponent({ version, })) }) - return () => h('section', { 'class': 'overflow-hidden rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)]', 'aria-label': props.title }, [ - h('header', { class: 'flex min-h-12 items-center justify-between gap-3 border-b border-[var(--varo-agent-border)] px-3.5' }, [ - h('strong', { class: 'text-[13px] text-[var(--varo-agent-foreground)]' }, props.title), - h('span', { class: 'text-[11px] tabular-nums text-[var(--varo-agent-muted)]' }, `${props.versions.length} 个版本`), + const active = computed(() => displayVersions.value.find(entry => entry.active)) + return () => h('section', { 'class': 'agent-thread-versions', 'aria-label': props.title }, [ + h('header', { class: 'agent-workspace-card__header' }, [ + h('span', { class: 'agent-workspace-card__heading' }, [ + h('strong', props.title), + h('small', active.value ? `当前 ${active.value.label}` : '选择一个会话版本'), + ]), + h('span', { class: 'agent-workspace-card__count' }, `${props.versions.length} 个版本`), ]), displayVersions.value.length - ? h('div', { 'class': 'flex max-w-full gap-2 overflow-x-auto p-2.5', 'role': 'list', 'aria-label': '会话版本列表' }, displayVersions.value.map(entry => + ? h('div', { 'class': 'agent-thread-versions__list', 'role': 'list', 'aria-label': '会话版本列表' }, displayVersions.value.map(entry => h('article', { - 'class': [ - 'grid w-[252px] flex-none gap-2 rounded-xl border bg-[var(--varo-agent-surface-strong)] p-3', - entry.active - ? 'border-[var(--varo-agent-primary)] shadow-[0_0_0_2px_var(--varo-agent-primary-soft)]' - : 'border-[var(--varo-agent-border)]', - ], + 'class': 'agent-thread-versions__card', 'data-active': String(entry.active), 'key': entry.version.id, 'role': 'listitem', }, [ - h('span', { class: 'flex min-w-0 items-center justify-between gap-2' }, [ - h('span', { class: 'flex min-w-0 items-center gap-2' }, [ - h('i', { 'class': ['h-2 w-2 flex-none rounded-full', entry.active ? 'bg-[var(--varo-agent-primary)]' : 'bg-[var(--varo-agent-border-strong)]'], 'aria-hidden': 'true' }), - h('strong', { class: 'truncate text-[12px] text-[var(--varo-agent-foreground)]' }, entry.label), - ]), + h('span', { class: 'agent-workspace-card__row-main' }, [ + h('span', { 'class': 'agent-workspace-card__mark', 'aria-hidden': 'true' }, renderWorkspaceIcon('branch')), + h('strong', entry.label), entry.active - ? h('small', { class: 'flex-none text-[10px] font-bold text-[var(--varo-agent-primary)]' }, '当前') + ? h('small', { class: 'agent-workspace-card__chip' }, '当前') : entry.version.pinned - ? h('small', { class: 'flex-none text-[10px] font-bold text-[var(--varo-agent-text)]' }, '已固定') + ? h('small', { class: 'agent-workspace-card__chip' }, '已固定') : null, ]), - h('p', { class: 'm-0 min-h-8 text-[11px] leading-4 text-[var(--varo-agent-muted)]' }, entry.version.summary || '暂无版本摘要'), - h('span', { class: 'grid gap-0.5 text-[10px] text-[var(--varo-agent-muted)]' }, [ - h('small', { class: 'truncate' }, `来源:${entry.parentLabel}`), - entry.version.createdAt ? h('time', { class: 'truncate' }, `创建:${entry.version.createdAt}`) : null, + h('p', { class: 'agent-workspace-card__detail' }, entry.version.summary || '暂无版本摘要'), + h('span', { class: 'agent-thread-versions__meta' }, [ + h('small', `来源:${entry.parentLabel}`), + entry.version.createdAt ? h('time', `创建:${entry.version.createdAt}`) : null, ]), - h('footer', { class: 'flex flex-wrap gap-2 border-t border-[var(--varo-agent-border)] pt-2' }, [ + h('footer', { class: 'agent-thread-versions__actions' }, [ entry.active ? null : h('button', { 'aria-label': `选择${entry.label}`, - 'class': workspacePrimaryTextButton, + 'class': workspaceActionClass('primary'), 'type': 'button', 'onClick': () => emit('select', entry.version), }, '选择'), h('button', { 'aria-label': `从${entry.label}创建分支`, - 'class': workspaceQuietButton, + 'class': workspaceActionClass(), 'type': 'button', 'onClick': () => emit('branch', entry.version), }, '分支'), @@ -678,14 +774,14 @@ export const AgentThreadVersions = defineComponent({ ? null : h('button', { 'aria-label': `固定${entry.label}`, - 'class': workspaceQuietButton, + 'class': workspaceActionClass(), 'type': 'button', 'onClick': () => emit('pin', entry.version), }, '固定'), ]), ]), )) - : h('p', { class: 'm-0 grid min-h-20 place-items-center px-3 text-[12px] text-[var(--varo-agent-muted)]' }, '暂无会话版本'), + : h('p', { class: 'agent-workspace-card__empty' }, '暂无会话版本'), ]) }, }) @@ -949,11 +1045,19 @@ export const AgentRecommendation = defineComponent({ emits: { accept: () => true }, setup(props, { emit, slots }) { const confidence = computed(() => Math.min(100, Math.max(0, props.confidence))) + const confidenceLabel = computed(() => ( + confidence.value >= 80 ? '高置信度' : confidence.value >= 55 ? '中等置信度' : '需要复核' + )) return () => h('section', { class: 'agent-recommendation grid gap-3 rounded-2xl border border-[var(--varo-agent-border)] bg-[var(--varo-agent-surface)] p-4 shadow-[var(--varo-agent-shadow)]' }, [ - h('header', { class: 'flex justify-between text-[11px] font-extrabold tracking-widest text-[var(--varo-agent-primary)]' }, [h('span', 'AGENT 建议'), h('span', `${confidence.value}%`)]), + h('header', { class: 'flex justify-between gap-3 text-[11px] font-extrabold tracking-widest text-[var(--varo-agent-primary)]' }, [ + h('span', 'AGENT 建议'), + h('span', { class: 'tabular-nums' }, `${confidenceLabel.value} · ${confidence.value}%`), + ]), h('strong', { class: 'text-[15px] text-[var(--varo-agent-foreground)]' }, props.title), props.description ? h('p', { class: 'm-0 text-xs leading-5 text-[var(--varo-agent-text)]' }, props.description) : null, - h('span', { class: 'h-1.5 overflow-hidden rounded-full bg-blue-100' }, [h('i', { class: 'block h-full rounded-full bg-blue-600', style: { width: `${confidence.value}%` } })]), + h('span', { 'class': 'h-1.5 overflow-hidden rounded-full bg-[var(--varo-agent-primary-soft)]', 'aria-hidden': 'true' }, [ + h('i', { class: 'block h-full rounded-full bg-[var(--varo-agent-primary)]', style: { width: `${confidence.value}%` } }), + ]), slots.default?.(), h('footer', { class: 'flex justify-end gap-2' }, [slots.secondary?.(), h('button', { class: primaryButton, type: 'button', onClick: () => emit('accept') }, props.acceptText)]), ]) @@ -1105,32 +1209,31 @@ export const AgentArtifact = defineComponent({ props: { artifact: { type: Object as PropType, required: true } }, emits: { open: (_artifact: AgentArtifactItem) => true }, setup(props, { emit }) { + const kindLabel = computed(() => { + if (props.artifact.kind === 'code') { return '代码产物' } + if (props.artifact.kind === 'image') { return '图像产物' } + if (props.artifact.kind === 'file') { return '文件产物' } + return '文档产物' + }) return () => - h('article', { class: 'overflow-hidden rounded-2xl border border-slate-800 bg-slate-950 shadow-lg' }, [ - h('header', { class: 'flex min-h-14 items-center justify-between gap-3 border-b border-slate-800 bg-slate-900 px-4' }, [ - h('span', { class: 'grid min-w-0 gap-0.5' }, [ - h('small', { class: 'text-[10px] font-black uppercase tracking-[0.16em] text-teal-300' }, props.artifact.kind || 'artifact'), - h('strong', { class: 'truncate text-xs text-slate-100' }, props.artifact.title), + h('article', { 'class': 'agent-artifact', 'data-kind': props.artifact.kind || 'document' }, [ + h('header', { class: 'agent-artifact__header' }, [ + h('span', { class: 'agent-artifact__heading' }, [ + h('small', { class: 'agent-artifact__kind' }, kindLabel.value), + h('strong', { class: 'agent-artifact__title' }, props.artifact.title), + props.artifact.language ? h('span', { class: 'agent-artifact__lang' }, props.artifact.language) : null, ]), - h( - 'button', - { - class: 'min-h-8 flex-none rounded-lg border border-slate-700 bg-slate-800 px-3 text-[11px] font-bold text-slate-200 transition-colors hover:border-teal-500 hover:text-white', - type: 'button', - onClick: () => emit('open', props.artifact), - }, - '打开', - ), + h('button', { + class: 'agent-artifact__open', + type: 'button', + onClick: () => emit('open', props.artifact), + }, '打开'), ]), props.artifact.content - ? h( - 'pre', - { class: 'm-0 max-h-72 overflow-auto whitespace-pre-wrap break-words bg-slate-950 px-4 py-3.5 font-mono text-[12px] leading-5 text-slate-200' }, - props.artifact.content, - ) + ? h('pre', { class: 'agent-artifact__body' }, props.artifact.content) : null, props.artifact.previewUrl - ? h('img', { alt: props.artifact.title, class: 'block max-h-80 w-full bg-slate-950 object-contain', src: props.artifact.previewUrl }) + ? h('img', { alt: props.artifact.title, class: 'agent-artifact__preview', src: props.artifact.previewUrl }) : null, ]) }, diff --git a/apps/playground-weapp-preview/index.html b/apps/playground-weapp-preview/index.html new file mode 100644 index 00000000..5c4509a7 --- /dev/null +++ b/apps/playground-weapp-preview/index.html @@ -0,0 +1,14 @@ + + + + + + + Varo 小程序 Web 兼容预览 + + +
+ + + + diff --git a/apps/playground-weapp-preview/package.json b/apps/playground-weapp-preview/package.json new file mode 100644 index 00000000..7c3fe10f --- /dev/null +++ b/apps/playground-weapp-preview/package.json @@ -0,0 +1,28 @@ +{ + "name": "@varo/playground-weapp-preview", + "type": "module", + "version": "0.1.0", + "private": true, + "scripts": { + "prepare:artifacts": "pnpm exec turbo run build --filter=@varo/playground-weapp", + "dev": "pnpm run prepare:artifacts && vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview --host 127.0.0.1", + "smoke:browser": "node scripts/smoke-preview.mjs", + "typecheck": "vue-tsc -p tsconfig.json --noEmit", + "test": "vitest run --passWithNoTests" + }, + "dependencies": { + "@varo-ui/h5": "workspace:*", + "@varo/weapp-web": "workspace:*", + "vue": "^3.5.42" + }, + "devDependencies": { + "@playwright/test": "^1.61.1", + "@varo/playground-weapp": "workspace:*", + "@vitejs/plugin-vue": "^6.0.8", + "typescript": "^6.0.3", + "vite": "^8.2.2", + "vue-tsc": "^3.3.11" + } +} diff --git a/apps/playground-weapp-preview/runtime.html b/apps/playground-weapp-preview/runtime.html new file mode 100644 index 00000000..f998b828 --- /dev/null +++ b/apps/playground-weapp-preview/runtime.html @@ -0,0 +1,13 @@ + + + + + + Varo 小程序 Web 兼容预览 + + +
正在加载小程序编译产物…
+
+ + + diff --git a/apps/playground-weapp-preview/scripts/smoke-preview.mjs b/apps/playground-weapp-preview/scripts/smoke-preview.mjs new file mode 100644 index 00000000..0e5d984a --- /dev/null +++ b/apps/playground-weapp-preview/scripts/smoke-preview.mjs @@ -0,0 +1,179 @@ +import assert from 'node:assert/strict' +import { mkdtemp } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { chromium, expect } from '@playwright/test' + +const baseUrl = process.env.PREVIEW_URL ?? 'http://127.0.0.1:4182/' +const artifacts = await mkdtemp(join(tmpdir(), 'varo-preview-smoke-')) +const browser = await chromium.launch({ channel: 'chrome', headless: true }) +const errors = [] +const checks = [] +const field = (root, name) => root.locator(`[data-preview-field="${name}"]`) +async function selectRadio(page, name) { + const radio = page.getByRole('radio', { name }) + await page.locator('label').filter({ has: radio }).click() + await expect(radio).toBeChecked() +} + +try { + const context = await browser.newContext({ viewport: { width: 1440, height: 1000 }, deviceScaleFactor: 1 }) + const page = await context.newPage() + page.on('pageerror', error => errors.push(error.message)) + await page.goto(baseUrl) + await expect(page.locator('.preview-frame')).toHaveAttribute('data-state', 'ready') + let runtime = page.frameLocator('iframe') + + const testButton = runtime.getByRole('button', { name: /^测试按钮:/ }) + await testButton.click() + await expect(field(runtime, 'button-state')).toHaveAttribute('data-preview-value', 'state=enabled;clicks=1;events=1') + await testButton.focus() + await page.keyboard.press('Enter') + await expect(field(runtime, 'button-state')).toHaveAttribute('data-preview-value', 'state=enabled;clicks=2;events=2') + await runtime.getByRole('button', { name: '禁用测试按钮', exact: true }).click() + await expect(testButton).toBeDisabled() + const disabledPoint = await page.evaluate(() => { + const frame = document.querySelector('iframe') + const button = [...frame.contentDocument.querySelectorAll('wx-button > button')].find(node => node.textContent.includes('测试按钮:')) + const child = button.getBoundingClientRect() + const bounds = frame.getBoundingClientRect() + const scale = bounds.width / frame.contentWindow.innerWidth + return { x: bounds.left + (child.left + child.width / 2) * scale, y: bounds.top + (child.top + child.height / 2) * scale } + }) + await page.mouse.click(disabledPoint.x, disabledPoint.y) + await expect(field(runtime, 'button-state')).toHaveAttribute('data-preview-value', 'state=disabled;clicks=2;events=2') + await expect(field(runtime, 'disabled-button-event-count')).toHaveText(/:0$/) + await runtime.getByRole('button', { name: '启用测试按钮', exact: true }).click() + checks.push('button pointer, keyboard, disabled state and single emitted events') + + const input = runtime.getByRole('textbox', { name: 'Web 预览受控输入' }) + await input.fill('Preview') + await input.pressSequentially('123') + await expect(input).toHaveValue('Preview123') + await expect(field(runtime, 'input-value')).toHaveText('受控值:Preview123') + await expect(field(runtime, 'input-state')).toContainText('focus / blur 事件:1 / 0') + await runtime.getByRole('button', { name: '外部重置值', exact: true }).click() + await expect(input).toHaveValue('外部重置值 #1') + await expect(field(runtime, 'input-state')).toContainText('focus / blur 事件:1 / 1') + await runtime.getByRole('button', { name: '请求聚焦', exact: true }).click() + await expect(input).toBeFocused() + await runtime.getByRole('button', { name: '移除焦点', exact: true }).click() + await expect(input).not.toBeFocused() + await expect(field(runtime, 'input-state')).toContainText('focus / blur 事件:2 / 2') + checks.push('controlled typing, external replacement and exact focus/blur delivery') + + await expect(field(runtime, 'context-lifecycle')).toHaveAttribute('data-preview-value', 'tree=mounted;provider=1/0;consumer=1/0') + await runtime.getByRole('button', { name: '更新注入值', exact: true }).click() + await expect(field(runtime, 'injected-context')).toHaveAttribute('data-preview-value', 'revision=2;value=来自 Provider 的值 #2') + await runtime.getByRole('button', { name: '卸载 Provider / Consumer', exact: true }).click() + await expect(field(runtime, 'injected-context')).toHaveCount(0) + await expect(field(runtime, 'context-lifecycle')).toHaveAttribute('data-preview-value', 'tree=unmounted;provider=1/1;consumer=1/1') + await runtime.getByRole('button', { name: '重新挂载 Provider / Consumer', exact: true }).click() + await expect(field(runtime, 'context-lifecycle')).toHaveAttribute('data-preview-value', 'tree=mounted;provider=2/1;consumer=2/1') + await expect(field(runtime, 'injected-context')).toHaveAttribute('data-preview-value', 'revision=2;value=来自 Provider 的值 #2') + checks.push('slot context propagation and exact detach/remount lifetimes') + await page.screenshot({ path: join(artifacts, 'desktop.png'), fullPage: true }) + + await page.setViewportSize({ width: 320, height: 844 }) + await selectRadio(page, /宽屏.*430 px/) + await expect.poll(() => page.evaluate(() => document.querySelector('iframe').contentWindow.innerWidth)).toBe(430) + const fit = await page.evaluate(() => { + const frame = document.querySelector('iframe').getBoundingClientRect() + const stage = document.querySelector('.preview-frame__stage').getBoundingClientRect() + return { width: innerWidth, scroll: document.documentElement.scrollWidth, frameLeft: frame.left, frameRight: frame.right, stageLeft: stage.left, stageRight: stage.right } + }) + assert.ok(fit.scroll <= fit.width && fit.frameLeft >= fit.stageLeft - 1 && fit.frameRight <= fit.stageRight + 1) + await expect(field(runtime, 'button-state')).toHaveAttribute('data-preview-value', 'state=enabled;clicks=2;events=2') + await page.screenshot({ path: join(artifacts, 'mobile.png'), fullPage: true }) + checks.push('narrow layout fits while device width changes preserve native state') + + await page.evaluate(() => { + const frame = document.querySelector('iframe') + const query = new URL(frame.src).searchParams + window.postMessage({ channel: 'varo-weapp-preview', type: 'error', scenario: 'controls', session: query.get('session'), message: 'foreign-window-message' }, location.origin) + }) + const currentFrame = page.frames().find(frame => frame.url().includes('runtime.html')) + assert.ok(currentFrame) + await currentFrame.evaluate(() => window.parent.postMessage({ channel: 'varo-weapp-preview', type: 'error', scenario: 'controls', session: 'stale-session', message: 'stale-message' }, location.origin)) + await expect(page.locator('.preview-frame')).toHaveAttribute('data-state', 'ready') + await currentFrame.evaluate(() => window.dispatchEvent(new ErrorEvent('error', { message: 'preview-smoke-intentional', error: new Error('preview-smoke-intentional') }))) + await expect(page.locator('.preview-frame')).toHaveAttribute('data-state', 'error') + await page.getByRole('button', { name: '重置并重试', exact: true }).click() + await expect(page.locator('.preview-frame')).toHaveAttribute('data-state', 'ready') + runtime = page.frameLocator('iframe') + await expect(field(runtime, 'button-state')).toHaveAttribute('data-preview-value', 'state=enabled;clicks=0;events=0') + checks.push('foreign/stale messages rejected and real runtime error boundary recovers on reset') + + await page.setViewportSize({ width: 1440, height: 1000 }) + await selectRadio(page, /^Agent 内容/) + await expect(page.locator('.preview-frame')).toHaveAttribute('data-state', 'ready') + runtime = page.frameLocator('iframe') + await runtime.getByRole('link', { name: 'Varo 预览链接', exact: true }).focus() + await page.keyboard.press('Enter') + await expect(field(runtime, 'markdown-link-output')).toContainText('https://example.com/varo-preview') + await expect(field(runtime, 'markdown-link-count')).toHaveText(/:1$/) + await runtime.getByRole('button', { name: '开始流式内容', exact: true }).click() + await expect(field(runtime, 'stream-phase')).toHaveText(/streaming/) + await runtime.getByRole('button', { name: '停止', exact: true }).click() + await expect(field(runtime, 'stream-phase')).toHaveText(/stopped/) + const stopped = await runtime.locator('.agent-stream').textContent() + await new Promise(resolve => setTimeout(resolve, 350)) + assert.equal(await runtime.locator('.agent-stream').textContent(), stopped) + await runtime.getByRole('button', { name: '继续流式内容', exact: true }).click() + await expect(field(runtime, 'stream-state')).toHaveAttribute('data-preview-value', 'phase=completed;chunks=14/14;pending=0;final=true') + await expect(runtime.locator('.agent-stream')).toContainText('controller.finish()') + await runtime.getByRole('button', { name: '从头重播', exact: true }).click() + await expect(field(runtime, 'stream-state')).toHaveAttribute('data-preview-value', 'phase=completed;chunks=14/14;pending=0;final=true') + checks.push('native Markdown link, stream stop/continue/replay and final Markdown content') + + const native = await context.newPage() + native.on('pageerror', error => errors.push(error.message)) + await native.setViewportSize({ width: 390, height: 844 }) + await native.goto(new URL('runtime.html?scenario=agent', baseUrl).href) + await expect(native.locator('#runtime-status')).toBeHidden() + const presentation = await native.evaluate(() => { + const cards = [...document.querySelectorAll('wx-view.varo-card')].map(node => ({ width: node.clientWidth, scroll: node.scrollWidth })) + const headers = [...document.querySelectorAll('wx-view')].filter(node => ['Surface', 'Runtime'].includes(node.textContent.trim())).map(node => ({ color: getComputedStyle(node).color, background: getComputedStyle(node).backgroundColor })) + return { cards, headers, width: innerWidth, scroll: document.documentElement.scrollWidth } + }) + assert.ok(presentation.cards.length === 2 && presentation.cards.every(card => card.scroll <= card.width + 1)) + assert.ok(presentation.headers.length === 2 && presentation.headers.every(header => header.color !== header.background)) + assert.ok(presentation.scroll <= presentation.width) + await expect(native.getByText('Surface', { exact: true })).toBeVisible() + await expect(native.getByText('Wevu 原生组件树', { exact: true })).toBeVisible() + await native.screenshot({ path: join(artifacts, 'native-agent.png'), fullPage: true }) + checks.push('native Markdown table contrast and contained horizontal scrolling') + + await selectRadio(page, /^原生地图/) + await expect(page.locator('.preview-frame')).toHaveAttribute('data-state', 'ready') + runtime = page.frameLocator('iframe') + await expect(runtime.locator('wx-map img.varo-native-map__tile').first()).toBeVisible({ timeout: 15000 }) + await expect(runtime.locator('wx-map img.varo-native-map__tile').first()).toHaveAttribute('src', /rt\d\.map\.gtimg\.com\/tile/) + await expect(field(runtime, 'map-center')).toContainText('杭州西湖') + await runtime.locator('wx-map').click() + await expect(field(runtime, 'map-region-count')).toContainText('1') + await runtime.getByRole('button', { name: '切换到上海' }).click() + await expect(field(runtime, 'map-center')).toContainText('上海外滩') + await page.screenshot({ path: join(artifacts, 'native-map.png'), fullPage: true }) + checks.push('native map Tencent raster tiles, GL controls, marker center and regionchange') + + await selectRadio(page, /^机器人对话/) + await expect(page.locator('.preview-frame')).toHaveAttribute('data-state', 'ready') + runtime = page.frameLocator('iframe') + await expect(runtime.locator('wx-wechat-robot-chat .varo-native-robot-chat__message--assistant').first()).toContainText('你好') + await expect(field(runtime, 'robot-status')).toContainText('机器人已连接') + await runtime.getByRole('textbox', { name: '对话内容' }).fill('查订单') + await runtime.getByRole('button', { name: '发送', exact: true }).click() + await expect(runtime.locator('wx-wechat-robot-chat .varo-native-robot-chat__message--user')).toContainText('查订单') + await expect(field(runtime, 'robot-last-query')).toContainText('查订单') + await expect(field(runtime, 'robot-query-count')).toContainText('1') + await page.screenshot({ path: join(artifacts, 'native-robot-chat.png'), fullPage: true }) + checks.push('native robot chat welcome, operateCard send and queryCallback') + + assert.deepEqual(errors.filter(message => !message.includes('preview-smoke-intentional')), []) + console.log(JSON.stringify({ status: 'passed', url: baseUrl, browser: browser.version(), checks, artifacts }, null, 2)) + await context.close() +} +finally { + await browser.close() +} diff --git a/apps/playground-weapp-preview/src/App.vue b/apps/playground-weapp-preview/src/App.vue new file mode 100644 index 00000000..fbe29f19 --- /dev/null +++ b/apps/playground-weapp-preview/src/App.vue @@ -0,0 +1,344 @@ + + + + + diff --git a/apps/playground-weapp-preview/src/components/PreviewFrame.vue b/apps/playground-weapp-preview/src/components/PreviewFrame.vue new file mode 100644 index 00000000..d5003bcd --- /dev/null +++ b/apps/playground-weapp-preview/src/components/PreviewFrame.vue @@ -0,0 +1,395 @@ + + +