@tbox.cn/app-template-agent 0.7.5 → 0.9.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 +105 -21
- package/README.md +40 -38
- 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 +7 -6
- 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 -3
- package/apps/server/src/app-tools.ts +2 -1
- package/apps/server/src/app.ts +248 -119
- 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 -15
- 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 -17
- 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 -111
- 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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Business application entry
|
|
2
2
|
|
|
3
|
-
`App.tsx`
|
|
3
|
+
`App.tsx` 是业务应用的唯一入口。缺省出厂 = 空装配 chat 形态(`AgentChatApp` 直出对话流,无 landing 注入——014 已退役)。商圈形态经 `tbox-app create --profile mall` 加装:mall-home 模块经 home 槽供给联单式主屏(欢迎区 + 单一联单活动主卡 + 会员/停车双快览 + 四域开发测试入口),消息追加在首页内容下方,底部保留三枚快捷指令与输入区。四域开发测试入口可见性由后端开关控制(`MALL_HOME_DEV_SERVICES` env → `GET /api/m/mall-home/config`,fail-closed 缺省隐藏;详见模板 ENV.md)。会员快览严格区分登录与入会状态,仅对已入会用户展示服务端返回的真实积分。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
业务能力经 `tbox-app module add` 扩展(shopping-guide/member/parking/promotion/alipay-activity),安装后自动装配进双端 `modules.ts`;联单节点是支付宝活动回执的支付笔数投影(mall-home 域),不从会话业务卡或本地默认值派生。生成完成后运行 `pnpm verify:generated`。
|
|
6
6
|
|
|
7
7
|
## 本地交互应用
|
|
8
8
|
|
|
@@ -32,7 +32,7 @@ business/
|
|
|
32
32
|
|
|
33
33
|
图标用于辅助识别,不应压过标题、数据和主要内容。生成或修改业务页面时遵守以下约束:
|
|
34
34
|
|
|
35
|
-
- 优先使用 `@phosphor-icons/react
|
|
35
|
+
- 优先使用 `@phosphor-icons/react`(模板缺省未内置,按需 `pnpm add @phosphor-icons/react` 引入;pnpm 严格 node_modules 下不随 kit 传递解析),同一区域统一图标系列、`weight`、底板形状和用色方式。浅色底板上的普通入口默认使用 `weight="regular"`;不要为了缩小图标而统一改成 `bold`,否则线条会显得拥挤、厚重。`bold` 只用于确实需要强调的单个操作,`fill` 只用于选中态或少量主操作,不要整页铺满粗线或实心图标。
|
|
36
36
|
- **Phosphor 的 `size` 属性是最终屏幕 CSS 像素,不会经过 PostCSS 的 750 稿换算。** 不要按设计稿尺寸写 `size={36}`、`size={48}`。普通导航/按钮使用 `18~24`,网格入口使用 `20~24`,列表或卡片前导图标使用 `24~28`;只有真正的空状态或主视觉才能更大。
|
|
37
37
|
- 图标底板写 Less 的 750 稿尺寸:四宫格/八宫格入口建议 `72~80px`,列表前导图标建议 `80~96px`。内部图形的视觉宽高不超过底板的 `55%~60%`。
|
|
38
38
|
- 点击热区和图标视觉尺寸分开处理。按钮可以保持足够大的可点击区域,但不要靠放大图标填满热区。
|
|
@@ -71,12 +71,6 @@ business/
|
|
|
71
71
|
|
|
72
72
|
## 对话智能体应用
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
保留平台对话能力时继续使用 `AgentChatApp`。缺省无 landing 注入:空态首屏 = 平台对话,服务端欢迎卡经 `engine.emitCard` 旁路呈现(012 出卡协议);mall 形态(`--profile mall`)下首屏由 home 槽供给(`MallHome`,来源 = `modules/module-mall-home`)。生成欢迎卡的智能体可直接由服务端出卡,无需改 `App.tsx`。
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
export default function BusinessApp() {
|
|
78
|
-
return <AgentChatApp landing={YourLanding} />;
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
修改对话组件、卡片或服务端工具时再读取对应目录的 `AGENTS.md`。不要修改通用 `pages/index` 来写死具体行业内容,也不要把 `src/examples/` 中的参考案例复制回运行时。
|
|
76
|
+
修改对话组件、卡片或服务端工具时再读取对应目录的 `AGENTS.md`。不要修改通用首页入口来写死具体行业内容。
|
|
@@ -1,22 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 卡片注册表 + 客户端组装上下文(模块加载期快照:registerAll → ctx 注册表)。
|
|
3
3
|
*
|
|
4
4
|
* 卡片注册发生在各模块 clientModule.register(ctx) 内(模块内自注册闭环),
|
|
5
5
|
* 本文件仅消费组装端上下文;CardType 由 apps/client/src/modules.ts 从装配数组自动推导。
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* W3-2(薄装配):三通道全经 ctx 读取——
|
|
8
|
+
* - CHAT_REGISTRY = 原始注册 + 覆盖通道(聊天视图;覆盖优先);
|
|
9
|
+
* - CARD_REGISTRY = 原始注册(RawCardRenderer 详情弹层消费);
|
|
10
|
+
* - INLINE_CARD_FILTER = 内联流过滤复合(ChatPage filterInlineCards 注入值);
|
|
11
|
+
* - CLIENT_SLOTS = 槽注册表(home 槽由模块注册)。
|
|
7
12
|
*/
|
|
8
|
-
import type { CardComponent } from '@tbox.cn/app-contracts';
|
|
13
|
+
import type { CardComponent, TboxCardPayload } from '@tbox.cn/app-contracts';
|
|
9
14
|
import { createClientContext } from '@tbox.cn/app-sdk/client';
|
|
10
15
|
import { registerAll, type CardType } from '../modules';
|
|
11
16
|
|
|
12
|
-
/**
|
|
17
|
+
/** 组装端单例:注册全部模块客户端能力(模块加载期执行一次;注册幂等告警) */
|
|
13
18
|
const ctx = createClientContext();
|
|
14
19
|
registerAll(ctx);
|
|
15
20
|
|
|
16
|
-
/**
|
|
21
|
+
/** 运行时卡片注册表(原始形态;由各模块 clientModule.register 填充) */
|
|
17
22
|
export const CARD_REGISTRY = ctx.cards.getAll() as Record<CardType, CardComponent>;
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
/** 聊天入口 registry:模块注册 + 覆盖通道(同 cardType 覆盖优先) */
|
|
25
|
+
export const CHAT_REGISTRY: Record<string, CardComponent> = {
|
|
26
|
+
...CARD_REGISTRY,
|
|
27
|
+
...ctx.cards.getOverrides(),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** 内联流过滤(域注册复合;无注册 = 恒等) */
|
|
31
|
+
export const INLINE_CARD_FILTER = (cards: readonly TboxCardPayload[]): TboxCardPayload[] =>
|
|
32
|
+
ctx.cards.applyInlineFilters(cards);
|
|
20
33
|
|
|
21
|
-
/**
|
|
22
|
-
export
|
|
34
|
+
/** 客户端槽注册表(home 槽由模块 register 注册——W3-2 收口完成) */
|
|
35
|
+
export const CLIENT_SLOTS = ctx.slots;
|
|
36
|
+
|
|
37
|
+
export type { CardType } from '../modules';
|
package/apps/client/src/main.tsx
CHANGED
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import { configureRuntime, installGlobalErrorReporting } from '@tbox.cn/app-sdk/client';
|
|
3
|
+
import { configureRuntime, createLogger, installGlobalErrorReporting } from '@tbox.cn/app-sdk/client';
|
|
4
4
|
import App from './app';
|
|
5
|
-
import '
|
|
5
|
+
import { configureChatConfigDefaults } from '@app/agent-ui-kit';
|
|
6
|
+
import '@app/agent-ui-kit/theme/global.less';
|
|
7
|
+
// 品牌主题(出厂接线——theme-tbox 平台品牌资产包:素材/标题经本包承载,模块零品牌资产)。
|
|
8
|
+
// 换主题 = 替换三行(index.css + brand.js + applyTheme;叠加会致旧主题残留死重);
|
|
9
|
+
// 部署者轨迹见 cookbook·build-app-image「主题接线」。CLI 自由区,module sync 永不触碰。
|
|
10
|
+
import '@tbox.cn/app-theme-tbox/index.css';
|
|
11
|
+
import '@tbox.cn/app-theme-tbox/brand.js';
|
|
12
|
+
import { applyTheme } from '@app/agent-ui-kit/theme';
|
|
13
|
+
applyTheme('tbox');
|
|
14
|
+
// chat.json 构建期快照 → kit 静态配置 store(createRoot 前注入,首帧即快照值;
|
|
15
|
+
// HELLO_ACK settings 达后经 ChatPage 内水合覆盖)
|
|
16
|
+
import chatConfigJson from '../../../config/chat.json';
|
|
17
|
+
|
|
18
|
+
const bootLogger = createLogger('BOOT');
|
|
19
|
+
|
|
20
|
+
// 壳→H5 接入契约观测:落页即打印 URL 参数(search + hash 双读源,全量加载仅一次)。
|
|
21
|
+
// 壳侧若把参数移进 hash,mall-scope-url 的 search-only 读源会静默失效——这里第一时间暴露。
|
|
22
|
+
// code/token 类值部分脱敏(前缀 + 长度,对齐 provider log-redact 惯例)。
|
|
23
|
+
const SENSITIVE_PARAM = /^(code|token|auth_code|visitortoken|session)$/i;
|
|
24
|
+
const collectParams = (source: string): Record<string, string> => {
|
|
25
|
+
const out: Record<string, string> = {};
|
|
26
|
+
new URLSearchParams(source).forEach((value, key) => {
|
|
27
|
+
out[key] = SENSITIVE_PARAM.test(key) ? `${value.slice(0, 6)}…(len=${value.length})` : value;
|
|
28
|
+
});
|
|
29
|
+
return out;
|
|
30
|
+
};
|
|
31
|
+
const hashQuery = window.location.hash.includes('?')
|
|
32
|
+
? window.location.hash.slice(window.location.hash.indexOf('?') + 1)
|
|
33
|
+
: '';
|
|
34
|
+
bootLogger.info(
|
|
35
|
+
`url params: ${JSON.stringify({ search: collectParams(window.location.search), hash: collectParams(hashQuery) })}`,
|
|
36
|
+
);
|
|
6
37
|
|
|
7
38
|
// H1:环境值注入点(SDK 零 Vite 依赖)——VITE_API_URL 空串 = 同源相对路径
|
|
8
39
|
configureRuntime({ apiBaseUrl: import.meta.env.VITE_API_URL as string | undefined });
|
|
@@ -10,7 +41,10 @@ configureRuntime({ apiBaseUrl: import.meta.env.VITE_API_URL as string | undefine
|
|
|
10
41
|
// 全局错误兜底:未捕获异常/rejection → 统一日志行(本地落地;上报扩展点见 SDK client logger)
|
|
11
42
|
installGlobalErrorReporting();
|
|
12
43
|
|
|
44
|
+
configureChatConfigDefaults(chatConfigJson as Record<string, unknown>);
|
|
45
|
+
|
|
13
46
|
const el = document.getElementById('root');
|
|
47
|
+
|
|
14
48
|
if (el) {
|
|
15
49
|
createRoot(el).render(
|
|
16
50
|
<StrictMode>
|
|
@@ -8,20 +8,23 @@
|
|
|
8
8
|
import type { ClientContext, InferModuleCards } from '@tbox.cn/app-sdk/client';
|
|
9
9
|
|
|
10
10
|
// @tbox:imports-begin
|
|
11
|
-
|
|
11
|
+
// 其余能力经 `tbox-app module add` 扩展(shopping-guide/member 等商圈模块;mall-home 经 profiles/mall)
|
|
12
12
|
// @tbox:imports-end
|
|
13
13
|
|
|
14
14
|
// @tbox:register-begin
|
|
15
|
+
// W4 单一模板统一化:缺省出厂 = 空装配(chat 流;home 槽缺席 → chat 形态派生)——
|
|
16
|
+
// mall-home(home 槽/覆盖卡/内联过滤)经 profiles/mall 加装(W5 接线)。
|
|
17
|
+
// 形态约定:数组恒多行(register 锚区行式插入依赖换行结构;单行 [] 会让 add 只插 import 不插注册行)
|
|
15
18
|
const modules = [
|
|
16
|
-
|
|
17
|
-
];
|
|
19
|
+
] as const;
|
|
18
20
|
// @tbox:register-end
|
|
19
21
|
|
|
20
22
|
// 自由区:可做装配级定制——条件注册、顺序调整
|
|
21
23
|
|
|
22
24
|
/** 注册所有已安装模块(客户端装配——勿与 apps/server/src/modules.ts 混淆) */
|
|
23
25
|
export function registerAll(ctx: ClientContext): void {
|
|
24
|
-
|
|
26
|
+
// 空装配下 (typeof modules)[number] = never:经最小注册面断言遍历(add 模块后自动收缩为具体类型)
|
|
27
|
+
for (const m of modules as readonly { register(ctx: ClientContext): void }[]) {
|
|
25
28
|
m.register(ctx);
|
|
26
29
|
}
|
|
27
30
|
}
|
|
@@ -1,167 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 首页(薄页,单一模板统一化 W2-5/W3-2):kit ChatPage 单点装配——零域 import。
|
|
3
|
+
*
|
|
4
|
+
* - store = 应用薄壳 store(会话泳道 deps 注入 SDK 默认实现);
|
|
5
|
+
* - cards/rawCards = 覆盖通道组装 / 原始形态(registry.ts 经 ctx 读取);
|
|
6
|
+
* - slots = 客户端槽注册表(home 槽由 module-mall-home register 注册);
|
|
7
|
+
* - filterInlineCards = 内联流过滤复合(域模块注册;无注册恒等)。
|
|
8
|
+
*/
|
|
9
|
+
import { ChatPage } from '@app/agent-ui-kit';
|
|
2
10
|
import { useChatStore } from '@/store/chat';
|
|
3
|
-
import {
|
|
4
|
-
import { useTTSAutoPlay } from '@/hooks/useTTSAutoPlay';
|
|
5
|
-
import { MessageBubble } from '@/components/MessageBubble';
|
|
6
|
-
import { ChatInput } from '@/components/ChatInput';
|
|
7
|
-
import { QuickCommands } from '@/components/QuickCommands';
|
|
8
|
-
import ChatHeader from '@/components/ChatHeader';
|
|
9
|
-
import ScrollToBottomButton from '@/components/ScrollToBottomButton';
|
|
10
|
-
import PullDownIndicator from '@/components/PullDownIndicator';
|
|
11
|
-
import { usePullDownHistory, consumeAutoSendQuery, runAutoSend } from '@tbox.cn/app-sdk/client';
|
|
12
|
-
import { CardRenderer } from '@/cards/CardRenderer';
|
|
13
|
-
import type { ChatInputMessage } from '@/types/chat';
|
|
14
|
-
import './index.less';
|
|
15
|
-
|
|
16
|
-
export interface ChatLandingProps {
|
|
17
|
-
onSend: (input: string | ChatInputMessage) => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type ChatLandingComponent = ComponentType<ChatLandingProps>;
|
|
21
|
-
|
|
22
|
-
interface IndexPageProps {
|
|
23
|
-
landing?: ChatLandingComponent;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default function IndexPage({ landing: Landing }: IndexPageProps) {
|
|
27
|
-
const messages = useChatStore((s) => s.messages);
|
|
28
|
-
const historyMessages = useChatStore((s) => s.historyMessages);
|
|
29
|
-
const historyHasMore = useChatStore((s) => s.historyHasMore);
|
|
30
|
-
const isLoadingHistory = useChatStore((s) => s.isLoadingHistory);
|
|
31
|
-
const loadMoreHistory = useChatStore((s) => s.loadMoreHistory);
|
|
32
|
-
const isGenerating = useChatStore((s) => s.isGenerating);
|
|
33
|
-
const orphanCards = useChatStore((s) => s.orphanCards);
|
|
34
|
-
const cardsByMessageId = useChatStore((s) => s.cardsByMessageId);
|
|
35
|
-
const initConversation = useChatStore((s) => s.initConversation);
|
|
36
|
-
const destroy = useChatStore((s) => s.destroy);
|
|
37
|
-
const sendMessage = useChatStore((s) => s.sendMessage);
|
|
38
|
-
const cancelGeneration = useChatStore((s) => s.cancelGeneration);
|
|
39
|
-
const newConversation = useChatStore((s) => s.newConversation);
|
|
40
|
-
const authBlocked = useChatStore((s) => s.authBlocked);
|
|
41
|
-
const retryBootstrap = useChatStore((s) => s.retryBootstrap);
|
|
42
|
-
// chat settings 水合:HELLO_ACK 服务端真源 → 静态 store(ack 未达/键缺席保留构建期快照;
|
|
43
|
-
// 重连/新会话产生新 helloAck 再水合——applyServerSettings 幂等)
|
|
44
|
-
const serverSettings = useChatStore((s) => s.helloAck?.chatConfig?.settings);
|
|
45
|
-
const applyServerSettings = useChatConfigStore((s) => s.applyServerSettings);
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
applyServerSettings(serverSettings);
|
|
48
|
-
}, [serverSettings, applyServerSettings]);
|
|
49
|
-
|
|
50
|
-
const [ttsAutoPlay, setTtsAutoPlay] = useState(false);
|
|
51
|
-
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
|
|
52
|
-
const bodyRef = useRef<HTMLDivElement>(null);
|
|
53
|
-
// 初始 true:全新会话(未滚动)也可直接下拉加载历史(onScroll 才是真源更新)
|
|
54
|
-
const isAtTopRef = useRef(true);
|
|
55
|
-
const isNearBottomRef = useRef(true);
|
|
56
|
-
|
|
57
|
-
// TTS 自动播放:开启后边生成边流式朗读(服务端关态经水合强制停——防按钮隐藏仍朗读的僵尸路径)
|
|
58
|
-
const enableTTS = useChatConfigStore((s) => s.enableTTS);
|
|
59
|
-
useTTSAutoPlay(ttsAutoPlay && enableTTS);
|
|
60
|
-
|
|
61
|
-
const send = useCallback((input: string | ChatInputMessage) => sendMessage(input), [sendMessage]);
|
|
62
|
-
|
|
63
|
-
// 连接终态阻断(登录失败/scope 拒绝):禁用输入 + 提示条 + 重试(authBlocked 非空 ⇒ socket 已关 ⇒ 发送必失败)
|
|
64
|
-
const blocked = authBlocked
|
|
65
|
-
? { message: authBlocked, onRetry: () => void retryBootstrap() }
|
|
66
|
-
: undefined;
|
|
67
|
-
|
|
68
|
-
const scrollToBottom = useCallback(() => {
|
|
69
|
-
const el = bodyRef.current;
|
|
70
|
-
if (el) el.scrollTop = el.scrollHeight;
|
|
71
|
-
isNearBottomRef.current = true;
|
|
72
|
-
setShowScrollToBottom(false);
|
|
73
|
-
}, []);
|
|
74
|
-
|
|
75
|
-
const NEAR_BOTTOM = 150;
|
|
76
|
-
const onScroll = useCallback(() => {
|
|
77
|
-
const el = bodyRef.current;
|
|
78
|
-
if (!el) return;
|
|
79
|
-
isAtTopRef.current = el.scrollTop <= 5;
|
|
80
|
-
const distanceToBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
81
|
-
isNearBottomRef.current = distanceToBottom < NEAR_BOTTOM;
|
|
82
|
-
setShowScrollToBottom(!isNearBottomRef.current);
|
|
83
|
-
}, []);
|
|
84
|
-
|
|
85
|
-
// 下拉加载历史:加载时保持滚动位置(记录加载前高度,加载后补偿)
|
|
86
|
-
const onLoadHistory = useCallback(async () => {
|
|
87
|
-
const el = bodyRef.current;
|
|
88
|
-
const prevHeight = el?.scrollHeight ?? 0;
|
|
89
|
-
await loadMoreHistory();
|
|
90
|
-
requestAnimationFrame(() => {
|
|
91
|
-
const cur = bodyRef.current;
|
|
92
|
-
if (cur) cur.scrollTop += cur.scrollHeight - prevHeight;
|
|
93
|
-
});
|
|
94
|
-
}, [loadMoreHistory]);
|
|
95
|
-
|
|
96
|
-
const pull = usePullDownHistory({
|
|
97
|
-
isAtTopRef,
|
|
98
|
-
isLoadingHistory,
|
|
99
|
-
hasMore: historyHasMore,
|
|
100
|
-
onLoad: onLoadHistory,
|
|
101
|
-
// touchmove 走容器上的非 passive 原生监听(web-view 内合成事件 passive,阻止不了原生滚动)
|
|
102
|
-
scrollRef: bodyRef,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
// 深链自动发送:同步消费(StrictMode 双挂载/刷新安全),引导完成后等连接就绪发送
|
|
107
|
-
const autoQuery = consumeAutoSendQuery();
|
|
108
|
-
void initConversation().then(async () => {
|
|
109
|
-
if (autoQuery) await runAutoSend(autoQuery, sendMessage);
|
|
110
|
-
});
|
|
111
|
-
return () => destroy();
|
|
112
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
113
|
-
}, []);
|
|
114
|
-
|
|
115
|
-
// 仅在贴近底部时自动滚动,避免打断用户上翻查看历史
|
|
116
|
-
// 依赖含 cardsByMessageId:012 主卡在文字完成后挂载、消息行增高,需补滚避免卡片截出视口
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
if (isNearBottomRef.current) {
|
|
119
|
-
const el = bodyRef.current;
|
|
120
|
-
if (el) el.scrollTop = el.scrollHeight;
|
|
121
|
-
}
|
|
122
|
-
}, [messages, orphanCards, cardsByMessageId]);
|
|
11
|
+
import { CARD_REGISTRY, CHAT_REGISTRY, CLIENT_SLOTS, INLINE_CARD_FILTER } from '@/cards/registry';
|
|
123
12
|
|
|
13
|
+
export default function IndexPage() {
|
|
124
14
|
return (
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
<div className="chat-page__body-wrapper">
|
|
134
|
-
<div
|
|
135
|
-
className="chat-page__body"
|
|
136
|
-
ref={bodyRef}
|
|
137
|
-
onScroll={onScroll}
|
|
138
|
-
>
|
|
139
|
-
{/* 下拉加载历史指示器 */}
|
|
140
|
-
<PullDownIndicator status={pull.pullStatus} distance={pull.pullDistance} />
|
|
141
|
-
|
|
142
|
-
{/* 历史消息(下拉加载) */}
|
|
143
|
-
{historyMessages.map((msg) => (
|
|
144
|
-
<MessageBubble key={msg.id} message={msg} />
|
|
145
|
-
))}
|
|
146
|
-
|
|
147
|
-
{Landing ? <Landing onSend={send} /> : null}
|
|
148
|
-
|
|
149
|
-
{/* 业务旁路 emit 的卡片(如欢迎卡) */}
|
|
150
|
-
{orphanCards.map((card) => (
|
|
151
|
-
<CardRenderer key={card.id} card={card} />
|
|
152
|
-
))}
|
|
153
|
-
|
|
154
|
-
{/* 消息(每条消息下的卡片由 MessageBubble 内部渲染) */}
|
|
155
|
-
{messages.map((msg) => (
|
|
156
|
-
<MessageBubble key={msg.id} message={msg} />
|
|
157
|
-
))}
|
|
158
|
-
</div>
|
|
159
|
-
|
|
160
|
-
<ScrollToBottomButton visible={showScrollToBottom && isGenerating} onClick={scrollToBottom} />
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
{!blocked && <QuickCommands onCommand={send} />}
|
|
164
|
-
<ChatInput generating={isGenerating} blocked={blocked} onSend={send} onCancel={cancelGeneration} onNewChat={newConversation} />
|
|
165
|
-
</div>
|
|
15
|
+
<ChatPage
|
|
16
|
+
store={useChatStore}
|
|
17
|
+
cards={CHAT_REGISTRY}
|
|
18
|
+
rawCards={CARD_REGISTRY}
|
|
19
|
+
slots={CLIENT_SLOTS}
|
|
20
|
+
filterInlineCards={INLINE_CARD_FILTER}
|
|
21
|
+
/>
|
|
166
22
|
);
|
|
167
23
|
}
|
|
@@ -5,11 +5,11 @@ const appLogger = createLogger('AgentChatApp');
|
|
|
5
5
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
import { AdapterProvider, createPlatformAdapters, type AdapterContextValue } from '@tbox.cn/app-sdk/client';
|
|
7
7
|
import type { TboxSessionData } from '@tbox.cn/app-contracts';
|
|
8
|
-
import IndexPage
|
|
8
|
+
import IndexPage from '@/pages/index/index';
|
|
9
9
|
import { authClient, platformApi } from './agent-runtime';
|
|
10
10
|
|
|
11
11
|
/** 平台默认的全栈对话运行时。普通本地应用无需读取或修改。 */
|
|
12
|
-
export default function AgentChatApp(
|
|
12
|
+
export default function AgentChatApp() {
|
|
13
13
|
const [session, setSession] = useState<TboxSessionData | null>(null);
|
|
14
14
|
// TTS 不依赖 session,恒可用且保持同一实例(播放状态不因会话刷新丢失)
|
|
15
15
|
const tts = useMemo(() => createPlatformAdapters().tts, []);
|
|
@@ -17,15 +17,16 @@ export default function AgentChatApp({ landing }: { landing?: ChatLandingCompone
|
|
|
17
17
|
useEffect(() => {
|
|
18
18
|
authClient
|
|
19
19
|
.ensureAuth()
|
|
20
|
-
|
|
20
|
+
// 会话 β 收口:身份恒取 token(服务端 401/403),userId 不再上行
|
|
21
|
+
.then(() => platformApi.getTboxSession())
|
|
21
22
|
.then(setSession)
|
|
22
23
|
.catch((err) => appLogger.error('tbox session init failed:', err));
|
|
23
24
|
}, []);
|
|
24
25
|
|
|
25
26
|
const refreshSession = useCallback(async (): Promise<AdapterContextValue> => {
|
|
26
27
|
platformApi.clearTboxSession();
|
|
27
|
-
|
|
28
|
-
const data = await platformApi.getTboxSession(
|
|
28
|
+
await authClient.refreshAuth();
|
|
29
|
+
const data = await platformApi.getTboxSession(true);
|
|
29
30
|
setSession(data);
|
|
30
31
|
return { tts, ...createPlatformAdapters(data, { tts }), refreshSession };
|
|
31
32
|
}, [tts]);
|
|
@@ -37,7 +38,7 @@ export default function AgentChatApp({ landing }: { landing?: ChatLandingCompone
|
|
|
37
38
|
|
|
38
39
|
return (
|
|
39
40
|
<AdapterProvider value={adapters}>
|
|
40
|
-
<IndexPage
|
|
41
|
+
<IndexPage />
|
|
41
42
|
</AdapterProvider>
|
|
42
43
|
);
|
|
43
44
|
}
|
|
@@ -4,12 +4,17 @@
|
|
|
4
4
|
* 环境值(VITE_API_URL / VITE_AUTH_PROVIDER)由 main.tsx 经 configureRuntime / 选项注入(H1)。
|
|
5
5
|
*/
|
|
6
6
|
import { createAuthClient, createPlatformApi } from '@tbox.cn/app-sdk/client';
|
|
7
|
+
// 读源归位 TIER1(W1-①):mall-scope-url 契约助手(search-only 语义;壳 URL 改写层契约见包注释)
|
|
8
|
+
import { readMallIdFromUrl } from '@app/contracts-mall';
|
|
7
9
|
|
|
8
10
|
/** VITE_AUTH_PROVIDER=external 时壳内换码登录转发外部(服务端需同步配置 AUTH_EXTERNAL_*) */
|
|
9
11
|
const AUTH_PROVIDER = import.meta.env.VITE_AUTH_PROVIDER as string | undefined;
|
|
10
12
|
|
|
11
13
|
export const authClient = createAuthClient({
|
|
12
14
|
...(AUTH_PROVIDER === 'external' ? { loginPlatform: 'external' } : {}),
|
|
15
|
+
// SPEZ-017:token 缓存域键——URL mallId 变更即失效旧 token 重登(跨 mall 防串域)。
|
|
16
|
+
// 读源与 chat.ts configureHelloContext 逐字符同源(mall-scope-url.ts 唯一真源,search-only)
|
|
17
|
+
scopeKey: () => readMallIdFromUrl() ?? '',
|
|
13
18
|
});
|
|
14
19
|
|
|
15
20
|
export const platformApi = createPlatformApi();
|