@tbox.cn/app-template-agent 0.7.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore +5 -0
- package/.tbox/app.json +9 -1
- package/AGENTS.md +12 -4
- package/ENV.md +102 -17
- package/README.md +36 -34
- package/apps/client/VISUAL_RESTORE.md +11 -11
- package/apps/client/index.html +3 -1
- package/apps/client/package.json +9 -8
- package/apps/client/src/business/App.tsx +4 -4
- package/apps/client/src/business/README.md +5 -11
- package/apps/client/src/cards/registry.ts +23 -8
- package/apps/client/src/main.tsx +36 -2
- package/apps/client/src/modules.ts +7 -4
- package/apps/client/src/pages/index/index.tsx +18 -162
- package/apps/client/src/platform/AgentChatApp.tsx +3 -3
- package/apps/client/src/platform/agent-runtime.ts +5 -0
- package/apps/client/src/store/chat.ts +47 -185
- package/apps/client/src/test/setup.ts +7 -0
- package/apps/client/vite.config.ts +16 -1
- package/apps/server/package.json +0 -1
- package/apps/server/src/app-tools.ts +2 -1
- package/apps/server/src/app.ts +178 -110
- package/apps/server/src/config/integration.ts +2 -6
- package/apps/server/src/dev.ts +0 -1
- package/apps/server/src/intent-routes.ts +3 -3
- package/apps/server/src/modules.ts +4 -4
- package/apps/server/src/pre-tools.ts +4 -3
- package/apps/server/tests/agent-prompt.test.ts +81 -0
- package/apps/server/tests/cards-registry.test.ts +1 -3
- package/apps/server/tests/deployment-loader.test.ts +35 -56
- package/apps/server/tests/tool-runtime.test.ts +25 -61
- package/package.json +5 -4
- package/packages/agent-ui-kit/LICENSE +21 -0
- package/packages/agent-ui-kit/README.md +17 -0
- package/packages/agent-ui-kit/package.json +58 -0
- package/packages/agent-ui-kit/src/ChatPage.less +62 -0
- package/packages/agent-ui-kit/src/ChatPage.tsx +305 -0
- package/packages/agent-ui-kit/src/chat-config.test.ts +82 -0
- package/packages/agent-ui-kit/src/chat-config.ts +62 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/ActionBar/DebugButton.tsx +2 -2
- package/{apps/client → packages/agent-ui-kit}/src/components/ActionBar/index.less +19 -16
- package/{apps/client → packages/agent-ui-kit}/src/components/ActionBar/index.tsx +10 -13
- package/packages/agent-ui-kit/src/components/CardRenderer.tsx +36 -0
- package/packages/agent-ui-kit/src/components/ChatHeader/index.less +148 -0
- package/packages/agent-ui-kit/src/components/ChatHeader/index.test.tsx +74 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/ChatHeader/index.tsx +27 -15
- package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/index.less +47 -39
- package/packages/agent-ui-kit/src/components/ChatInput/index.test.tsx +156 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/index.tsx +13 -4
- package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/voice/useVoicePress.ts +2 -2
- package/packages/agent-ui-kit/src/components/HistoryEntry/index.less +49 -0
- package/packages/agent-ui-kit/src/components/HistoryEntry/index.tsx +42 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/MediaList/index.less +3 -3
- package/{apps/client → packages/agent-ui-kit}/src/components/MediaList/index.tsx +6 -6
- package/{apps/client → packages/agent-ui-kit}/src/components/MessageBubble/index.less +25 -20
- package/packages/agent-ui-kit/src/components/MessageBubble/index.test.tsx +112 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/MessageBubble/index.tsx +22 -15
- package/{apps/client → packages/agent-ui-kit}/src/components/PullDownIndicator/index.less +2 -2
- package/{apps/client → packages/agent-ui-kit}/src/components/PullDownIndicator/index.tsx +2 -2
- package/packages/agent-ui-kit/src/components/QuickCommands/index.less +63 -0
- package/packages/agent-ui-kit/src/components/QuickCommands/index.test.tsx +99 -0
- package/packages/agent-ui-kit/src/components/QuickCommands/index.tsx +52 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/ScrollToBottomButton/index.less +1 -1
- package/packages/agent-ui-kit/src/components/ToolCallBlock/detail.test.ts +346 -0
- package/packages/agent-ui-kit/src/components/ToolCallBlock/detail.ts +204 -0
- package/packages/agent-ui-kit/src/components/ToolCallBlock/index.less +312 -0
- package/packages/agent-ui-kit/src/components/ToolCallBlock/index.test.tsx +261 -0
- package/packages/agent-ui-kit/src/components/ToolCallBlock/index.tsx +212 -0
- package/packages/agent-ui-kit/src/components/ToolCallBlock/titles.ts +102 -0
- package/packages/agent-ui-kit/src/hooks/use-drag-gesture.test.ts +66 -0
- package/packages/agent-ui-kit/src/hooks/use-drag-gesture.ts +129 -0
- package/packages/agent-ui-kit/src/hooks/use-pull-down-history.ts +241 -0
- package/packages/agent-ui-kit/src/hooks/use-pull-down-wheel-native.test.ts +64 -0
- package/{apps/client → packages/agent-ui-kit}/src/hooks/useMediaUpload.ts +3 -3
- package/{apps/client → packages/agent-ui-kit}/src/hooks/useTTSAutoPlay.ts +7 -5
- package/packages/agent-ui-kit/src/index.ts +69 -0
- package/packages/agent-ui-kit/src/store-context.tsx +92 -0
- package/packages/agent-ui-kit/src/test/setup.ts +7 -0
- package/packages/agent-ui-kit/src/theme/contract.json +72 -0
- package/packages/agent-ui-kit/src/theme/global.less +99 -0
- package/packages/agent-ui-kit/src/theme/motion.less +19 -0
- package/packages/agent-ui-kit/src/theme/theme-contract.test.ts +69 -0
- package/packages/agent-ui-kit/src/theme/theme.ts +17 -0
- package/packages/agent-ui-kit/src/theme/tokens.less +79 -0
- package/packages/agent-ui-kit/src/utils/haptics.test.ts +51 -0
- package/packages/agent-ui-kit/src/utils/haptics.ts +44 -0
- package/packages/agent-ui-kit/src/utils/pull-down-machine.test.ts +188 -0
- package/packages/agent-ui-kit/src/utils/pull-down-machine.ts +157 -0
- package/packages/agent-ui-kit/src/utils/tool-call-display.test.ts +240 -0
- package/packages/agent-ui-kit/src/utils/tool-call-display.ts +146 -0
- package/packages/agent-ui-kit/src/vite-env.d.ts +1 -0
- package/packages/agent-ui-kit/tsconfig.json +8 -0
- package/packages/agent-ui-kit/vitest.config.ts +7 -0
- package/pnpm-lock.template.yaml +177 -83
- package/pnpm-workspace.template.yaml +12 -4
- package/profiles/mall/config/agent.jsonc +33 -0
- package/profiles/mall/config/chat.json +21 -0
- package/profiles/mall/config/knowledge.json +5 -0
- package/profiles/mall/config/llm_provider.json +5 -0
- package/profiles/mall/config/plugins.json +3 -0
- package/profiles/mall.json +16 -0
- package/scripts/check-workspace.mjs +3 -2
- package/apps/client/assets/copy@2x.png +0 -0
- package/apps/client/assets/debug.png +0 -0
- package/apps/client/assets/dislike@2x.png +0 -0
- package/apps/client/assets/like@2x.png +0 -0
- package/apps/client/assets/speaker@2x.png +0 -0
- package/apps/client/src/cards/CardRenderer.tsx +0 -20
- package/apps/client/src/components/ChatHeader/index.less +0 -104
- package/apps/client/src/components/ErrorBoundary.tsx +0 -2
- package/apps/client/src/components/QuickCommands/index.less +0 -48
- package/apps/client/src/components/QuickCommands/index.tsx +0 -29
- package/apps/client/src/components/ToolCallBlock/index.less +0 -131
- package/apps/client/src/components/ToolCallBlock/index.tsx +0 -125
- package/apps/client/src/examples/tourism/TourismLanding.tsx +0 -96
- package/apps/client/src/examples/tourism/index.less +0 -192
- package/apps/client/src/pages/index/index.less +0 -43
- package/apps/client/src/store/chat-config.ts +0 -40
- package/apps/client/src/styles/global.less +0 -60
- package/apps/client/src/styles/tokens.less +0 -60
- package/apps/server/src/config/deployment.ts +0 -47
- package/packages/module-weather/package.json +0 -36
- package/packages/module-weather/skills/weather-guide/SKILL.md +0 -22
- package/packages/module-weather/skills/weather-guide/references/dressing-tips.md +0 -5
- package/packages/module-weather/src/client/cards/weather/index.less +0 -22
- package/packages/module-weather/src/client/cards/weather/index.tsx +0 -14
- package/packages/module-weather/src/client/index.ts +0 -19
- package/packages/module-weather/src/index.ts +0 -2
- package/packages/module-weather/src/server/cards/weather/meta.ts +0 -27
- package/packages/module-weather/src/server/index.ts +0 -45
- package/packages/module-weather/src/server/pre-tools.ts +0 -20
- package/packages/module-weather/src/server/tool.ts +0 -29
- package/packages/module-weather/tbox.module.json +0 -27
- package/packages/module-weather/tsconfig.json +0 -14
- /package/{apps/client → packages/agent-ui-kit}/assets/keyboard-circle.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/more-circle.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/new-chat.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/quick-command-star.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/send-gradient.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/stop@2x.png +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/voice-circle.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/voice/VoicePressField.tsx +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/voice/VoiceRecordingOverlay.tsx +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/voice/machine.ts +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ReasoningBlock/index.less +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ReasoningBlock/index.tsx +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ScrollToBottomButton/index.tsx +0 -0
- /package/{apps/client/src/types/chat.ts → packages/agent-ui-kit/src/types.ts} +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/utils/copyText.ts +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/utils/imagePreview.ts +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/utils/session-error.ts +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/utils/toast.ts +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "mall 画像(单一模板统一化 W4):create --profile mall 执行器(W5 接线)按 modules 序列加装 + configOverlay 叠加(--config-dir 通道)。provider 轴独立(缺省 mock-only 演示态;vendor 档由部署者后续 add)。",
|
|
3
|
+
"name": "mall",
|
|
4
|
+
"description": "商圈 AI Agent:家页形态(home 槽)+ 域装配 + 六业务域(导购/会员/停车/营销/活动/客服)",
|
|
5
|
+
"modules": [
|
|
6
|
+
"@tbox.cn/app-module-mall-core",
|
|
7
|
+
"@tbox.cn/app-module-mall-home",
|
|
8
|
+
"@tbox.cn/app-module-shopping-guide",
|
|
9
|
+
"@tbox.cn/app-module-member",
|
|
10
|
+
"@tbox.cn/app-module-parking",
|
|
11
|
+
"@tbox.cn/app-module-promotion",
|
|
12
|
+
"@tbox.cn/app-module-alipay-activity",
|
|
13
|
+
"@tbox.cn/app-module-customer-service"
|
|
14
|
+
],
|
|
15
|
+
"configOverlay": "profiles/mall/config"
|
|
16
|
+
}
|
|
@@ -100,9 +100,10 @@ function printFailure(findings) {
|
|
|
100
100
|
process.exitCode = 1;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
/** 豁免:SDK
|
|
103
|
+
/** 豁免:SDK/契约/UI-Kit 包无 tbox.module.json(002 §2.3 / 003 §4.1;kit 为壳 UI 源码包,
|
|
104
|
+
* 单一模板统一化 W2-5——无模块贡献面,非业务模块) */
|
|
104
105
|
function isExemptPackage(dirName) {
|
|
105
|
-
return dirName === 'app-sdk' || dirName === 'shared' || dirName.startsWith('contracts');
|
|
106
|
+
return dirName === 'app-sdk' || dirName === 'shared' || dirName.startsWith('contracts') || dirName === 'agent-ui-kit';
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
/** 校验 packages/* 每个业务包必须有 tbox.module.json(002 §2.3 / 003 §4.1) */
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 卡片渲染入口 — 注入本地 registry 后委托 SDK 的 CardRenderer。
|
|
3
|
-
* 011 B3:动作按钮 → sendUiAction(token 来自 payload.actionTokens)。
|
|
4
|
-
* P1:onAction 升级为完整 UiAction(CardRenderer 按 kind 分发后回传,桥一行透传)。
|
|
5
|
-
*/
|
|
6
|
-
import { CardRenderer as SdkCardRenderer } from '@tbox.cn/app-sdk/client';
|
|
7
|
-
import type { TboxCardPayload, UiAction } from '@tbox.cn/app-contracts';
|
|
8
|
-
import { CARD_REGISTRY } from './registry';
|
|
9
|
-
import { useChatStore } from '@/store/chat';
|
|
10
|
-
|
|
11
|
-
export function CardRenderer({ card }: { card: TboxCardPayload }) {
|
|
12
|
-
const sendUiAction = useChatStore((s) => s.sendUiAction);
|
|
13
|
-
return (
|
|
14
|
-
<SdkCardRenderer
|
|
15
|
-
card={card}
|
|
16
|
-
registry={CARD_REGISTRY}
|
|
17
|
-
onAction={(action: UiAction) => sendUiAction(card.id, action)}
|
|
18
|
-
/>
|
|
19
|
-
);
|
|
20
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/* 顶部居中浮动控件:毛玻璃按钮组 + 下方小手柄;折叠时只剩手柄。对齐原模板。 */
|
|
2
|
-
.chat-header {
|
|
3
|
-
position: absolute;
|
|
4
|
-
top: 4px;
|
|
5
|
-
left: 0;
|
|
6
|
-
right: 0;
|
|
7
|
-
z-index: 100;
|
|
8
|
-
display: flex;
|
|
9
|
-
flex-direction: column;
|
|
10
|
-
align-items: center;
|
|
11
|
-
padding: 4px 16px 0;
|
|
12
|
-
pointer-events: none;
|
|
13
|
-
|
|
14
|
-
&__group {
|
|
15
|
-
pointer-events: auto;
|
|
16
|
-
display: flex;
|
|
17
|
-
align-items: center;
|
|
18
|
-
justify-content: center;
|
|
19
|
-
gap: 8px;
|
|
20
|
-
margin-bottom: 8px;
|
|
21
|
-
padding: 12px 16px;
|
|
22
|
-
background: rgba(255, 255, 255, 0.4);
|
|
23
|
-
backdrop-filter: blur(10px);
|
|
24
|
-
border-radius: 31px;
|
|
25
|
-
max-height: 100px;
|
|
26
|
-
overflow: hidden;
|
|
27
|
-
opacity: 1;
|
|
28
|
-
transform: translateY(0) scale(1);
|
|
29
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
30
|
-
|
|
31
|
-
&--collapsed {
|
|
32
|
-
opacity: 0;
|
|
33
|
-
transform: translateY(-20px) scale(0.95);
|
|
34
|
-
max-height: 0;
|
|
35
|
-
margin-bottom: 0;
|
|
36
|
-
padding-top: 0;
|
|
37
|
-
padding-bottom: 0;
|
|
38
|
-
pointer-events: none;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
&__btn {
|
|
43
|
-
display: flex;
|
|
44
|
-
align-items: center;
|
|
45
|
-
gap: 8px;
|
|
46
|
-
appearance: none;
|
|
47
|
-
border: none;
|
|
48
|
-
padding: 12px 24px;
|
|
49
|
-
background: #fff;
|
|
50
|
-
border-radius: 92px;
|
|
51
|
-
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
52
|
-
transition: all 0.2s ease;
|
|
53
|
-
|
|
54
|
-
&:active {
|
|
55
|
-
transform: scale(0.98);
|
|
56
|
-
background: #f5f5f5;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
&__btn-icon {
|
|
61
|
-
width: 32px;
|
|
62
|
-
height: 32px;
|
|
63
|
-
flex-shrink: 0;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
&__btn-text {
|
|
67
|
-
font-size: 24px;
|
|
68
|
-
color: #4b6282;
|
|
69
|
-
font-weight: 500;
|
|
70
|
-
white-space: nowrap;
|
|
71
|
-
line-height: 1;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
&__toggle {
|
|
75
|
-
pointer-events: auto;
|
|
76
|
-
appearance: none;
|
|
77
|
-
border: none;
|
|
78
|
-
display: flex;
|
|
79
|
-
align-items: center;
|
|
80
|
-
justify-content: center;
|
|
81
|
-
width: 110px;
|
|
82
|
-
height: 30px;
|
|
83
|
-
background: rgba(255, 255, 255, 0.8);
|
|
84
|
-
border-radius: 14px;
|
|
85
|
-
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
86
|
-
transition: all 0.2s ease;
|
|
87
|
-
|
|
88
|
-
&:active {
|
|
89
|
-
transform: scale(0.98);
|
|
90
|
-
background: #f5f5f5;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
&__arrow {
|
|
95
|
-
display: flex;
|
|
96
|
-
align-items: center;
|
|
97
|
-
justify-content: center;
|
|
98
|
-
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
99
|
-
|
|
100
|
-
&--up {
|
|
101
|
-
transform: rotate(180deg);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
.quick-commands {
|
|
2
|
-
position: relative;
|
|
3
|
-
z-index: 1;
|
|
4
|
-
width: 100%;
|
|
5
|
-
max-width: var(--tbox-content-max-width);
|
|
6
|
-
margin: 0 auto;
|
|
7
|
-
box-sizing: border-box;
|
|
8
|
-
display: flex;
|
|
9
|
-
gap: 16px;
|
|
10
|
-
overflow-x: auto;
|
|
11
|
-
padding: 0 24px 12px;
|
|
12
|
-
-ms-overflow-style: none;
|
|
13
|
-
scrollbar-width: none;
|
|
14
|
-
|
|
15
|
-
&::-webkit-scrollbar {
|
|
16
|
-
display: none;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
&__item {
|
|
20
|
-
flex-shrink: 0;
|
|
21
|
-
display: inline-flex;
|
|
22
|
-
align-items: center;
|
|
23
|
-
gap: 8px;
|
|
24
|
-
height: 61px;
|
|
25
|
-
padding: 0 24px 0 16px;
|
|
26
|
-
appearance: none;
|
|
27
|
-
border: none;
|
|
28
|
-
background: #fff;
|
|
29
|
-
border-radius: 31px;
|
|
30
|
-
box-shadow: 0 2px 12px rgba(110, 139, 180, 0.12);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
&__icon {
|
|
34
|
-
width: 32px;
|
|
35
|
-
height: 32px;
|
|
36
|
-
object-fit: contain;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
&__icon-svg {
|
|
40
|
-
color: var(--tbox-primary);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
&__label {
|
|
44
|
-
font-size: 26px;
|
|
45
|
-
color: rgba(31, 59, 99, 0.85);
|
|
46
|
-
white-space: nowrap;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// ⚠️ 平台标准组件:输入框上方的「快捷指令」。视觉还原时不改本组件、不另画。
|
|
2
|
-
// 改内容只改 client/config/chat.json 的 quickCommands 数组({ label, query, icon? })。
|
|
3
|
-
// 011 I2:服务端 HelloAck.chatConfig.quickCommands 优先(单一真源),本地 chat.json 为 fallback。
|
|
4
|
-
// label 只写纯文字;前面的星标由本组件渲染(icon 字段或默认 ✦),不要把 ✦/emoji 写进 label。
|
|
5
|
-
import { useChatConfigStore } from '@/store/chat-config';
|
|
6
|
-
import { useChatStore } from '@/store/chat';
|
|
7
|
-
import starIcon from '../../../assets/quick-command-star.svg';
|
|
8
|
-
import './index.less';
|
|
9
|
-
|
|
10
|
-
export function QuickCommands({ onCommand }: { onCommand: (query: string) => void }) {
|
|
11
|
-
const staticCommands = useChatConfigStore((s) => s.quickCommands);
|
|
12
|
-
const serverCommands = useChatStore((s) => s.helloAck?.chatConfig.quickCommands);
|
|
13
|
-
// 服务端下发优先;未收到 HELLO_ACK(非 mall 部署)回落本地静态
|
|
14
|
-
const commands = (serverCommands && serverCommands.length > 0
|
|
15
|
-
? serverCommands.map((c) => ({ label: c.label, query: c.prompt }))
|
|
16
|
-
: staticCommands) as { label: string; query: string }[];
|
|
17
|
-
if (!commands.length) return null;
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div className="quick-commands">
|
|
21
|
-
{commands.map((cmd) => (
|
|
22
|
-
<button key={cmd.label} className="quick-commands__item" onClick={() => onCommand(cmd.query)}>
|
|
23
|
-
<img className="quick-commands__icon" src={starIcon} alt="" />
|
|
24
|
-
<span className="quick-commands__label">{cmd.label}</span>
|
|
25
|
-
</button>
|
|
26
|
-
))}
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
.tool-calls {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
gap: 8px;
|
|
5
|
-
margin: 0 0 12px;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.tool-call {
|
|
9
|
-
border-radius: 12px;
|
|
10
|
-
overflow: hidden;
|
|
11
|
-
|
|
12
|
-
&--calling {
|
|
13
|
-
background: rgba(16, 163, 127, 0.08);
|
|
14
|
-
}
|
|
15
|
-
&--done {
|
|
16
|
-
background: rgba(0, 0, 0, 0.03);
|
|
17
|
-
}
|
|
18
|
-
&--error {
|
|
19
|
-
background: rgba(239, 68, 68, 0.08);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
&__header {
|
|
23
|
-
display: flex;
|
|
24
|
-
align-items: center;
|
|
25
|
-
justify-content: space-between;
|
|
26
|
-
padding: 12px 16px;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
&__left {
|
|
30
|
-
display: flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
gap: 8px;
|
|
33
|
-
min-width: 0;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
&__spinner {
|
|
37
|
-
width: 14px;
|
|
38
|
-
height: 14px;
|
|
39
|
-
border: 2px solid rgba(16, 163, 127, 0.3);
|
|
40
|
-
border-top-color: #10a37f;
|
|
41
|
-
border-radius: 50%;
|
|
42
|
-
flex-shrink: 0;
|
|
43
|
-
animation: tool-spin 0.8s linear infinite;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 010:前置工具「自动」徽标(与模型主动调用区分)
|
|
47
|
-
&__badge {
|
|
48
|
-
font-size: 18px;
|
|
49
|
-
line-height: 1;
|
|
50
|
-
padding: 3px 8px;
|
|
51
|
-
border-radius: 8px;
|
|
52
|
-
background: rgba(93, 126, 198, 0.12);
|
|
53
|
-
color: #5f7ec6;
|
|
54
|
-
flex-shrink: 0;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
&__name {
|
|
58
|
-
font-size: 22px;
|
|
59
|
-
line-height: 1.4;
|
|
60
|
-
color: #4a5568;
|
|
61
|
-
font-weight: 500;
|
|
62
|
-
overflow: hidden;
|
|
63
|
-
text-overflow: ellipsis;
|
|
64
|
-
white-space: nowrap;
|
|
65
|
-
}
|
|
66
|
-
&--calling &__name {
|
|
67
|
-
color: #10a37f;
|
|
68
|
-
}
|
|
69
|
-
&--error &__name {
|
|
70
|
-
color: #ef4444;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
&__toggle {
|
|
74
|
-
font-size: 20px;
|
|
75
|
-
color: #8b95b0;
|
|
76
|
-
flex-shrink: 0;
|
|
77
|
-
margin-left: 8px;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
&__result {
|
|
81
|
-
padding: 0 16px 12px;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
&__result-text {
|
|
85
|
-
font-size: 20px;
|
|
86
|
-
color: #6b7a99;
|
|
87
|
-
line-height: 1.6;
|
|
88
|
-
font-family: Menlo, Monaco, Consolas, monospace;
|
|
89
|
-
white-space: pre-wrap;
|
|
90
|
-
word-break: break-all;
|
|
91
|
-
|
|
92
|
-
&--error {
|
|
93
|
-
color: #dc2626;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// 018 dev 视图输入/输出双栏(紧凑单行 + 头尾保留)
|
|
98
|
-
&__io {
|
|
99
|
-
display: flex;
|
|
100
|
-
flex-direction: column;
|
|
101
|
-
gap: 4px;
|
|
102
|
-
|
|
103
|
-
& + & {
|
|
104
|
-
margin-top: 12px;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
&__io-label {
|
|
109
|
-
font-size: 18px;
|
|
110
|
-
color: #8a97ad;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
&__io-text {
|
|
114
|
-
font-size: 20px;
|
|
115
|
-
color: #6b7a99;
|
|
116
|
-
line-height: 1.6;
|
|
117
|
-
font-family: Menlo, Monaco, Consolas, monospace;
|
|
118
|
-
white-space: pre-wrap;
|
|
119
|
-
word-break: break-all;
|
|
120
|
-
|
|
121
|
-
&--error {
|
|
122
|
-
color: #dc2626;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@keyframes tool-spin {
|
|
128
|
-
to {
|
|
129
|
-
transform: rotate(360deg);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { Check, X } from '@phosphor-icons/react';
|
|
3
|
-
import { parseMemoryToolCallArgs, parsePreToolToolCallArgs } from '@tbox.cn/app-contracts';
|
|
4
|
-
import type { ToolCallInfo } from '@/types/chat';
|
|
5
|
-
import { describeToolCall, formatCompactToolIo } from '@tbox.cn/app-sdk/client';
|
|
6
|
-
import './index.less';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 工具调用状态卡片(调用中 spinner / 完成 / 失败)。
|
|
10
|
-
* 010:展示语义判定下沉 SDK describeToolCall(skill/preTool/normal/memory 四分支),
|
|
11
|
-
* 本组件退化为纯渲染(徽标/颜色/展开交互)。
|
|
12
|
-
* 018 tool-debug:debug 门控在渲染层——prod 恒无出入参展开入口(I4);
|
|
13
|
-
* dev 详情走 formatCompactToolIo(紧凑单行 + 头尾保留),输入输出双栏。
|
|
14
|
-
*/
|
|
15
|
-
export default function ToolCallBlock({
|
|
16
|
-
toolCalls,
|
|
17
|
-
debug = false,
|
|
18
|
-
}: {
|
|
19
|
-
toolCalls: ToolCallInfo[];
|
|
20
|
-
/** 018 tool-debug:dev 视图(出入参展开 + 紧凑格式);prod 缺省 false */
|
|
21
|
-
debug?: boolean;
|
|
22
|
-
}) {
|
|
23
|
-
const [expandedId, setExpandedId] = useState<string | null>(null);
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div className="tool-calls">
|
|
27
|
-
{toolCalls.map((tc) => {
|
|
28
|
-
const display = describeToolCall(tc);
|
|
29
|
-
const isCalling = tc.status === 'calling';
|
|
30
|
-
const isError =
|
|
31
|
-
tc.status === 'error' ||
|
|
32
|
-
(display.kind === 'preTool' && display.status === 'error') ||
|
|
33
|
-
(display.kind === 'memory' && display.status === 'degraded');
|
|
34
|
-
const isDone = !isCalling && !isError;
|
|
35
|
-
const isExpanded = expandedId === tc.id;
|
|
36
|
-
// 018 I4:prod 恒无展开入口;dev 按 display.hasDetail
|
|
37
|
-
const hasDetail = debug && display.hasDetail;
|
|
38
|
-
const stateClass = isError ? 'tool-call--error' : isCalling ? 'tool-call--calling' : 'tool-call--done';
|
|
39
|
-
|
|
40
|
-
const displayName = (() => {
|
|
41
|
-
if (display.kind === 'skill') {
|
|
42
|
-
return isCalling ? `加载技能 ${display.label}` : `已加载技能 ${display.label}`;
|
|
43
|
-
}
|
|
44
|
-
if (display.kind === 'preTool') {
|
|
45
|
-
if (display.status === 'calling') return `自动查询 ${display.label}`;
|
|
46
|
-
if (display.status === 'error') return `自动查询失败 ${display.label}`;
|
|
47
|
-
return `已自动获取 ${display.label}`;
|
|
48
|
-
}
|
|
49
|
-
if (display.kind === 'memory') {
|
|
50
|
-
return display.label;
|
|
51
|
-
}
|
|
52
|
-
return isCalling
|
|
53
|
-
? `调用 ${display.label}`
|
|
54
|
-
: isDone
|
|
55
|
-
? `${display.label} 已完成`
|
|
56
|
-
: `${display.label} 调用失败`;
|
|
57
|
-
})();
|
|
58
|
-
|
|
59
|
-
// 详情文本(018:dev 态才计算——prod 无展开无渲染需求)
|
|
60
|
-
const detailText = (() => {
|
|
61
|
-
if (!hasDetail) return '';
|
|
62
|
-
if (display.kind === 'preTool') {
|
|
63
|
-
return (display.status === 'error' ? display.errorText : display.resultText) ?? '';
|
|
64
|
-
}
|
|
65
|
-
if (display.kind === 'memory') {
|
|
66
|
-
return display.detailText ?? '';
|
|
67
|
-
}
|
|
68
|
-
return isError ? (tc.error ?? '') : (tc.result ?? '');
|
|
69
|
-
})();
|
|
70
|
-
// dev 输入侧:normal 用 tc.args;preTool 解包 toolArgs(对齐 mall 视图模型);memory 取 query
|
|
71
|
-
const inputText = (() => {
|
|
72
|
-
if (!hasDetail) return '';
|
|
73
|
-
if (display.kind === 'normal') return tc.args ?? '';
|
|
74
|
-
if (display.kind === 'preTool') {
|
|
75
|
-
return JSON.stringify(parsePreToolToolCallArgs(tc.args)?.toolArgs ?? {});
|
|
76
|
-
}
|
|
77
|
-
if (display.kind === 'memory') {
|
|
78
|
-
return parseMemoryToolCallArgs(tc.args)?.query ?? '';
|
|
79
|
-
}
|
|
80
|
-
return '';
|
|
81
|
-
})();
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<div key={tc.id} className={`tool-call ${stateClass}`}>
|
|
85
|
-
<div
|
|
86
|
-
className="tool-call__header"
|
|
87
|
-
onClick={() => hasDetail && setExpandedId(isExpanded ? null : tc.id)}
|
|
88
|
-
>
|
|
89
|
-
<div className="tool-call__left">
|
|
90
|
-
{isCalling && <div className="tool-call__spinner" />}
|
|
91
|
-
{isDone && <Check size={16} color="#10a37f" strokeWidth={2.5} />}
|
|
92
|
-
{isError && <X size={16} color="#ef4444" strokeWidth={2.5} />}
|
|
93
|
-
<span className="tool-call__name">{displayName}</span>
|
|
94
|
-
{display.kind === 'preTool' && (
|
|
95
|
-
<span className="tool-call__badge">自动</span>
|
|
96
|
-
)}
|
|
97
|
-
</div>
|
|
98
|
-
{hasDetail && (
|
|
99
|
-
<span className={`tool-call__toggle${isExpanded ? ' tool-call__toggle--up' : ''}`}>
|
|
100
|
-
{isExpanded ? '收起' : '详情'}
|
|
101
|
-
</span>
|
|
102
|
-
)}
|
|
103
|
-
</div>
|
|
104
|
-
{isExpanded && hasDetail && (
|
|
105
|
-
<div className="tool-call__result">
|
|
106
|
-
{inputText && (
|
|
107
|
-
<div className="tool-call__io">
|
|
108
|
-
<span className="tool-call__io-label">输入</span>
|
|
109
|
-
<span className="tool-call__io-text">{formatCompactToolIo(inputText)}</span>
|
|
110
|
-
</div>
|
|
111
|
-
)}
|
|
112
|
-
<div className="tool-call__io">
|
|
113
|
-
<span className="tool-call__io-label">输出</span>
|
|
114
|
-
<span className={`tool-call__io-text${isError ? ' tool-call__io-text--error' : ''}`}>
|
|
115
|
-
{formatCompactToolIo(detailText) || '(无)'}
|
|
116
|
-
</span>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
)}
|
|
120
|
-
</div>
|
|
121
|
-
);
|
|
122
|
-
})}
|
|
123
|
-
</div>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { ArrowsClockwise, CaretRight } from '@phosphor-icons/react';
|
|
3
|
-
import type { ChatLandingProps } from '@/pages/index/index';
|
|
4
|
-
import './index.less';
|
|
5
|
-
|
|
6
|
-
const CAROUSEL_ITEMS = [
|
|
7
|
-
{ title: '预约购票', desc: '景区门票多种选择', img: 'https://mdn.alipayobjects.com/tcmarket/afts/img/HmxURLvK52sAAAAARYAAAAgAwxLNAQBr/original', prompt: '我想预约购票' },
|
|
8
|
-
{ title: '智能导游', desc: '随心游无目的漫游', img: 'https://mdn.alipayobjects.com/tcmarket/afts/img/DbHcTK8C-JIAAAAARMAAAAgAwxLNAQBr/original', prompt: '请为我智能导游' },
|
|
9
|
-
{ title: '景点介绍', desc: '智能推荐景点讲解', img: 'https://mdn.alipayobjects.com/tcmarket/afts/img/CKZLRLnGC9sAAAAARVAAAAgAwxLNAQBr/original', prompt: '介绍一下景区的景点' },
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
const SERVICE_ITEMS = [
|
|
13
|
-
{ img: 'https://mdn.alipayobjects.com/tcmarket/afts/img/pBvTRZ2lWokAAAAAQHAAAAgAwxLNAQBr/original', title: '杭城美食', desc: '千家商户 7 折起', prompt: '推荐一些杭州特色美食' },
|
|
14
|
-
{ img: 'https://mdn.alipayobjects.com/tcmarket/afts/img/OqxGT6_0RCYAAAAAQGAAAAgAwxLNAQBr/original', title: '酒店预定', desc: '万家酒店 5 折起', prompt: '帮我查找附近的酒店' },
|
|
15
|
-
{ img: 'https://mdn.alipayobjects.com/tcmarket/afts/img/HYeNTLrh83AAAAAAQFAAAAgAwxLNAQBr/original', title: '天气查询', desc: '实时天气速览', prompt: '今天天气怎么样?' },
|
|
16
|
-
{ img: 'https://mdn.alipayobjects.com/tcmarket/afts/img/atCHR6ykFnEAAAAAQHAAAAgAwxLNAQBr/original', title: '路线推荐', desc: '智能规划行程', prompt: '推荐一条游览路线' },
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const ALL_SUGGESTIONS = [
|
|
20
|
-
{ title: '热门景区推荐', prompt: '有哪些热门景区推荐?' },
|
|
21
|
-
{ title: '门票优惠政策', prompt: '有哪些门票优惠政策?' },
|
|
22
|
-
{ title: '景区开放时间', prompt: '景区的开放时间是什么时候?' },
|
|
23
|
-
{ title: '项目推荐', prompt: '有什么推荐的游玩项目?' },
|
|
24
|
-
{ title: '交通指南', prompt: '怎么到达景区?' },
|
|
25
|
-
{ title: '住宿推荐', prompt: '附近有哪些住宿推荐?' },
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
const pickRandom = <T,>(items: T[], count: number): T[] =>
|
|
29
|
-
[...items].sort(() => Math.random() - 0.5).slice(0, count);
|
|
30
|
-
|
|
31
|
-
/** @tbox-template-example tourism */
|
|
32
|
-
export default function TourismLanding({ onSend }: ChatLandingProps) {
|
|
33
|
-
const [suggestions, setSuggestions] = useState(() => pickRandom(ALL_SUGGESTIONS, 4));
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div className="tourism-example">
|
|
37
|
-
<div className="tourism-example__welcome">
|
|
38
|
-
<div className="tourism-example__welcome-title">欢迎来到文旅景区</div>
|
|
39
|
-
<div className="tourism-example__welcome-desc">
|
|
40
|
-
我是您的通用文旅景区 AI 智能体,适用于山水类单景区、城市类多景区的业务场景。
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
|
|
44
|
-
<section className="tourism-example__section tourism-example__carousel-section">
|
|
45
|
-
<h2 className="tourism-example__section-title">游玩服务</h2>
|
|
46
|
-
<div className="tourism-example__carousel">
|
|
47
|
-
{CAROUSEL_ITEMS.map((item) => (
|
|
48
|
-
<button key={item.title} className="tourism-example__carousel-item" onClick={() => onSend(item.prompt)}>
|
|
49
|
-
<img className="tourism-example__carousel-image" src={item.img} alt={item.title} />
|
|
50
|
-
<span className="tourism-example__carousel-overlay">
|
|
51
|
-
<span className="tourism-example__carousel-title">{item.title}</span>
|
|
52
|
-
<span className="tourism-example__carousel-desc">{item.desc}</span>
|
|
53
|
-
</span>
|
|
54
|
-
</button>
|
|
55
|
-
))}
|
|
56
|
-
</div>
|
|
57
|
-
</section>
|
|
58
|
-
|
|
59
|
-
<section className="tourism-example__section">
|
|
60
|
-
<h2 className="tourism-example__section-title">配套服务</h2>
|
|
61
|
-
<div className="tourism-example__service-grid">
|
|
62
|
-
{SERVICE_ITEMS.map((item) => (
|
|
63
|
-
<button key={item.title} className="tourism-example__service-item" onClick={() => onSend(item.prompt)}>
|
|
64
|
-
<img className="tourism-example__service-icon" src={item.img} alt={item.title} />
|
|
65
|
-
<span className="tourism-example__service-content">
|
|
66
|
-
<span className="tourism-example__service-title-row">
|
|
67
|
-
<span className="tourism-example__service-title">{item.title}</span>
|
|
68
|
-
<CaretRight size={20} color="#c0c4cc" />
|
|
69
|
-
</span>
|
|
70
|
-
<span className="tourism-example__service-desc">{item.desc}</span>
|
|
71
|
-
</span>
|
|
72
|
-
</button>
|
|
73
|
-
))}
|
|
74
|
-
</div>
|
|
75
|
-
</section>
|
|
76
|
-
|
|
77
|
-
<section className="tourism-example__section">
|
|
78
|
-
<div className="tourism-example__suggestion-header">
|
|
79
|
-
<h2 className="tourism-example__section-title">猜你想问</h2>
|
|
80
|
-
<button className="tourism-example__refresh" onClick={() => setSuggestions(pickRandom(ALL_SUGGESTIONS, 4))}>
|
|
81
|
-
<ArrowsClockwise size={12} />
|
|
82
|
-
<span>换一换</span>
|
|
83
|
-
</button>
|
|
84
|
-
</div>
|
|
85
|
-
<div className="tourism-example__suggestion-grid">
|
|
86
|
-
{suggestions.map((item) => (
|
|
87
|
-
<button key={item.prompt} className="tourism-example__suggestion" onClick={() => onSend(item.prompt)}>
|
|
88
|
-
<span>{item.title}</span>
|
|
89
|
-
<CaretRight size={20} color="#c0c4cc" />
|
|
90
|
-
</button>
|
|
91
|
-
))}
|
|
92
|
-
</div>
|
|
93
|
-
</section>
|
|
94
|
-
</div>
|
|
95
|
-
);
|
|
96
|
-
}
|