chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,971 @@
# Chat Markdown 自定义渲染元素(组件)实现指南
## 📋 目录
- [概述](#概述)
- [核心架构](#核心架构)
- [快速开始:生成 Markdown 并插入自定义标签](#快速开始生成-markdown-并插入自定义标签)
- [实现步骤:创建自定义渲染组件](#实现步骤创建自定义渲染组件)
- [现有示例](#现有示例)
- [最佳实践](#最佳实践)
- [常见问题](#常见问题)
## 概述
本项目使用 `mp-html` 组件作为 Markdown 渲染引擎,通过 `markdown-it-container` 插件机制实现自定义元素的渲染。当需要在 Markdown 中渲染自定义组件时(如工具调用、执行过程等),可以通过创建容器块来扩展渲染能力。
### 核心优势
-**插件化设计**:通过 `markdown-it-container` 插件扩展,不影响核心渲染逻辑
-**类型安全**:支持 UTS/TypeScript 类型定义
-**灵活扩展**:可以自定义任意容器标签的渲染
-**动态数据**:支持通过 `processingList` 传递动态数据,实现实时更新
-**多端支持**:基于 uni-app-x支持 H5、小程序、App 多端
## 核心架构
### 技术栈
- **渲染引擎**`mp-html` 组件(基于 `markdown-it`
- **插件系统**`markdown-it-container`
- **容器组件**`container.vue` 自定义组件
- **数据处理**`processingList` + `getProcessingDataByPriority`
### 架构图
```
┌─────────────────────────────────────────┐
│ Markdown 文本 │
│ (包含 :::container 语法) │
└──────────────┬──────────────────────────┘
│ markdown-it 解析
┌─────────────────────────────────────────┐
│ markdown-it-container 插件 │
│ - 识别 :::container 语法 │
│ - 转换为 <container> 标签 │
└──────────────┬──────────────────────────┘
│ 生成 HTML 节点
┌─────────────────────────────────────────┐
│ mp-html 组件 │
│ (uni_modules/mp-html) │
└──────────────┬──────────────────────────┘
│ 识别 container 标签
┌─────────────────────────────────────────┐
│ node.vue 组件 │
│ - 识别 <container> 标签 │
│ - 调用 getRenderData() │
│ - 合并 processingList 数据 │
└──────────────┬──────────────────────────┘
│ 传递合并后的数据
┌─────────────────────────────────────────┐
│ container.vue 组件 │
│ - 渲染工具调用状态 │
│ - 显示执行详情 │
│ - 处理用户交互 │
└─────────────────────────────────────────┘
```
### 数据流
```
Markdown 文本
:::container executeId="xxx" type="Page" status="EXECUTING"
:::
<container data='{"executeId":"xxx","type":"Page","status":"EXECUTING"}'></container>
container.vue 组件接收 data 属性
getRenderData() 合并 processingList 数据
最终渲染的组件数据 = data + processingList[executeId]
```
## 快速开始:生成 Markdown 并插入自定义标签
在实际开发中,我们通常需要在代码中动态生成包含自定义容器标签的 Markdown 字符串。本节介绍如何在前端生成这些标签。
### 方式一:使用工具函数(推荐)
#### 1. 使用现有的工具函数
项目已提供 `containerHelper.uts` 工具函数,位于 `subpackages/utils/containerHelper.uts`
**核心函数**
```uts
/**
* 生成工具调用自定义块
* @param beforeText 之前的文本内容
* @param toolCallData 工具调用数据
* @returns 包含自定义块的完整文本
*/
export function getCustomBlock(
beforeText: string,
toolCallData: {
type?: string;
name?: string;
executeId?: string;
status?: string
}
): string
```
**使用示例**
```uts
import { getCustomBlock } from '@/subpackages/utils/containerHelper.uts'
// 在消息处理中生成容器块
const toolCallData = {
type: 'Page',
name: '页面预览',
executeId: 'exec-123',
status: 'EXECUTING'
}
let markdown = '这是一段普通文本。'
markdown = getCustomBlock(markdown, toolCallData)
// 结果: '这是一段普通文本。\n\n:::container executeId="exec-123" type="Page" status="EXECUTING" name="页面预览"\n:::\n\n'
```
#### 2. 底层工具函数
如果需要更灵活的控制,可以使用底层函数:
```uts
import { getBlockWrapper, getBlockName } from '@/subpackages/utils/containerHelper.uts'
/**
* 生成自定义容器块
* @param data 属性数据对象
* @returns 格式化的容器块字符串
*/
function generateContainerBlock(data: Record<string, any>): string {
const blockName = getBlockName() // 返回 'container'
return getBlockWrapper(blockName, data)
}
// 使用示例
const block = generateContainerBlock({
executeId: 'exec-123',
type: 'Page',
status: 'EXECUTING',
name: '页面预览'
})
// 结果: '\n\n:::container executeId="exec-123" type="Page" status="EXECUTING" name="页面预览"\n:::\n\n'
```
### 方式二:直接字符串拼接
如果只是偶尔使用,也可以直接拼接字符串:
```uts
// 准备数据
const executeId = 'exec-123'
const type = 'Page'
const status = 'EXECUTING'
const name = '页面预览'
// 拼接 Markdown
const markdown = `这是一段文本。
:::container executeId="${executeId}" type="${type}" status="${status}" name="${name}"
:::
继续其他内容...`
```
### 方式三:创建专用的生成函数
对于特定的业务场景,可以创建专门的生成函数:
```uts
/**
* 生成页面预览容器块
* @param markdownText 现有的 Markdown 文本
* @param pageData 页面数据
* @returns 插入容器块后的 Markdown
*/
export function insertPagePreviewBlock(
markdownText: string,
pageData: { executeId: string; name: string; status: string }
): string {
const block = `\n\n:::container executeId="${pageData.executeId}" type="Page" status="${pageData.status}" name="${pageData.name}"\n:::\n\n`
// 检查是否已存在,避免重复插入
if (markdownText.includes(`executeId="${pageData.executeId}"`)) {
return markdownText
}
return `${markdownText}${block}`
}
/**
* 生成工具调用容器块
* @param markdownText 现有的 Markdown 文本
* @param toolCallData 工具调用数据
* @returns 插入容器块后的 Markdown
*/
export function insertToolCallBlock(
markdownText: string,
toolCallData: { executeId: string; type: string; name: string; status: string }
): string {
const attrs = Object.entries(toolCallData)
.map(([key, value]) => `${key}="${value}"`)
.join(' ')
const block = `\n\n:::container ${attrs}\n:::\n\n`
// 检查是否已存在
if (markdownText.includes(`executeId="${toolCallData.executeId}"`)) {
return markdownText
}
return `${markdownText}${block}`
}
```
### 数据传递规则
#### 1. 简单属性(字符串、数字)
直接在容器语法中传递:
```markdown
:::container executeId="exec-123" type="Page" status="EXECUTING" name="页面预览"
:::
```
#### 2. 复杂数据(对象、数组)
如果需要传递复杂数据,有两种方式:
**方式一:通过 processingList 传递(推荐)**
```uts
// 在消息对象中设置 processingList
const message = {
text: markdownWithContainer,
processingList: [
{
executeId: 'exec-123',
type: AgentComponentTypeEnum.Page,
name: '页面预览',
status: ProcessingEnum.EXECUTING,
result: {
input: { url: 'https://example.com' },
data: { html: '<div>...</div>' }
},
// ... 其他字段
}
]
}
```
**方式二:在容器语法中传递 JSON 字符串**
```markdown
:::container executeId="exec-123" data='{"key":"value","nested":{"prop":123}}'
:::
```
### 实际使用场景
#### 场景 1SSE 流式消息处理
```uts
// 在 AgentDetailService.uts 中
import { getCustomBlock } from '@/subpackages/utils/containerHelper.uts'
// 处理 PROCESSING 事件
if (eventType === ConversationEventTypeEnum.PROCESSING) {
const processingResult = responseData.result || {}
responseData.executeId = processingResult.executeId
// 生成包含容器块的 Markdown
const accumulatedText = getCustomBlock(
currentMessage.text || '',
responseData
)
// 更新消息
newMessage = {
...currentMessage,
text: accumulatedText,
status: MessageStatusEnum.Loading,
processingList: [
...(currentMessage?.processingList || []),
responseData,
] as ProcessingInfo[],
}
}
```
#### 场景 2检查标签是否已存在
`getCustomBlock` 函数已经内置了重复检查逻辑:
```uts
// 在 containerHelper.uts 中
export function getCustomBlock(
beforeText: string,
toolCallData: { type?: string; name?: string; executeId?: string; status?: string }
): string {
const { type, executeId } = toolCallData
if (!type || !executeId) {
return beforeText
}
const hasBlock = beforeText.includes(`executeId="${executeId}"`)
if (hasBlock) {
// 如果已经存在相同的执行ID则不重复添加
return beforeText
}
// ... 生成新块
}
```
#### 场景 3动态更新处理状态
```uts
// 当处理状态更新时,更新 processingList
const updateProcessingStatus = (
executeId: string,
newStatus: ProcessingEnum,
result?: ExecuteResultInfo
) => {
const currentMessage = getCurrentMessage()
// 更新 processingList 中对应项的状态
const updatedProcessingList = currentMessage.processingList?.map(item => {
if (item.executeId === executeId) {
return {
...item,
status: newStatus,
result: result || item.result
}
}
return item
}) || []
// 更新消息
updateMessage({
processingList: updatedProcessingList
})
}
```
### 注意事项
1. **使用换行分隔**:容器块前后需要添加换行,确保 Markdown 解析正确
```markdown
✅ 正确
文本内容
:::container executeId="xxx"
:::
继续内容
❌ 可能有问题
文本内容
:::container executeId="xxx"
:::
继续内容
```
2. **属性值转义**:属性值中的引号需要转义或使用单引号
```markdown
✅ 正确
:::container name="页面\"预览\""
:::
✅ 也可以
:::container name='页面"预览"'
:::
```
3. **避免重复插入**:在流式渲染中,注意检查标签是否已存在,避免重复插入
4. **executeId 唯一性**:确保每个容器块都有唯一的 `executeId`,用于标识和匹配 `processingList` 中的数据
## 实现步骤:创建自定义渲染组件
如果你需要创建新的自定义渲染组件,请按照以下步骤操作。
### 第一步:了解容器组件结构
容器组件位于 `uni_modules/mp-html/components/mp-html/container/container.vue`。
**组件 Props**
```vue
props: {
data: {
type: Object,
required: true
}
}
```
**数据合并逻辑**
在 `node.vue` 中,通过 `getRenderData()` 方法合并数据:
```javascript
getRenderData(data) {
if(!data) {
return {}
}
if(typeof data === 'string') {
data = JSON.parse(data)
}
// 从 processingList 中按优先级获取数据并合并
const result = {
...data,
...getProcessingDataByPriority(data.executeId, this.processingList)
}
return result
}
```
### 第二步:修改容器组件(如果需要)
如果需要支持新的渲染类型,可以修改 `container.vue` 组件:
```vue
<template>
<view class="tool-call-status">
<!-- 根据 type 渲染不同的内容 -->
<view v-if="toolCall.type === 'Page'" class="page-preview">
<!-- 页面预览内容 -->
</view>
<view v-else-if="toolCall.type === 'Tool'" class="tool-call">
<!-- 工具调用内容 -->
</view>
<!-- 默认渲染 -->
<view v-else class="default-container">
<!-- 默认内容 -->
</view>
</view>
</template>
<script>
export default {
props: {
data: {
type: Object,
required: true
}
},
computed: {
toolCall() {
return this.data
}
}
}
</script>
```
### 第三步:在 Markdown 中使用
在代码中生成包含容器块的 Markdown
```uts
import { getCustomBlock } from '@/subpackages/utils/containerHelper.uts'
const markdown = getCustomBlock('', {
executeId: 'exec-123',
type: 'Page',
name: '页面预览',
status: 'EXECUTING'
})
```
### 第四步:传递 processingList
在消息对象中设置 `processingList`
```uts
const message = {
text: markdown,
processingList: [
{
executeId: 'exec-123',
type: AgentComponentTypeEnum.Page,
name: '页面预览',
status: ProcessingEnum.EXECUTING,
result: {
input: { /* 输入参数 */ },
data: { /* 输出数据 */ }
},
cardBindConfig: { /* 卡片配置 */ },
targetId: 123
}
] as ProcessingInfo[]
}
```
### 第五步:在组件中使用
在消息组件中传递 `processingList`
```vue
<template>
<mp-html
:content="processedText"
:processing-list="msg.processingList"
:markdown="true"
:container-style="`width: 100%;`"
/>
</template>
```
## 现有示例
### 示例 1工具调用容器Tool Call
**生成 Markdown**
```uts
import { getCustomBlock } from '@/subpackages/utils/containerHelper.uts'
const markdown = getCustomBlock('', {
executeId: 'tool-call-123',
type: 'Tool',
name: '读取文件',
status: 'EXECUTING'
})
```
**Markdown 语法**
```markdown
:::container executeId="tool-call-123" type="Tool" status="EXECUTING" name="读取文件"
:::
```
**渲染效果**
- 显示工具名称和状态
- 支持展开/收起查看详情
- 显示调用参数和结果
- 支持复制功能
### 示例 2页面预览容器Page Preview
**生成 Markdown**
```uts
const markdown = getCustomBlock('', {
executeId: 'page-preview-123',
type: 'Page',
name: '页面预览',
status: 'FINISHED'
})
```
**processingList 数据**
```uts
{
executeId: 'page-preview-123',
type: AgentComponentTypeEnum.Page,
name: '页面预览',
status: ProcessingEnum.FINISHED,
result: {
input: { url: 'https://example.com' },
data: { html: '<div>页面内容</div>' }
}
}
```
**渲染效果**
- 显示页面名称和状态
- 支持预览页面(点击眼睛图标)
- 显示执行结果
### 示例 3动态状态更新
**初始状态**
```uts
// 生成 EXECUTING 状态的容器
let markdown = getCustomBlock('', {
executeId: 'exec-123',
type: 'Page',
status: 'EXECUTING'
})
let processingList = [{
executeId: 'exec-123',
status: ProcessingEnum.EXECUTING,
// ... 其他字段
}]
```
**更新为完成状态**
```uts
// 更新 processingList
processingList = processingList.map(item => {
if (item.executeId === 'exec-123') {
return {
...item,
status: ProcessingEnum.FINISHED,
result: {
input: { /* ... */ },
data: { /* ... */ }
}
}
}
return item
})
// Markdown 文本不需要修改,组件会自动根据 processingList 更新显示
```
## 最佳实践
### 1. 使用工具函数生成容器块
**推荐**:使用 `getCustomBlock` 函数
```uts
import { getCustomBlock } from '@/subpackages/utils/containerHelper.uts'
const markdown = getCustomBlock(beforeText, toolCallData)
```
**原因**
- 自动处理重复检查
- 统一的格式和风格
- 易于维护和修改
### 2. executeId 唯一性
**推荐**:确保每个容器块都有唯一的 `executeId`
```uts
const executeId = `exec-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
```
**原因**
- 用于匹配 `processingList` 中的数据
- 避免重复插入容器块
- 支持动态更新状态
### 3. 数据分离
**推荐**:简单属性放在容器语法中,复杂数据通过 `processingList` 传递
```uts
// ✅ 推荐
// Markdown 中只包含标识信息
:::container executeId="exec-123" type="Page" status="EXECUTING"
:::
// processingList 中包含完整数据
processingList: [{
executeId: 'exec-123',
result: { /* 复杂数据 */ }
}]
```
**原因**
- Markdown 文本保持简洁
- 复杂数据可以动态更新
- 支持流式渲染
### 4. 状态管理
**推荐**:使用 `ProcessingEnum` 枚举管理状态
```uts
import { ProcessingEnum } from '@/types/enums/common.uts'
const status = ProcessingEnum.EXECUTING // 'EXECUTING'
// 或
const status = ProcessingEnum.FINISHED // 'FINISHED'
const status = ProcessingEnum.FAILED // 'FAILED'
```
**原因**
- 类型安全
- 避免拼写错误
- 便于维护
### 5. 优先级处理
**推荐**:理解 `getProcessingDataByPriority` 的优先级逻辑
优先级顺序:`FINISHED` > `FAILED` > `EXECUTING`
```uts
// 如果 processingList 中有多个相同 executeId 的项
// 会优先使用 FINISHED 状态的数据
const result = getProcessingDataByPriority(executeId, processingList)
```
**原因**
- 确保显示最终状态
- 支持状态覆盖
- 处理并发更新
### 6. 错误处理
**推荐**:在生成容器块时进行数据验证
```uts
export function getCustomBlock(
beforeText: string,
toolCallData: { type?: string; name?: string; executeId?: string; status?: string }
): string {
const { type, executeId } = toolCallData
// ✅ 验证必要字段
if (!type || !executeId) {
console.warn('getCustomBlock: type 或 executeId 缺失', toolCallData)
return beforeText
}
// ... 其他逻辑
}
```
### 7. 性能优化
**推荐**:避免频繁更新 Markdown 文本
```uts
// ✅ 推荐:只更新 processingList
updateMessage({
processingList: newProcessingList
})
// ❌ 不推荐:每次都重新生成整个 Markdown
updateMessage({
text: generateNewMarkdown() // 可能导致性能问题
})
```
**原因**
- Markdown 解析有性能开销
- `processingList` 更新更高效
- 组件会自动响应数据变化
## 常见问题
### Q1: 容器块不显示怎么办?
**A**: 检查以下几点:
1. **Markdown 语法是否正确**:确保使用 `:::container` 语法,且前后有换行
```markdown
✅ 正确
:::container executeId="xxx"
:::
❌ 错误
:::container executeId="xxx":::
```
2. **mp-html 组件配置**:确保 `markdown` 属性为 `true`
```vue
<mp-html :markdown="true" :content="text" />
```
3. **容器插件是否启用**:检查 `markdown-it/index.js` 中是否配置了 `markdown-it-container`
4. **控制台错误**:检查浏览器控制台是否有错误信息
### Q2: 如何传递复杂数据?
**A**: 使用 `processingList` 传递复杂数据:
```uts
// 在消息对象中设置
const message = {
text: markdownWithContainer,
processingList: [{
executeId: 'exec-123',
result: {
input: { /* 复杂对象 */ },
data: { /* 复杂对象 */ }
}
}]
}
```
容器组件会自动从 `processingList` 中获取并合并数据。
### Q3: 如何更新容器状态?
**A**: 更新 `processingList` 中对应项的状态:
```uts
// 更新 processingList
const updatedList = processingList.map(item => {
if (item.executeId === targetExecuteId) {
return {
...item,
status: ProcessingEnum.FINISHED,
result: newResult
}
}
return item
})
// 更新消息
updateMessage({ processingList: updatedList })
```
容器组件会自动响应数据变化并更新显示。
### Q4: 如何支持新的容器类型?
**A**: 修改 `container.vue` 组件,添加新的渲染逻辑:
```vue
<template>
<view class="custom-container">
<!-- 根据 type 渲染不同内容 -->
<view v-if="toolCall.type === 'YourNewType'">
<!-- 新类型的渲染内容 -->
</view>
<!-- 现有类型 -->
<view v-else>
<!-- 现有渲染逻辑 -->
</view>
</view>
</template>
```
### Q5: 容器组件如何获取数据?
**A**: 数据通过以下流程传递:
1. **Markdown 中的属性**:通过 `:::container executeId="xxx"` 传递
2. **processingList**:通过组件 props 传递
3. **数据合并**:在 `node.vue` 的 `getRenderData()` 中合并
4. **组件接收**`container.vue` 通过 `data` prop 接收合并后的数据
### Q6: 如何调试容器组件?
**A**: 使用以下方法:
1. **添加 console.log**:在 `container.vue` 中打印接收的数据
```vue
<script>
export default {
props: {
data: Object
},
mounted() {
console.log('Container data:', this.data)
}
}
</script>
```
2. **检查 processingList**:确认 `processingList` 中是否有对应 `executeId` 的数据
3. **检查数据合并**:在 `node.vue` 的 `getRenderData()` 中添加日志
4. **Vue DevTools**:使用 Vue DevTools 检查组件 props 和 data
### Q7: 如何处理多个相同 executeId 的容器?
**A**: `getProcessingDataByPriority` 函数会按优先级返回数据:
```uts
// 优先级FINISHED > FAILED > EXECUTING
const result = getProcessingDataByPriority(executeId, processingList)
// 返回优先级最高的项
```
如果有多个相同 `executeId` 的项,会优先使用 `FINISHED` 状态的数据。
### Q8: 容器组件支持哪些交互?
**A**: 当前 `container.vue` 组件支持:
- **展开/收起**:点击头部切换详情显示
- **复制**:复制参数或结果到剪贴板
- **预览**:对于 Page 类型,支持页面预览(点击眼睛图标)
可以在 `container.vue` 中添加更多交互功能。
### Q9: 如何在流式渲染中避免重复插入?
**A**: `getCustomBlock` 函数已经内置了重复检查:
```uts
const hasBlock = beforeText.includes(`executeId="${executeId}"`)
if (hasBlock) {
return beforeText // 已存在,不重复添加
}
```
确保每次调用时传入完整的 `beforeText`。
### Q10: 容器组件样式如何自定义?
**A**: 修改 `container.vue` 中的样式:
```vue
<style lang="scss">
.tool-call-status {
// 自定义样式
background-color: #f0f0f0;
border-radius: 8rpx;
// 更多样式...
}
</style>
```
## 总结
通过本指南,你可以:
1. ✅ 理解 Markdown 自定义渲染的架构和原理
2.**快速上手**:学会如何在代码中生成包含自定义容器标签的 Markdown
3.**深入实现**:了解如何创建和自定义渲染组件
4. ✅ 遵循最佳实践,编写高质量的代码
5. ✅ 解决常见问题,快速定位和修复错误
## 参考资源
- [mp-html 文档](https://github.com/jin-yufeng/mp-html)
- [markdown-it-container 文档](https://github.com/markdown-it/markdown-it-container)
- [uni-app-x 官方文档](https://uniapp.dcloud.net.cn/uni-app-x/)
---
**维护者**:开发团队
如有问题或建议,请联系开发团队或提交 Issue。

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,101 @@
# 移动端 Agent 组件接入指南
本文档说明如何在移动端接入文件树、远程桌面和文件预览组件,以配合 `CustomActionService``AgentDetailService` 完成 Agent 完整能力的落地。
## 1. 架构概述
移动端采用了与 PC 端一致的事件驱动架构:
- **消息层** (`AgentDetailService`): 解析后端流式消息,自动触发 `OPEN_DESKTOP` (事件) 和 `REFRESH_FILE_LIST` (工具调用)。
- **交互层** (`task-result`): 用户点击任务结果时,触发 `OPEN_PREVIEW`
- **中枢层** (`CustomActionService`): 统一分发事件。
- **UI 层** (待接入): 监听事件并做出响应(如弹出抽屉、跳转页面)。
## 2. 核心事件监听
在你的聊天页面(如 `chat-conversation-component.uvue`)或全局布局中,监听以下事件:
```typescript
import { CustomEventType, OpenPreviewEventData, OpenDesktopEventData, RefreshFileListEventData } from '@/utils/customActionService.uts'
export default {
onLoad() {
// 1. 监听打开文件预览/文件树
uni.$on(CustomEventType.OPEN_PREVIEW, (data: OpenPreviewEventData) => {
console.log('收到打开预览请求:', data.conversationId)
// TODO: 打开文件树抽屉/侧边栏
// this.showFileTreeDrawer = true
// this.currentViewMode = 'preview'
// this.fetchFileList(data.conversationId)
})
// 2. 监听打开远程桌面
uni.$on(CustomEventType.OPEN_DESKTOP, (data: OpenDesktopEventData) => {
console.log('收到打开远程桌面请求:', data.conversationId)
// TODO: 显示远程桌面组件/跳转远程桌面页面
// this.showDesktopView = true
// this.currentViewMode = 'desktop'
// this.connectVNC(data.conversationId)
})
// 3. 监听刷新文件列表
uni.$on(CustomEventType.REFRESH_FILE_LIST, (data: RefreshFileListEventData) => {
console.log('收到刷新文件列表请求:', data.conversationId)
// TODO: 如果当前文件树是打开的,重新获取文件列表
// if (this.showFileTreeDrawer) {
// this.fetchFileList(data.conversationId)
// }
})
},
onUnload() {
// 记得移除监听
uni.$off(CustomEventType.OPEN_PREVIEW)
uni.$off(CustomEventType.OPEN_DESKTOP)
uni.$off(CustomEventType.REFRESH_FILE_LIST)
}
}
```
## 3. UI 组件接入要求
### 3.1 文件树组件 (File Tree)
- **触发时机**: 收到 `OPEN_PREVIEW` 或用户主动点击文件图标。
- **功能要求**:
- 根据 `conversationId` 调用后端 API 获取文件列表。
- 收到 `REFRESH_FILE_LIST` 事件时自动刷新。
- 点击文件项时,调用 `CustomActionService.openPage` 或内部逻辑进行预览。
### 3.2 远程桌面组件 (Remote Desktop)
- **触发时机**: 收到 `OPEN_DESKTOP` 或用户主动切换。
- **功能要求**:
- 建立 VNC 连接(这通常需要特定的 VNC 客户端组件或 WebView 方案)。
- 处理容器保活Keep-alive
- 支持键盘输入和触摸手势映射。
### 3.3 文件预览组件 (Preview)
- **触发时机**: 用户在文件树中点击文件。
- **功能要求**:
- 根据文件类型图片、Markdown、代码、HTML渲染不同内容。
- 对于 HTML/Web 应用,建议使用 `webview` 并通过 `CustomActionService.openPage` 打开。
## 4. 调试方法
你可以手动调用 `CustomActionService` 来模拟触发,测试 UI 响应:
```typescript
import { CustomActionService } from '@/utils/customActionService.uts'
// 模拟打开预览
CustomActionService.openPreviewView(123)
// 模拟打开远程桌面
CustomActionService.openDesktopView(123)
```
## 5. 常见问题
- **Q: `task-result` 点击无反应?**
- A: 检查是否传入了 `conversationId`。如果没有 `conversationId``task-result` 会降级发出 `task_result_click` 事件,你需要手动处理该事件。
- **Q: 样式不符合预期?**
- A: `task-result``Plan` 的样式已内置在组件中,如需全局调整,请修改 `uni_modules/mp-html` 下的相关文件。

View File

@@ -0,0 +1,114 @@
# 会话组件自动滚动底部规则文档
> 本文档描述了 `chat-conversation-component` 会话组件中自动滚动到底部的完整逻辑规则。
>
> **最后更新**: 2025-12-30
---
## 核心状态变量
| 变量名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `autoToLastMsg` | `boolean` | `false` | 是否启用自动滚动到底部 |
| `scrollTouch` | `boolean` | `false` | 用户是否正在触摸滚动touchend 后延迟 300ms 重置) |
| `mouseScroll` | `boolean` | `false` | 鼠标滚轮是否在滚动H5专用 |
| `scrollInBottom` | `boolean` | `true` | 当前视图是否在底部 |
| `needSetLockAutoToLastMsg` | `boolean` | `true` | 是否需要检查锁定自动滚动 |
| `scrollIntoView` | `string` | `''` | 滚动目标元素ID设为 `"last-msg"` 触发滚动 |
**文件位置**: `layers/AgentDetailData.uts`
---
## 触发自动滚动的场景
### 1. 发送消息时
- **代码位置**: `chat-conversation-component.uvue``handleSendMessage`
- **行为**: 设置 `autoToLastMsg = true`,调用 `scrollToLastMsg(false)`
### 2. 加载历史消息时
- **代码位置**: `layers/AgentDetailService.uts``handleQueryConversation`
- **行为**: 设置 `autoToLastMsg = true`
### 3. 点击"滚动到底部"按钮
- **代码位置**: `layers/ScrollManager.uts``scrollToLastMsg`
- **行为**: 设置 `autoToLastMsg = true``scrollIntoView = "last-msg"`
### 4. 流式消息更新时
- **代码位置**: `layers/AgentDetailService.uts``handleChangeMessageList`
- **行为**: 当 `autoToLastMsg = true` 时,发送 `streamMessageUpdate` 事件
- **响应**: 100ms 防抖后调用 `scrollToLastMsg(false)`
### 5. 消息列表/建议列表变化时
- **代码位置**: `chat-conversation-component.uvue`
- **行为**: 通过 watch 监听,调用 `handleScrollToLastMsg()`
---
## 取消自动滚动的情况
### 1. 用户主动向上滚动
- **条件**: 触摸/鼠标滚动 + 向上滚动 + 距离底部 > 50px
- **代码位置**: `layers/ScrollManager.uts``setIsInScrollBottom`
### 2. 消息列表为空
- **代码位置**: `layers/ScrollManager.uts``lockAutoToLastMsg`
---
## 滚动检测逻辑(兼容微信小程序)
```typescript
// 使用事件参数获取滚动信息
const scrollTop = e?.detail?.scrollTop ?? 0
const scrollHeight = e?.detail?.scrollHeight ?? 0
// 微信小程序使用估算值,其他平台使用 DOM 查询
// #ifdef MP-WEIXIN
const offsetHeight = 600
// #endif
// #ifndef MP-WEIXIN
const offsetHeight = uni.getElementById('msg-list')?.offsetHeight ?? 600
// #endif
const diff = scrollHeight - scrollTop - offsetHeight
// 判断是否在底部(距离 < 30px
scrollInBottom = diff < 30
// 用户向上滚动时,距离超过 50px 才禁用自动滚动
if ((scrollTouch || mouseScroll) && isScrollingUp) {
autoToLastMsg = diff <= 50
}
```
---
## 事件总线
| 事件名 | 发送位置 | 监听位置 | 用途 |
|--------|---------|---------|------|
| `streamMessageUpdate` | `AgentDetailService.uts` | `chat-conversation-component.uvue` | 流式消息更新触发滚动100ms防抖|
| `updateMessageComponent` | `AgentDetailService.uts` | `chat-conversation-component.uvue` | 更新消息组件内容 |
---
## 平台兼容性
| 平台 | 触摸检测 | 鼠标检测 | DOM 查询 |
|------|----------|----------|----------|
| 微信小程序 | `touchstart/end/cancel` | 不适用 | 使用事件参数 |
| H5/Web | `touchstart/end/cancel` | `wheel` 事件 | `uni.getElementById` |
| App | `touchstart/end/cancel` | 不适用 | `uni.getElementById` |
---
## 相关文件
| 文件 | 职责 |
|------|------|
| `layers/AgentDetailData.uts` | 状态变量定义 |
| `layers/ScrollManager.uts` | 滚动逻辑管理(跨平台兼容)|
| `layers/AgentDetailService.uts` | 消息处理与事件触发 |
| `chat-conversation-component.uvue` | 主组件,事件监听与 watch |

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,267 @@
key,zh-cn,en-us
"Mobile.AgentDev.downloadFailedWithCode","下载失败: {code}","Download failed: {code}"
"Mobile.AgentList.collectFailed","添加收藏失败","Failed to add favorite"
"Mobile.AgentList.confirmDeleteAgent","你确认要删除该智能体吗?","Are you sure you want to remove this agent?"
"Mobile.AgentList.deleteFailed","删除失败","Delete failed"
"Mobile.AgentList.fetchAgentFailed","获取智能体列表失败","Failed to load agents"
"Mobile.AgentList.fetchCategoryFailed","获取分类信息失败","Failed to load categories"
"Mobile.AgentList.unCollectFailed","取消收藏失败","Failed to remove favorite"
"Mobile.AgentSearch.emptyResult","暂无结果","No results"
"Mobile.AgentSearch.fetchAgentFailed","获取智能体列表失败","Failed to load agents"
"Mobile.AgentSearch.fetchRecentUsedFailed","获取最近使用智能体列表失败","Failed to load recent agents"
"Mobile.AgentSearch.placeholder","搜索智能体","Search agents"
"Mobile.AgentUnion.fetchHomeAgentListFailed","获取主页智能体列表失败","Failed to load agents"
"Mobile.AgentUnion.searchPlaceholder","搜索智联录","Search Directory"
"Mobile.App.pressAgainToExit","再按一次退出应用","Press again to exit the app"
"Mobile.AudioUploader.durationTooLong","录音时长过长最多支持10分钟","Audio duration is too long. Maximum is 10 minutes."
"Mobile.AudioUploader.fileEmpty","音频文件为空","Audio file is empty"
"Mobile.AudioUploader.fileReadFailed","文件读取失败: {reason}","File read failed: {reason}"
"Mobile.AudioUploader.fileTooLarge","音频文件过大请控制在10MB以内","Audio file is too large. Keep it within 10MB."
"Mobile.AudioUploader.fileTooLargeDuration","文件过大,请控制录音时长","File is too large. Please shorten recording duration."
"Mobile.AudioUploader.formatNotSupported","音频格式不支持","Audio format is not supported"
"Mobile.AudioUploader.networkException","网络连接异常,请检查网络设置","Network exception. Please check your connection."
"Mobile.AudioUploader.networkFailed","网络请求失败","Network request failed"
"Mobile.AudioUploader.networkTimeout","网络连接超时,请检查网络设置","Network timeout. Please check your connection."
"Mobile.AudioUploader.noTextRecognized","未识别到文字","No text recognized"
"Mobile.AudioUploader.pathRequired","音频文件路径不能为空","Audio file path is required"
"Mobile.AudioUploader.requestFailedWithCode","请求失败,状态码: {code}","Request failed with status code: {code}"
"Mobile.AudioUploader.responseParseFailed","响应数据解析失败","Failed to parse response data"
"Mobile.AudioUploader.serverError","服务器内部错误,请稍后重试","Server internal error. Please try again later."
"Mobile.AudioUploader.uploadFailed","上传失败","Upload failed"
"Mobile.Auth.agreementPrefix","已阅读并同意协议:","I have read and agree to the following:"
"Mobile.Auth.agreementSeparator","",", "
"Mobile.Auth.clipboardEmpty","剪贴板为空","Clipboard is empty"
"Mobile.Auth.clipboardGetFailed","获取剪贴板失败","Failed to get clipboard"
"Mobile.Auth.clipboardNotSixDigits","剪贴板内容不是6位数字验证码","Clipboard content is not a 6-digit code"
"Mobile.Auth.clipboardReadFailed","读取剪贴板失败","Failed to read clipboard"
"Mobile.Auth.codeAutoFilled","验证码已自动填充","Code auto-filled"
"Mobile.Auth.codeLoginRegister","验证码登录/注册","Code Sign In / Sign Up"
"Mobile.Auth.codeSentSuccess","验证码发送成功","Verification code sent"
"Mobile.Auth.codeSentToEmailTarget","你的邮箱","your email"
"Mobile.Auth.codeSentToPhoneTarget","手机号","phone number"
"Mobile.Auth.codeSentToPrefix","验证码已发送至","The code has been sent to"
"Mobile.Auth.confirmPassword","请确认密码","Please confirm password"
"Mobile.Auth.defaultAreaCode","+86","+86"
"Mobile.Auth.inputEmail","请输入邮箱","Please enter email"
"Mobile.Auth.inputEmailCode","输入邮箱验证码","Enter email verification code"
"Mobile.Auth.inputPassword","请输入密码","Please enter password"
"Mobile.Auth.inputPasswordAgain","请再次输入密码","Enter password again"
"Mobile.Auth.inputPhone","请输入手机号","Please enter phone number"
"Mobile.Auth.inputSmsCode","输入短信验证码","Enter SMS verification code"
"Mobile.Auth.inputVerifyCode","请输入验证码","Enter verification code"
"Mobile.Auth.invalidEmailFormat","请输入正确的邮箱格式","Please enter a valid email address"
"Mobile.Auth.invalidPhoneFormat","请输入正确的手机号格式","Please enter a valid phone number"
"Mobile.Auth.login","登录","Sign In"
"Mobile.Auth.loginFailed","登录失败","Sign in failed"
"Mobile.Auth.loginSuccess","登录成功","Signed in"
"Mobile.Auth.nextStep","下一步","Next"
"Mobile.Auth.oneClickLogin","一键登录","Quick Sign In"
"Mobile.Auth.passwordLogin","密码登录","Password Sign In"
"Mobile.Auth.passwordMinLength","密码至少需要6个字符","Password must be at least 6 characters"
"Mobile.Auth.passwordMismatch","两次输入的密码不一致","The two passwords do not match"
"Mobile.Auth.passwordSetFailed","密码设置失败,请重试","Failed to set password. Please try again."
"Mobile.Auth.passwordSetSuccess","密码设置成功","Password set successfully"
"Mobile.Auth.pasteSixDigits","请粘贴6位数字验证码","Please paste a 6-digit code"
"Mobile.Auth.phoneLoginRegister","手机号码登录/注册","Phone Sign In / Sign Up"
"Mobile.Auth.pleaseAgreeProtocol","请同意服务协议及隐私保护!","Please agree to the Terms and Privacy Policy."
"Mobile.Auth.privacyAgreement","隐私协议","Privacy Policy"
"Mobile.Auth.resend","重新获取","Resend"
"Mobile.Auth.resendAfterSeconds","{seconds}s后","Retry in {seconds}s"
"Mobile.Auth.resendSuccess","重新发送成功","Code sent again"
"Mobile.Auth.sending","正在发送...","Sending..."
"Mobile.Auth.serviceAgreement","服务使用协议","Terms of Service"
"Mobile.Auth.setPasswordDesc","请至少使用6个字符。请勿使用你登录其他网站的密码或容易被猜到的密码","Use at least 6 characters. Do not reuse passwords from other websites."
"Mobile.Auth.setPasswordTitle","密码设置","Set Password"
"Mobile.Auth.setting","设置中...","Setting..."
"Mobile.Auth.userCancelAuthorize","用户取消授权","Authorization canceled"
"Mobile.Auth.welcomeUse","欢迎使用{siteName}","Welcome to {siteName}"
"Mobile.ButtonWrapper.pagePathConfigError","页面路径配置错误","Invalid page path configuration"
"Mobile.ButtonWrapper.pagePathParamConfigError","页面路径参数配置错误","Invalid page path parameter configuration"
"Mobile.Chat.aiGeneratedDisclaimer","内容由AI生成请仔细甄别","AI-generated content, please verify carefully"
"Mobile.Chat.album","相册","Album"
"Mobile.Chat.chooseImageFailed","选择图片失败: {reason}","Failed to choose image: {reason}"
"Mobile.Chat.file","文件","File"
"Mobile.Chat.fileLimitReached","已经有 {count} 个文件了,请删除部分文件之后重新选择","You already have {count} files. Remove some files and try again."
"Mobile.Chat.fileMaxSelect","最多只能选择 {count} 个文件","You can select up to {count} files."
"Mobile.Chat.fillRequiredParams","请填写必填参数","Please fill in required fields"
"Mobile.Chat.imageLimitReached","已经有 {count} 张图片了,请删除部分图片之后重新选择","You already have {count} images. Remove some images and try again."
"Mobile.Chat.imageMaxSelect","最多只能选择 {count} 张图片","You can select up to {count} images."
"Mobile.Chat.modelSelect","模型选择","Model Selection"
"Mobile.Chat.noAgentPermission","您无该智能体权限","You do not have permission for this agent"
"Mobile.Chat.previewTypeUnsupported","当前不支持预览该类型文件","Preview is not supported for this file type"
"Mobile.Chat.removeUploadFailed","请先删除上传失败的图片或文件","Please remove failed uploads first"
"Mobile.Chat.skill","技能","Skill"
"Mobile.Chat.takePhoto","拍照","Camera"
"Mobile.Chat.uploadFileAlt","上传文件","Uploaded file"
"Mobile.Chat.uploadImageAlt","上传图片","Uploaded image"
"Mobile.Common.agentIconAlt","智能体图标","Agent icon"
"Mobile.Common.agree","同意","Agree"
"Mobile.Common.all","全部","All"
"Mobile.Common.appLogoAlt","应用 Logo","App logo"
"Mobile.Common.appName","启明","Qiming"
"Mobile.Common.avatarAlt","头像","Avatar"
"Mobile.Common.cancel","取消","Cancel"
"Mobile.Common.confirm","确认","Confirm"
"Mobile.Common.conversationHistory","会话记录","Conversations"
"Mobile.Common.copyFailed","复制失败","Copy failed"
"Mobile.Common.copySuccess","复制成功","Copied"
"Mobile.Common.coverImageAlt","封面图","Cover image"
"Mobile.Common.deleteIconAlt","删除","Delete"
"Mobile.Common.disagree","不同意","Disagree"
"Mobile.Common.from","来自","From"
"Mobile.Common.invalidInputString","输入为空或不是字符串","Input is empty or not a string"
"Mobile.Common.invalidJsonFormat","JSON格式错误","Invalid JSON format"
"Mobile.Common.loadMore","加载更多","Load more"
"Mobile.Common.loginRegister","登录 / 注册","Sign In / Sign Up"
"Mobile.Common.menu","菜单","Menu"
"Mobile.Common.missingPathParam","缺少路径参数: {key}","Missing path parameter: {key}"
"Mobile.Common.networkRetry","网络错误,请重试","Network error. Please try again."
"Mobile.Common.noData","暂无数据","No data"
"Mobile.Common.noDataImageAlt","暂无数据插图","No data illustration"
"Mobile.Common.noMoreData","没有更多数据了","No more data"
"Mobile.Common.noticeExperience","当前展示为体验数据,登录后使用完整功能","Demo data is shown now. Sign in to use full features."
"Mobile.Common.ok","确定","OK"
"Mobile.Common.open","打开","Open"
"Mobile.Common.optionSeparator","/","/"
"Mobile.Common.pageHome","页面首页","Home page"
"Mobile.Common.pleaseInput","请输入","Please enter"
"Mobile.Common.pleaseSelect","请选择","Please select"
"Mobile.Common.recentUsed","最近使用","Recent"
"Mobile.Common.requiredMark","*","*"
"Mobile.Common.retry","重试","Retry"
"Mobile.Common.skillIconAlt","技能图标","Skill icon"
"Mobile.Common.switchFailed","语言切换失败","Language switch failed"
"Mobile.Common.switchSuccess","语言已更新","Language updated"
"Mobile.Common.switching","切换中...","Switching..."
"Mobile.Common.tip","提示","Notice"
"Mobile.Common.workspace","工作台","Workspace"
"Mobile.Conversation.agentIdMissing","智能体ID不存在","Agent ID is missing"
"Mobile.Conversation.allConversations","全部会话","All conversations"
"Mobile.Conversation.chatKeyRequired","会话链接Key不能为空","Chat key cannot be empty"
"Mobile.Conversation.executing","执行中","Running"
"Mobile.Conversation.favorite","收藏","Favorite"
"Mobile.Conversation.favoriteSuccess","已添加到收藏","Added to favorites"
"Mobile.Conversation.idRequired","会话ID不能为空","Conversation ID cannot be empty"
"Mobile.Conversation.loadingHistory","正在加载历史会话","Loading history conversations"
"Mobile.Conversation.more","更多","More"
"Mobile.Conversation.moreInfo","更多信息","More info"
"Mobile.Conversation.newConversation","新建会话","New conversation"
"Mobile.Conversation.relatedTitle","相关会话","Related conversations"
"Mobile.Conversation.settingsLocked","对话开始后,对话设置将无法修改。","Settings cannot be changed after the conversation starts."
"Mobile.Conversation.settingsTitle","对话设置","Conversation settings"
"Mobile.Conversation.share","分享","Share"
"Mobile.Conversation.startWithAgent","和{name}开始会话","Start chatting with {name}"
"Mobile.Conversation.unfavoriteSuccess","已取消收藏","Removed from favorites"
"Mobile.Conversation.unknownAgent","未知智能体","Unknown agent"
"Mobile.Conversation.unnamed","未命名会话","Untitled conversation"
"Mobile.Conversation.viewAll","查看全部","View all"
"Mobile.FilePreview.documentRenderFailed","文档渲染失败","Document render failed"
"Mobile.FilePreview.getShareKeyFailed","获取分享链接失败","Failed to get share key"
"Mobile.FilePreview.invalidParams","会话ID或文件名不能为空","Conversation ID or file name cannot be empty"
"Mobile.FilePreview.previewPageLoadFailed","预览页面加载失败","Preview page failed to load"
"Mobile.FilePreview.shareDomainMissing","分享失败,无法获取当前域名","Share failed: current domain is unavailable"
"Mobile.FilePreview.shareFailed","分享失败","Share failed"
"Mobile.FilePreview.shareSuccess","分享成功,链接已复制","Shared successfully. Link copied."
"Mobile.FileTree.downloadComplete","下载完成","Download complete"
"Mobile.FileTree.downloadFailedWithCode","下载失败: {code}","Download failed: {code}"
"Mobile.FileTree.downloadNetworkFailed","下载失败,请检查网络","Download failed. Please check your network."
"Mobile.FileTree.downloadServerError","下载失败:服务器返回错误","Download failed: server returned an error"
"Mobile.FileTree.downloading","下载中...","Downloading..."
"Mobile.FileTree.exportFailed","导出失败: {message}","Export failed: {message}"
"Mobile.FileTree.exportSuccess","导出成功!","Export successful!"
"Mobile.FileTree.exportUnknownError","导出过程中发生未知错误","Unknown export error"
"Mobile.FileTree.fileValidateFailed","文件验证失败","File validation failed"
"Mobile.FileTree.invalidConversationId","会话ID不存在或无效无法导出","Conversation ID is invalid. Cannot export."
"Mobile.FileTree.linkFileNotSupported","该文件为链接文件,不支持预览","This link file cannot be previewed"
"Mobile.FileTree.noFiles","暂无文件","No files"
"Mobile.FileTree.openFailed","无法打开文件,请通过微信设置查看","Cannot open file. Check it in WeChat settings."
"Mobile.FileTree.preparingDownload","准备下载...","Preparing download..."
"Mobile.FileTree.saveFailed","保存文件失败","Failed to save file"
"Mobile.FileTree.serverError","服务器返回错误","Server returned an error"
"Mobile.FileTree.zipSavedContent","ZIP文件已保存成功\n\n文件名{fileName}\n文件路径\n{filePath}\n\n提示文件保存在小程序文件目录中可通过微信设置中的小程序存储空间查看或点击""打开""直接查看文件。","ZIP file saved successfully!\n\nFile name: {fileName}\nFile path:\n{filePath}\n\nTip: The file is stored in mini-program storage. You can check it in WeChat settings, or tap ""Open"" to view it."
"Mobile.Header.logout","退出登录","Sign Out"
"Mobile.Header.logoutFailed","退出失败","Sign out failed"
"Mobile.Header.logoutSuccess","退出成功","Signed out"
"Mobile.Header.profile","个人资料","Profile"
"Mobile.Link.copied","链接已复制","Link copied"
"Mobile.Link.copyAndOpen","链接已复制,请手动粘贴打开","Link copied. Paste it to open manually."
"Mobile.Link.invalidConfig","链接地址配置错误","Link configuration is invalid"
"Mobile.Link.openFailed","打开链接失败","Failed to open the link"
"Mobile.Nav.agent","智能体","Agents"
"Mobile.Nav.app","应用","Apps"
"Mobile.Nav.home","首页","Home"
"Mobile.Nav.unionRecord","智联录","Directory"
"Mobile.Page.inputRequired","请输入内容","Please enter content"
"Mobile.Page.loading","加载中","Loading"
"Mobile.Page.loadingMore","加载中...","Loading..."
"Mobile.PageCard.createdAt","创建于 {date}","Created on {date}"
"Mobile.PageCard.noPreviewImage","无可用预览图","No preview image available"
"Mobile.PagePreview.defaultTitle","页面预览","Page preview"
"Mobile.Permission.cameraDescription","拍照功能需要访问你的摄像头。请在浏览器设置中允许摄像头权限,然后重新尝试。","Taking photos needs camera access. Allow camera permission in browser settings and try again."
"Mobile.Permission.cameraTitle","需要摄像头权限","Camera permission required"
"Mobile.Permission.gotIt","知道了","Got it"
"Mobile.Permission.guideContent","{description}\n\n操作步骤\n{steps}","{description}\n\nSteps:\n{steps}"
"Mobile.Permission.microphoneDescription","录音功能需要访问你的麦克风。请在浏览器设置中允许麦克风权限,然后重新尝试。","Recording needs microphone access. Allow microphone permission in browser settings and try again."
"Mobile.Permission.microphoneTitle","需要麦克风权限","Microphone permission required"
"Mobile.Permission.stepAllowCamera","找到""摄像头""选项,选择""允许""","Find ""Camera"" and choose ""Allow"""
"Mobile.Permission.stepAllowMicrophone","找到""麦克风""选项,选择""允许""","Find ""Microphone"" and choose ""Allow"""
"Mobile.Permission.stepClickLock","点击浏览器地址栏左侧的锁定图标","Click the lock icon on the left side of the browser address bar"
"Mobile.Permission.stepRefreshRetryPhoto","刷新页面后重新尝试拍照","Refresh the page and try taking photos again"
"Mobile.Permission.stepRefreshRetryRecord","刷新页面后重新尝试录音","Refresh the page and try recording again"
"Mobile.Profile.dynamicCodeExpire","动态认证码({expire} 过期)","Dynamic Code (expires at {expire})"
"Mobile.Profile.email","邮箱","Email"
"Mobile.Profile.language","语言","Language"
"Mobile.Profile.languagePlaceholder","请选择语言","Select language"
"Mobile.Profile.pendingBind","待绑定","Not linked"
"Mobile.Profile.phone","手机号","Phone"
"Mobile.Profile.title","个人资料","Profile"
"Mobile.Profile.userName","用户名","Username"
"Mobile.Profile.version","版本","Version"
"Mobile.Sandbox.noneAvailable","无可用电脑","No available computer"
"Mobile.Sandbox.selectorTitle","智能体电脑选择","Agent computer selection"
"Mobile.Sandbox.switchFailed","切换失败","Switch failed"
"Mobile.Sandbox.unavailable","电脑不可用","Computer unavailable"
"Mobile.Skill.empty","暂无技能","No skills"
"Mobile.Skill.myFavorites","我的收藏","My favorites"
"Mobile.Skill.recentUsed","最近使用","Recent"
"Mobile.Skill.searchSkillName","搜索技能名称","Search skill name"
"Mobile.Stream.httpErrorWithCode","请求失败,状态码: {code}","Request failed with status code: {code}"
"Mobile.Stream.timeoutDisconnected","请求超时,已自动断开","Request timed out and was disconnected"
"Mobile.TempSession.defaultDesc","我是你的智能助手,有什么需要帮忙的吗?","I am your AI assistant. What can I help you with?"
"Mobile.TempSession.inputPlaceholder","请输入指令","Enter your instruction"
"Mobile.ThirdParty.MpHtml.callArguments","调用参数","Call arguments"
"Mobile.ThirdParty.MpHtml.callResult","调用结果","Call result"
"Mobile.ThirdParty.MpHtml.copyCode","复制代码","Copy code"
"Mobile.ThirdParty.MpHtml.emptyResult","结果为空","Result is empty"
"Mobile.ThirdParty.MpHtml.executionPlan","执行计划","Execution plan"
"Mobile.ThirdParty.MpHtml.saveSuccess","保存成功","Saved successfully"
"Mobile.ThirdParty.UniAiX.invalidMessageContent","消息内容不是字符串","Message content is not a string"
"Mobile.ThirdParty.UniCaptcha.defaultTitle","请输入验证码","Enter verification code"
"Mobile.ThirdParty.UniCaptcha.fillCaptcha","请填写验证码","Please enter the verification code"
"Mobile.ThirdParty.UniCaptcha.sceneRequired","scene不能为空","Scene is required"
"Mobile.ThirdParty.XTools.getServiceFailed","获取服务失败,不支持该操作","Failed to get service. This action is not supported"
"Mobile.ThirdParty.XTools.saveFailed","保存失败","Save failed"
"Mobile.ThirdParty.XTools.saveSuccess","保存成功","Saved successfully"
"Mobile.ThirdParty.XTools.wechatUnsupported","当前环境不支持微信操作","WeChat operation is not supported in the current environment"
"Mobile.Time.dayBeforeYesterday","前天","The day before yesterday"
"Mobile.Time.daysAgo","{count}天前","{count} days ago"
"Mobile.Time.hoursAgo","{count}小时前","{count} hours ago"
"Mobile.Time.justNow","刚刚","Just now"
"Mobile.Time.lastYear","去年","Last year"
"Mobile.Time.minutesAgo","{count}分钟前","{count} minutes ago"
"Mobile.Time.monthsAgo","{count}月前","{count} months ago"
"Mobile.Time.yearsAgo","{count}年前","{count} years ago"
"Mobile.Time.yesterday","昨天","Yesterday"
"Mobile.Voice.connecting","正在连接","Connecting..."
"Mobile.Voice.conversationActiveError","会话正在进行中,请先暂停现有会话","Conversation is active. Pause the current session first."
"Mobile.Voice.goToSettings","去设置","Go to settings"
"Mobile.Voice.holdToTalk","按住说话","Hold to talk"
"Mobile.Voice.noSpeechDetected","未识别到文字","No speech recognized"
"Mobile.Voice.permissionContent","录音功能需要访问你的麦克风,请在设置中允许录音权限","Recording needs microphone access. Allow permission in settings."
"Mobile.Voice.permissionRequired","需要录音权限","Microphone permission required"
"Mobile.Voice.recognizing","语音识别中","Recognizing speech"
"Mobile.Voice.recordFailed","录音失败","Recording failed"
"Mobile.Voice.serviceNotReady","音频上传服务未初始化","Audio upload service is not initialized"
"Mobile.Webview.aiReading","AI读取网页内容中...","AI is reading webpage content..."
"Mobile.Webview.defaultTitle","扩展页面","Extended page"
"Mobile.Webview.loadFailed","页面加载失败","Page failed to load"
1 key zh-cn en-us
2 Mobile.AgentDev.downloadFailedWithCode 下载失败: {code} Download failed: {code}
3 Mobile.AgentList.collectFailed 添加收藏失败 Failed to add favorite
4 Mobile.AgentList.confirmDeleteAgent 你确认要删除该智能体吗? Are you sure you want to remove this agent?
5 Mobile.AgentList.deleteFailed 删除失败 Delete failed
6 Mobile.AgentList.fetchAgentFailed 获取智能体列表失败 Failed to load agents
7 Mobile.AgentList.fetchCategoryFailed 获取分类信息失败 Failed to load categories
8 Mobile.AgentList.unCollectFailed 取消收藏失败 Failed to remove favorite
9 Mobile.AgentSearch.emptyResult 暂无结果 No results
10 Mobile.AgentSearch.fetchAgentFailed 获取智能体列表失败 Failed to load agents
11 Mobile.AgentSearch.fetchRecentUsedFailed 获取最近使用智能体列表失败 Failed to load recent agents
12 Mobile.AgentSearch.placeholder 搜索智能体 Search agents
13 Mobile.AgentUnion.fetchHomeAgentListFailed 获取主页智能体列表失败 Failed to load agents
14 Mobile.AgentUnion.searchPlaceholder 搜索智联录 Search Directory
15 Mobile.App.pressAgainToExit 再按一次退出应用 Press again to exit the app
16 Mobile.AudioUploader.durationTooLong 录音时长过长,最多支持10分钟 Audio duration is too long. Maximum is 10 minutes.
17 Mobile.AudioUploader.fileEmpty 音频文件为空 Audio file is empty
18 Mobile.AudioUploader.fileReadFailed 文件读取失败: {reason} File read failed: {reason}
19 Mobile.AudioUploader.fileTooLarge 音频文件过大,请控制在10MB以内 Audio file is too large. Keep it within 10MB.
20 Mobile.AudioUploader.fileTooLargeDuration 文件过大,请控制录音时长 File is too large. Please shorten recording duration.
21 Mobile.AudioUploader.formatNotSupported 音频格式不支持 Audio format is not supported
22 Mobile.AudioUploader.networkException 网络连接异常,请检查网络设置 Network exception. Please check your connection.
23 Mobile.AudioUploader.networkFailed 网络请求失败 Network request failed
24 Mobile.AudioUploader.networkTimeout 网络连接超时,请检查网络设置 Network timeout. Please check your connection.
25 Mobile.AudioUploader.noTextRecognized 未识别到文字 No text recognized
26 Mobile.AudioUploader.pathRequired 音频文件路径不能为空 Audio file path is required
27 Mobile.AudioUploader.requestFailedWithCode 请求失败,状态码: {code} Request failed with status code: {code}
28 Mobile.AudioUploader.responseParseFailed 响应数据解析失败 Failed to parse response data
29 Mobile.AudioUploader.serverError 服务器内部错误,请稍后重试 Server internal error. Please try again later.
30 Mobile.AudioUploader.uploadFailed 上传失败 Upload failed
31 Mobile.Auth.agreementPrefix 已阅读并同意协议: I have read and agree to the following:
32 Mobile.Auth.agreementSeparator ,
33 Mobile.Auth.clipboardEmpty 剪贴板为空 Clipboard is empty
34 Mobile.Auth.clipboardGetFailed 获取剪贴板失败 Failed to get clipboard
35 Mobile.Auth.clipboardNotSixDigits 剪贴板内容不是6位数字验证码 Clipboard content is not a 6-digit code
36 Mobile.Auth.clipboardReadFailed 读取剪贴板失败 Failed to read clipboard
37 Mobile.Auth.codeAutoFilled 验证码已自动填充 Code auto-filled
38 Mobile.Auth.codeLoginRegister 验证码登录/注册 Code Sign In / Sign Up
39 Mobile.Auth.codeSentSuccess 验证码发送成功 Verification code sent
40 Mobile.Auth.codeSentToEmailTarget 你的邮箱 your email
41 Mobile.Auth.codeSentToPhoneTarget 手机号 phone number
42 Mobile.Auth.codeSentToPrefix 验证码已发送至 The code has been sent to
43 Mobile.Auth.confirmPassword 请确认密码 Please confirm password
44 Mobile.Auth.defaultAreaCode +86 +86
45 Mobile.Auth.inputEmail 请输入邮箱 Please enter email
46 Mobile.Auth.inputEmailCode 输入邮箱验证码 Enter email verification code
47 Mobile.Auth.inputPassword 请输入密码 Please enter password
48 Mobile.Auth.inputPasswordAgain 请再次输入密码 Enter password again
49 Mobile.Auth.inputPhone 请输入手机号 Please enter phone number
50 Mobile.Auth.inputSmsCode 输入短信验证码 Enter SMS verification code
51 Mobile.Auth.inputVerifyCode 请输入验证码 Enter verification code
52 Mobile.Auth.invalidEmailFormat 请输入正确的邮箱格式 Please enter a valid email address
53 Mobile.Auth.invalidPhoneFormat 请输入正确的手机号格式 Please enter a valid phone number
54 Mobile.Auth.login 登录 Sign In
55 Mobile.Auth.loginFailed 登录失败 Sign in failed
56 Mobile.Auth.loginSuccess 登录成功 Signed in
57 Mobile.Auth.nextStep 下一步 Next
58 Mobile.Auth.oneClickLogin 一键登录 Quick Sign In
59 Mobile.Auth.passwordLogin 密码登录 Password Sign In
60 Mobile.Auth.passwordMinLength 密码至少需要6个字符 Password must be at least 6 characters
61 Mobile.Auth.passwordMismatch 两次输入的密码不一致 The two passwords do not match
62 Mobile.Auth.passwordSetFailed 密码设置失败,请重试 Failed to set password. Please try again.
63 Mobile.Auth.passwordSetSuccess 密码设置成功 Password set successfully
64 Mobile.Auth.pasteSixDigits 请粘贴6位数字验证码 Please paste a 6-digit code
65 Mobile.Auth.phoneLoginRegister 手机号码登录/注册 Phone Sign In / Sign Up
66 Mobile.Auth.pleaseAgreeProtocol 请同意服务协议及隐私保护! Please agree to the Terms and Privacy Policy.
67 Mobile.Auth.privacyAgreement 隐私协议 Privacy Policy
68 Mobile.Auth.resend 重新获取 Resend
69 Mobile.Auth.resendAfterSeconds {seconds}s后 Retry in {seconds}s
70 Mobile.Auth.resendSuccess 重新发送成功 Code sent again
71 Mobile.Auth.sending 正在发送... Sending...
72 Mobile.Auth.serviceAgreement 服务使用协议 Terms of Service
73 Mobile.Auth.setPasswordDesc 请至少使用6个字符。请勿使用你登录其他网站的密码或容易被猜到的密码 Use at least 6 characters. Do not reuse passwords from other websites.
74 Mobile.Auth.setPasswordTitle 密码设置 Set Password
75 Mobile.Auth.setting 设置中... Setting...
76 Mobile.Auth.userCancelAuthorize 用户取消授权 Authorization canceled
77 Mobile.Auth.welcomeUse 欢迎使用{siteName} Welcome to {siteName}
78 Mobile.ButtonWrapper.pagePathConfigError 页面路径配置错误 Invalid page path configuration
79 Mobile.ButtonWrapper.pagePathParamConfigError 页面路径参数配置错误 Invalid page path parameter configuration
80 Mobile.Chat.aiGeneratedDisclaimer 内容由AI生成,请仔细甄别 AI-generated content, please verify carefully
81 Mobile.Chat.album 相册 Album
82 Mobile.Chat.chooseImageFailed 选择图片失败: {reason} Failed to choose image: {reason}
83 Mobile.Chat.file 文件 File
84 Mobile.Chat.fileLimitReached 已经有 {count} 个文件了,请删除部分文件之后重新选择 You already have {count} files. Remove some files and try again.
85 Mobile.Chat.fileMaxSelect 最多只能选择 {count} 个文件 You can select up to {count} files.
86 Mobile.Chat.fillRequiredParams 请填写必填参数 Please fill in required fields
87 Mobile.Chat.imageLimitReached 已经有 {count} 张图片了,请删除部分图片之后重新选择 You already have {count} images. Remove some images and try again.
88 Mobile.Chat.imageMaxSelect 最多只能选择 {count} 张图片 You can select up to {count} images.
89 Mobile.Chat.modelSelect 模型选择 Model Selection
90 Mobile.Chat.noAgentPermission 您无该智能体权限 You do not have permission for this agent
91 Mobile.Chat.previewTypeUnsupported 当前不支持预览该类型文件 Preview is not supported for this file type
92 Mobile.Chat.removeUploadFailed 请先删除上传失败的图片或文件 Please remove failed uploads first
93 Mobile.Chat.skill 技能 Skill
94 Mobile.Chat.takePhoto 拍照 Camera
95 Mobile.Chat.uploadFileAlt 上传文件 Uploaded file
96 Mobile.Chat.uploadImageAlt 上传图片 Uploaded image
97 Mobile.Common.agentIconAlt 智能体图标 Agent icon
98 Mobile.Common.agree 同意 Agree
99 Mobile.Common.all 全部 All
100 Mobile.Common.appLogoAlt 应用 Logo App logo
101 Mobile.Common.appName 启明 Qiming
102 Mobile.Common.avatarAlt 头像 Avatar
103 Mobile.Common.cancel 取消 Cancel
104 Mobile.Common.confirm 确认 Confirm
105 Mobile.Common.conversationHistory 会话记录 Conversations
106 Mobile.Common.copyFailed 复制失败 Copy failed
107 Mobile.Common.copySuccess 复制成功 Copied
108 Mobile.Common.coverImageAlt 封面图 Cover image
109 Mobile.Common.deleteIconAlt 删除 Delete
110 Mobile.Common.disagree 不同意 Disagree
111 Mobile.Common.from 来自 From
112 Mobile.Common.invalidInputString 输入为空或不是字符串 Input is empty or not a string
113 Mobile.Common.invalidJsonFormat JSON格式错误 Invalid JSON format
114 Mobile.Common.loadMore 加载更多 Load more
115 Mobile.Common.loginRegister 登录 / 注册 Sign In / Sign Up
116 Mobile.Common.menu 菜单 Menu
117 Mobile.Common.missingPathParam 缺少路径参数: {key} Missing path parameter: {key}
118 Mobile.Common.networkRetry 网络错误,请重试 Network error. Please try again.
119 Mobile.Common.noData 暂无数据 No data
120 Mobile.Common.noDataImageAlt 暂无数据插图 No data illustration
121 Mobile.Common.noMoreData 没有更多数据了 No more data
122 Mobile.Common.noticeExperience 当前展示为体验数据,登录后使用完整功能 Demo data is shown now. Sign in to use full features.
123 Mobile.Common.ok 确定 OK
124 Mobile.Common.open 打开 Open
125 Mobile.Common.optionSeparator / /
126 Mobile.Common.pageHome 页面首页 Home page
127 Mobile.Common.pleaseInput 请输入 Please enter
128 Mobile.Common.pleaseSelect 请选择 Please select
129 Mobile.Common.recentUsed 最近使用 Recent
130 Mobile.Common.requiredMark * *
131 Mobile.Common.retry 重试 Retry
132 Mobile.Common.skillIconAlt 技能图标 Skill icon
133 Mobile.Common.switchFailed 语言切换失败 Language switch failed
134 Mobile.Common.switchSuccess 语言已更新 Language updated
135 Mobile.Common.switching 切换中... Switching...
136 Mobile.Common.tip 提示 Notice
137 Mobile.Common.workspace 工作台 Workspace
138 Mobile.Conversation.agentIdMissing 智能体ID不存在 Agent ID is missing
139 Mobile.Conversation.allConversations 全部会话 All conversations
140 Mobile.Conversation.chatKeyRequired 会话链接Key不能为空 Chat key cannot be empty
141 Mobile.Conversation.executing 执行中 Running
142 Mobile.Conversation.favorite 收藏 Favorite
143 Mobile.Conversation.favoriteSuccess 已添加到收藏 Added to favorites
144 Mobile.Conversation.idRequired 会话ID不能为空 Conversation ID cannot be empty
145 Mobile.Conversation.loadingHistory 正在加载历史会话 Loading history conversations
146 Mobile.Conversation.more 更多 More
147 Mobile.Conversation.moreInfo 更多信息 More info
148 Mobile.Conversation.newConversation 新建会话 New conversation
149 Mobile.Conversation.relatedTitle 相关会话 Related conversations
150 Mobile.Conversation.settingsLocked 对话开始后,对话设置将无法修改。 Settings cannot be changed after the conversation starts.
151 Mobile.Conversation.settingsTitle 对话设置 Conversation settings
152 Mobile.Conversation.share 分享 Share
153 Mobile.Conversation.startWithAgent 和{name}开始会话 Start chatting with {name}
154 Mobile.Conversation.unfavoriteSuccess 已取消收藏 Removed from favorites
155 Mobile.Conversation.unknownAgent 未知智能体 Unknown agent
156 Mobile.Conversation.unnamed 未命名会话 Untitled conversation
157 Mobile.Conversation.viewAll 查看全部 View all
158 Mobile.FilePreview.documentRenderFailed 文档渲染失败 Document render failed
159 Mobile.FilePreview.getShareKeyFailed 获取分享链接失败 Failed to get share key
160 Mobile.FilePreview.invalidParams 会话ID或文件名不能为空 Conversation ID or file name cannot be empty
161 Mobile.FilePreview.previewPageLoadFailed 预览页面加载失败 Preview page failed to load
162 Mobile.FilePreview.shareDomainMissing 分享失败,无法获取当前域名 Share failed: current domain is unavailable
163 Mobile.FilePreview.shareFailed 分享失败 Share failed
164 Mobile.FilePreview.shareSuccess 分享成功,链接已复制 Shared successfully. Link copied.
165 Mobile.FileTree.downloadComplete 下载完成 Download complete
166 Mobile.FileTree.downloadFailedWithCode 下载失败: {code} Download failed: {code}
167 Mobile.FileTree.downloadNetworkFailed 下载失败,请检查网络 Download failed. Please check your network.
168 Mobile.FileTree.downloadServerError 下载失败:服务器返回错误 Download failed: server returned an error
169 Mobile.FileTree.downloading 下载中... Downloading...
170 Mobile.FileTree.exportFailed 导出失败: {message} Export failed: {message}
171 Mobile.FileTree.exportSuccess 导出成功! Export successful!
172 Mobile.FileTree.exportUnknownError 导出过程中发生未知错误 Unknown export error
173 Mobile.FileTree.fileValidateFailed 文件验证失败 File validation failed
174 Mobile.FileTree.invalidConversationId 会话ID不存在或无效,无法导出 Conversation ID is invalid. Cannot export.
175 Mobile.FileTree.linkFileNotSupported 该文件为链接文件,不支持预览 This link file cannot be previewed
176 Mobile.FileTree.noFiles 暂无文件 No files
177 Mobile.FileTree.openFailed 无法打开文件,请通过微信设置查看 Cannot open file. Check it in WeChat settings.
178 Mobile.FileTree.preparingDownload 准备下载... Preparing download...
179 Mobile.FileTree.saveFailed 保存文件失败 Failed to save file
180 Mobile.FileTree.serverError 服务器返回错误 Server returned an error
181 Mobile.FileTree.zipSavedContent ZIP文件已保存成功!\n\n文件名:{fileName}\n文件路径:\n{filePath}\n\n提示:文件保存在小程序文件目录中,可通过微信设置中的小程序存储空间查看,或点击"打开"直接查看文件。 ZIP file saved successfully!\n\nFile name: {fileName}\nFile path:\n{filePath}\n\nTip: The file is stored in mini-program storage. You can check it in WeChat settings, or tap "Open" to view it.
182 Mobile.Header.logout 退出登录 Sign Out
183 Mobile.Header.logoutFailed 退出失败 Sign out failed
184 Mobile.Header.logoutSuccess 退出成功 Signed out
185 Mobile.Header.profile 个人资料 Profile
186 Mobile.Link.copied 链接已复制 Link copied
187 Mobile.Link.copyAndOpen 链接已复制,请手动粘贴打开 Link copied. Paste it to open manually.
188 Mobile.Link.invalidConfig 链接地址配置错误 Link configuration is invalid
189 Mobile.Link.openFailed 打开链接失败 Failed to open the link
190 Mobile.Nav.agent 智能体 Agents
191 Mobile.Nav.app 应用 Apps
192 Mobile.Nav.home 首页 Home
193 Mobile.Nav.unionRecord 智联录 Directory
194 Mobile.Page.inputRequired 请输入内容 Please enter content
195 Mobile.Page.loading 加载中 Loading
196 Mobile.Page.loadingMore 加载中... Loading...
197 Mobile.PageCard.createdAt 创建于 {date} Created on {date}
198 Mobile.PageCard.noPreviewImage 无可用预览图 No preview image available
199 Mobile.PagePreview.defaultTitle 页面预览 Page preview
200 Mobile.Permission.cameraDescription 拍照功能需要访问你的摄像头。请在浏览器设置中允许摄像头权限,然后重新尝试。 Taking photos needs camera access. Allow camera permission in browser settings and try again.
201 Mobile.Permission.cameraTitle 需要摄像头权限 Camera permission required
202 Mobile.Permission.gotIt 知道了 Got it
203 Mobile.Permission.guideContent {description}\n\n操作步骤:\n{steps} {description}\n\nSteps:\n{steps}
204 Mobile.Permission.microphoneDescription 录音功能需要访问你的麦克风。请在浏览器设置中允许麦克风权限,然后重新尝试。 Recording needs microphone access. Allow microphone permission in browser settings and try again.
205 Mobile.Permission.microphoneTitle 需要麦克风权限 Microphone permission required
206 Mobile.Permission.stepAllowCamera 找到"摄像头"选项,选择"允许" Find "Camera" and choose "Allow"
207 Mobile.Permission.stepAllowMicrophone 找到"麦克风"选项,选择"允许" Find "Microphone" and choose "Allow"
208 Mobile.Permission.stepClickLock 点击浏览器地址栏左侧的锁定图标 Click the lock icon on the left side of the browser address bar
209 Mobile.Permission.stepRefreshRetryPhoto 刷新页面后重新尝试拍照 Refresh the page and try taking photos again
210 Mobile.Permission.stepRefreshRetryRecord 刷新页面后重新尝试录音 Refresh the page and try recording again
211 Mobile.Profile.dynamicCodeExpire 动态认证码({expire} 过期) Dynamic Code (expires at {expire})
212 Mobile.Profile.email 邮箱 Email
213 Mobile.Profile.language 语言 Language
214 Mobile.Profile.languagePlaceholder 请选择语言 Select language
215 Mobile.Profile.pendingBind 待绑定 Not linked
216 Mobile.Profile.phone 手机号 Phone
217 Mobile.Profile.title 个人资料 Profile
218 Mobile.Profile.userName 用户名 Username
219 Mobile.Profile.version 版本 Version
220 Mobile.Sandbox.noneAvailable 无可用电脑 No available computer
221 Mobile.Sandbox.selectorTitle 智能体电脑选择 Agent computer selection
222 Mobile.Sandbox.switchFailed 切换失败 Switch failed
223 Mobile.Sandbox.unavailable 电脑不可用 Computer unavailable
224 Mobile.Skill.empty 暂无技能 No skills
225 Mobile.Skill.myFavorites 我的收藏 My favorites
226 Mobile.Skill.recentUsed 最近使用 Recent
227 Mobile.Skill.searchSkillName 搜索技能名称 Search skill name
228 Mobile.Stream.httpErrorWithCode 请求失败,状态码: {code} Request failed with status code: {code}
229 Mobile.Stream.timeoutDisconnected 请求超时,已自动断开 Request timed out and was disconnected
230 Mobile.TempSession.defaultDesc 我是你的智能助手,有什么需要帮忙的吗? I am your AI assistant. What can I help you with?
231 Mobile.TempSession.inputPlaceholder 请输入指令 Enter your instruction
232 Mobile.ThirdParty.MpHtml.callArguments 调用参数 Call arguments
233 Mobile.ThirdParty.MpHtml.callResult 调用结果 Call result
234 Mobile.ThirdParty.MpHtml.copyCode 复制代码 Copy code
235 Mobile.ThirdParty.MpHtml.emptyResult 结果为空 Result is empty
236 Mobile.ThirdParty.MpHtml.executionPlan 执行计划 Execution plan
237 Mobile.ThirdParty.MpHtml.saveSuccess 保存成功 Saved successfully
238 Mobile.ThirdParty.UniAiX.invalidMessageContent 消息内容不是字符串 Message content is not a string
239 Mobile.ThirdParty.UniCaptcha.defaultTitle 请输入验证码 Enter verification code
240 Mobile.ThirdParty.UniCaptcha.fillCaptcha 请填写验证码 Please enter the verification code
241 Mobile.ThirdParty.UniCaptcha.sceneRequired scene不能为空 Scene is required
242 Mobile.ThirdParty.XTools.getServiceFailed 获取服务失败,不支持该操作 Failed to get service. This action is not supported
243 Mobile.ThirdParty.XTools.saveFailed 保存失败 Save failed
244 Mobile.ThirdParty.XTools.saveSuccess 保存成功 Saved successfully
245 Mobile.ThirdParty.XTools.wechatUnsupported 当前环境不支持微信操作 WeChat operation is not supported in the current environment
246 Mobile.Time.dayBeforeYesterday 前天 The day before yesterday
247 Mobile.Time.daysAgo {count}天前 {count} days ago
248 Mobile.Time.hoursAgo {count}小时前 {count} hours ago
249 Mobile.Time.justNow 刚刚 Just now
250 Mobile.Time.lastYear 去年 Last year
251 Mobile.Time.minutesAgo {count}分钟前 {count} minutes ago
252 Mobile.Time.monthsAgo {count}月前 {count} months ago
253 Mobile.Time.yearsAgo {count}年前 {count} years ago
254 Mobile.Time.yesterday 昨天 Yesterday
255 Mobile.Voice.connecting 正在连接 Connecting...
256 Mobile.Voice.conversationActiveError 会话正在进行中,请先暂停现有会话 Conversation is active. Pause the current session first.
257 Mobile.Voice.goToSettings 去设置 Go to settings
258 Mobile.Voice.holdToTalk 按住说话 Hold to talk
259 Mobile.Voice.noSpeechDetected 未识别到文字 No speech recognized
260 Mobile.Voice.permissionContent 录音功能需要访问你的麦克风,请在设置中允许录音权限 Recording needs microphone access. Allow permission in settings.
261 Mobile.Voice.permissionRequired 需要录音权限 Microphone permission required
262 Mobile.Voice.recognizing 语音识别中 Recognizing speech
263 Mobile.Voice.recordFailed 录音失败 Recording failed
264 Mobile.Voice.serviceNotReady 音频上传服务未初始化 Audio upload service is not initialized
265 Mobile.Webview.aiReading AI读取网页内容中... AI is reading webpage content...
266 Mobile.Webview.defaultTitle 扩展页面 Extended page
267 Mobile.Webview.loadFailed 页面加载失败 Page failed to load

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,311 @@
# Qiming Mobile 多语言接入进度报告
## 1. 背景
本轮多语言改造的目标,不只是把部分页面文案替换成 `t("...")`,而是建立一套可以持续扩展的接入方案,覆盖以下几类场景:
- 业务页面与公共组件的用户可见文案
- 框架组件和原生 `uni.*` API 的提示文案
- 第三方库内部默认文案或运行时提示
- H5 语言切换后的动态刷新与兜底能力
当前项目已经完成一轮较完整的 i18n 基础建设与业务接入,核心主链路已可用,且具备继续扩展的基础。
## 2. 当前方案总览
### 2.1 核心运行时
项目以 `utils/i18n.uts` 作为统一多语言运行时,负责以下能力:
- 当前语言状态管理:`i18nState.currentLang`
- 词典缓存管理:本地缓存语言包与语言列表
- 文案查询:`t(key)``translateText(text)`
- 模板变量替换:支持 `{name}` 这类占位符
- 初始化加载:应用启动后加载远端语言包
- 语言切换:`setLanguage(lang)`
- 用户语言同步:`syncLanguageFromUser(lang)`
- H5 tabBar 文案同步:运行时调用 `uni.setTabBarItem`
这意味着项目内新增文案时,优先应接入这一层,而不是在页面里各自维护独立字典。
### 2.2 语言资源组织
当前语言资源由三层组成:
1. 远端词典
服务端通过 i18n 接口返回当前语言的词典映射,作为首选来源。
2. 本地默认语言文件
作为平台导入与本地兜底来源,主要文件为:
- `constants/i18n-locales/zh-CN.uts`
- `constants/i18n-locales/zh-HK.uts`
- `constants/i18n-locales/zh-TW.uts`
- `constants/i18n-locales/en-US.uts`
当前项目已采用“标准语言码 + bundle 文件 key”双层方案
- 标准语言码:`zh-CN``zh-TW``zh-HK``en-US`
- bundle 文件 key`zh-cn``zh-tw``zh-hk``en-us`
其中,运行时统一处理标准语言码归一,本地词包仍按小写文件 key 组织,避免将业务语言码与资源文件命名耦合在一起。
3. 本地字面量映射与兜底层
`constants/i18n.local.constants.uts` 负责:
- 把历史遗留的中文字面量映射到 `Mobile.*` key
- 在服务端未返回某个词条时,回退到本地默认语言
- 在旧代码仍传原始中文时,尽量兼容显示
### 2.3 Key 规范
当前项目统一采用 `Mobile.*` 前缀管理业务文案,例如:
- `Mobile.Nav.home`
- `Mobile.Auth.login`
- `Mobile.Chat.skill`
- `Mobile.ThirdParty.MpHtml.copyCode`
这套命名已经覆盖导航、登录、聊天、文件预览、权限、第三方组件等主要模块。
### 2.4 框架组件与业务组件接入方式
当前推荐的接入方式分为两类:
1. 直接传 key
适用于明确是固定文案的标题、按钮、占位符,例如:
- `title="Mobile.Nav.agent"`
- `placeholder="Mobile.AgentSearch.placeholder"`
2. 组件内部统一走 `translateText()`
适用于公共组件既要兼容 i18n key也要兼容旧调用方直接传中文或服务端返回文本的场景。
这样做的好处是:
- 新代码可以直接传 key
- 旧代码不必一次性全部重构
- 切语言时,动态文案仍可重新计算
### 2.5 第三方库接入方式
项目新增了 `utils/i18n-third-party.uts` 作为第三方语言适配层,当前职责包括:
- 维护第三方库当前语言状态
- 对外暴露统一同步入口 `syncThirdPartyLocales(lang)`
- 为第三方组件提供适配后的文案对象
目前已经落地的典型模式有:
1. 显式传入文案对象
例如给 `uni-load-more` 提供 `content-text`,避免继续使用组件内部默认语言逻辑。
2. 直接修改第三方组件内部用户可见文案
对一些不支持外部 locale 注入、但业务真实使用到的组件,直接将内部提示接到项目 i18n。
3. 避免循环依赖
第三方适配层不直接依赖运行时 `t()`,而是只依赖语言状态和本地兜底能力,降低循环引用风险。
## 3. 已完成能力
### 3.1 基础能力
当前已完成以下基础建设:
- i18n 运行时搭建完成
- 语言列表、语言词典、当前语言缓存机制完成
- H5 初始化语言加载接入完成
- H5 语言切换能力接入完成
- 用户登录后语言与后端用户配置同步完成
- H5 tabBar 文案运行时同步完成
- 本地默认语言文件拆分完成
- `zh-TW` / `zh-HK` 默认兜底语言文件已补齐
- i18n 标准语言码已统一为 `zh-CN / zh-TW / zh-HK / en-US`
- 本地字面量到 key 的兼容映射完成
### 3.2 业务主链路覆盖
当前主业务链路已完成较系统的文案接入,包括但不限于:
- 首页与 tab 页面
- 登录、验证码、重置密码链路
- 智能体列表与分类页
- 搜索页
- 临时会话页
- 聊天主链路
- 更多信息、相关会话、文件树、文件预览
- H5 webview 扩展页面
- 个人中心与语言切换页
H5 主链路中的标题、按钮、toast、modal、placeholder、loading、空态文案、部分 `alt` 文本都已经纳入统一管理。
微信小程序当前仍采用“固定简中”策略,因此小程序侧的静态中文配置属于当前设计范围,不视为本轮遗漏。
### 3.3 公共组件与基础组件覆盖
以下类型的组件已完成较多接入:
- 导航类组件:如 `custom-nav-bar`
- 列表/分页类组件:如 `published-agent-list`
- 下拉菜单/标签类组件:如 `menu-dropdown`
- 空态和图片描述类组件
- 聊天输入、录音、技能选择等高频组件
这些组件已逐步改成:
- 默认值使用 i18n key
- 渲染时通过 `translateText()` 兼容旧值与 key
- 避免只在初始化时计算一次文案,降低切语言后不刷新的风险
### 3.4 第三方库接入进度
当前已处理的第三方或框架相关模块包括:
- `uni-load-more`
- `mp-html`
- `uni-ai-x`
- `uni-captcha`
- `x-tools`
- `lime-cascader` 的业务实际调用路径
已完成的接入类型包括:
- 复制成功/失败提示
- 加载中/无更多/加载更多文案
- 弹窗标题、按钮、提示文案
- markdown 内部按钮与执行结果相关文案
- 验证码组件 placeholder、标题、错误提示
- 文件或系统工具提示文案
## 4. 当前进度判断
从当前仓库状态看,可以把进度分为三个层次:
### 4.1 已经完成
- H5 主业务用户可见文案的多语言主链路基本打通
- H5 多语言切换、初始化加载、用户语言同步已具备
- 多数高频公共组件已收口到统一方案
- 多个真实使用中的第三方库已完成接入
### 4.2 基本完成但仍需维护
- 历史兼容路径仍存在部分“中文字面量 -> key”的桥接逻辑
- 某些组件仍需依赖 `translateText()` 兼容旧调用方式
- 第三方库新增升级后,仍需按既有方案继续补接
### 4.3 尚未完全收敛的部分
当前仓库里仍可能存在以下残留,但优先级较低:
- 注释中的中文
- 调试日志中的中文
- `unpackage` 构建产物中的旧文案
- 第三方库 demo / example 页面中的演示文案
- 未被当前业务真正引用的三方组件内部默认文案
另外,`pages.json` 等配置层仍保留部分静态中文默认值。
在当前“小程序固定简中、H5 运行时覆盖”的策略下,这些配置属于平台级默认值,而不是本轮多语言漏接项。
这些内容不会直接阻塞当前业务多语言能力,但会影响后续全仓扫描时的噪音与维护成本。
另外,从语言资源建设角度看,当前已经完成繁中默认资源补齐,但后续仍需要继续关注:
- `zh-TW``zh-HK` 是否需要进一步按地区表达细化词条
- 后端语言列表与前端标准语言码是否完全一致
这类工作属于语言资源层和接口协同层的持续优化,不会改变当前主链路方案,但会影响后续多地区文案的一致性。
## 5. 已知边界与风险
### 5.1 H5 与小程序行为不同
当前策略中:
- H5 支持动态语言切换
- 微信小程序仍保持固定简中策略,当前不提供动态切换能力
因此如果后续要扩展小程序语言切换,需要重新评估:
- 平台能力限制
- 页面初始化时机
- tabBar 与页面标题刷新策略
### 5.2 第三方库不一定支持标准 locale 注入
不是所有第三方库都提供官方多语言入口,当前项目实际采用了三种手段:
- 外部传入文案
- 包装适配
- 直接修改第三方源码
这意味着第三方升级时,需要特别关注:
- 改动是否被覆盖
- API 是否变化
- 已接入的 i18n patch 是否失效
### 5.3 历史兼容逻辑仍然存在
为降低一次性迁移成本,当前保留了对旧文案传入方式的兼容。这带来两个结果:
- 优点:旧代码不必一次性全部重构
- 风险:新同学可能继续误传中文字面量,而不是直接传 key
因此后续应逐步推动“新增代码默认传 key”成为团队共识。
## 6. 推荐接入规范
后续新增页面、组件或三方库时,建议统一遵循以下规范。
### 6.1 新增业务文案
- 优先新增 `Mobile.*` key
- 同步补齐 `zh-CN.uts``zh-HK.uts``zh-TW.uts``en-US.uts`
- 若涉及历史兼容,再视情况补 `i18n.local.constants.uts`
### 6.2 公共组件
- 组件 `props` 默认值尽量写 key不直接写中文
- 渲染时统一走 `translateText()`
- 对只允许固定 key 的新组件,可在文档中明确约束,逐步减少兼容负担
### 6.3 用户提示类 API
以下场景必须优先检查是否已接 i18n
- `uni.showToast`
- `uni.showModal`
- `uni.showLoading`
- `uni.setNavigationBarTitle`
- 输入框 `placeholder`
- 图片 `alt`
### 6.4 第三方库
新增第三方库接入时,优先按以下顺序评估:
1. 是否支持官方 locale 配置
2. 是否可以通过 props 外部注入文案
3. 是否需要在 `utils/i18n-third-party.uts` 增加适配入口
4. 是否必须 patch 第三方源码
## 7. 后续建议
基于当前进度,后续建议按以下顺序推进:
1. 补充这份报告对应的维护约定
包括“新增文案必须补双语 key”“新增第三方需评估 locale 入口”等约束。
2. 持续校验标准语言码与 bundle key 的映射关系
重点确保 `zh-CN / zh-TW / zh-HK / en-US` 与本地词包 key、后端接口返回值、缓存值保持一致。
3. 持续做低优先级清理
将业务源码中的中文日志、历史 fallback、非核心残留继续收敛降低全仓扫描噪音。
4. 继续完善第三方审计
对真实业务已使用但尚未完全收口的第三方组件,做按需补接。
5. 结合现有审计能力持续验证
当前已有执行记录文档与多轮扫描结果,后续建议将报告、规则与审计结果保持同步。
## 8. 结论
截至当前版本,`qiming-mobile` 的多语言能力已经从“页面零散替换”演进为“统一运行时 + 本地兜底 + 第三方适配”的可维护方案。
从业务价值上看,当前主链路已经具备实际可用的多语言支撑能力;从工程角度看,也已经形成了继续扩展所需的基础结构。剩余工作主要集中在低优先级清理、第三方长尾收口,以及后续团队协作过程中的规范执行。

View File

@@ -0,0 +1,112 @@
# 主包大小优化说明
## 问题描述
主包大小3378KB超过微信小程序 2MB2048KB限制。
## 优化措施
### 1. 删除未使用的 uni-ai-x 静态资源
#### 已删除的资源:
- **ai-provider 图片**672K所有 AI 服务商 logo 图片
- azure.png, baidu.png, bailian.png, deepseek.png
- doubao.png, ifly.png, minimax.png, openai.png
- qwen.png, siliconflow.png
- 说明:项目中只使用了 bailian其他图片未使用
- **uni-chat-logo.png**188K
- 说明:代码中已被注释掉,未使用
- **default-avatar.png**4K
- 说明:未使用
#### 已删除的字体:
- **uni-ai-x/static/font/FiraCode-Regular.ttf**284K
- 说明:代码块字体,项目中未直接使用
### 2. 删除 mp-html 中的字体资源
#### 已删除的字体:
- **mp-html/static/font/FiraCode-Regular.ttf**283K
- 说明:代码高亮字体,已修复引用
### 3. 优化 KaTeX 数学公式字体
#### 已删除的不常用字体:
- **KaTeX_Typewriter-Regular.ttf**27K
- **KaTeX_Size3-Regular.ttf**7.4K
- **KaTeX_Size4-Regular.ttf**10K
#### 保留的必要字体:
- KaTeX_AMS-Regular.ttf62K
- KaTeX_Main-Regular.ttf52K
- KaTeX_Main-Bold.ttf50K
- KaTeX_Main-Italic.ttf33K
- KaTeX_Main-BoldItalic.ttf32K
- KaTeX_Math-Italic.ttf31K
- KaTeX_Math-BoldItalic.ttf30K
- KaTeX_SansSerif-Regular.ttf19K
- KaTeX_SansSerif-Bold.ttf24K
- KaTeX_SansSerif-Italic.ttf22K
- KaTeX_Size1-Regular.ttf12K
- KaTeX_Size2-Regular.ttf11K
## 优化效果统计
### 已删除的资源总大小:
- ai-provider 图片672K
- uni-chat-logo.png188K
- FiraCode 字体uni-ai-x284K
- FiraCode 字体mp-html283K
- KaTeX Typewriter 字体27K
- KaTeX Size3 字体7.4K
- KaTeX Size4 字体10K
- **总计:约 1.48MB**
### 预期优化后主包大小:
- 优化前3378KB约 3.3MB
- 优化后:预计 < 1900KB约 1.9MB
- **减小:约 1.48MB**
## 修改的文件
1. **uni_modules/mp-html/components/mp-html/styles/highlight.scss**
- 注释掉 FiraCode 字体引用
2. **uni_modules/mp-html/components/mp-html/styles/katex-fonts.scss**
- 注释掉 Typewriter、Size3、Size4 字体定义
3. **删除的文件**
- `uni_modules/uni-ai-x/static/ai-provider/*.png`
- `uni_modules/uni-ai-x/static/uni-chat-logo.png`
- `uni_modules/uni-ai-x/static/default-avatar.png`
- `uni_modules/uni-ai-x/static/font/FiraCode-Regular.ttf`
- `uni_modules/mp-html/static/font/FiraCode-Regular.ttf`
- `static/fonts/KaTeX_Typewriter-Regular.ttf`
- `static/fonts/KaTeX_Size3-Regular.ttf`
- `static/fonts/KaTeX_Size4-Regular.ttf`
## 注意事项
1. **FiraCode 字体**:如果未来需要代码高亮字体,需要重新添加并修改引用
2. **KaTeX 特殊字体**
- Typewriter 字体已删除,某些等宽文本可能显示异常
- Size3、Size4 字体已删除,特大字号可能显示异常
3. **AI Provider 图片**:如果未来需要添加新的 AI 服务商,需要在 `uni_modules/uni-ai-x/static/ai-provider/` 目录下添加对应的 logo 图片
4. **验证方法**
- 重新构建项目
- 检查主包大小是否 < 2MB
- 测试数学公式渲染是否正常
- 测试代码块显示是否正常
## 后续优化建议
如果主包仍然超过 2MB可以考虑
1. 启用分包策略subPackages
2. 进一步压缩图片资源
3. 移除更多未使用的组件和模块
4. 使用 CDN 加载静态资源

View File

@@ -0,0 +1,62 @@
---
description: 如何在 GitLab 和 GitHub 之间切换 Git 远程仓库
---
# Git 远程仓库切换指南
本项目同时托管在 GitLab 和 GitHub可根据需要切换远程仓库地址。
## 仓库地址
| 平台 | 仓库地址 |
| ------ | ------------------------------------------------------------------------ |
| GitLab | `https://git.yichamao.com/agent-platform/agent-platform-front-weapp.git` |
| GitHub | `https://github.com/qiming-ai/qiming-mobile.git` |
## 查看当前远程仓库
```bash
git remote -v
```
## 切换到 GitLab
```bash
git remote set-url origin https://git.yichamao.com/agent-platform/agent-platform-front-weapp.git
```
## 切换到 GitHub
```bash
git remote set-url origin https://github.com/qiming-ai/qiming-mobile.git
```
## 验证切换是否成功
```bash
git remote -v
```
输出应显示新的远程仓库地址,例如:
```
origin https://github.com/qiming-ai/qiming-mobile.git (fetch)
origin https://github.com/qiming-ai/qiming-mobile.git (push)
```
## 注意事项
1. 切换远程仓库前,请确保本地代码已提交或暂存
2. 切换后首次 `git pull` 可能需要处理分支追踪关系
3. 如需同时保留两个远程仓库,可以添加新的 remote 而非修改 origin
```bash
# 添加 GitLab 为额外的远程仓库
git remote add gitlab https://git.yichamao.com/agent-platform/agent-platform-front-weapp.git
# 添加 GitHub 为额外的远程仓库
git remote add github https://github.com/qiming-ai/qiming-mobile.git
# 推送到指定远程
git push gitlab main
git push github main
```

View File

@@ -0,0 +1,35 @@
# uni_modules 清理记录
## 背景
本次清理目标是收缩 `uni_modules` 目录体积,优先删除未使用模块、文档文件、演示壳文件和测试遗留文件,不涉及业务功能逻辑调整,也不主动修改仍在运行链路中的核心组件代码。
## 本次已删除内容
### 1. 整体删除的未使用模块
- `uni_modules/uni-table`
- `uni_modules/uni-datetime-picker`
- `uni_modules/uni-config-center`
### 2. 删除的文档类文件
- 各模块下的 `README.md` / `readme.md`
- 各模块下的 `CHANGELOG.md` / `changelog.md`
- `uni_modules/uni-ai-x/license.md`
### 3. 删除的 demo / 测试遗留文件
- `lime-*` 模块中仅用于演示的外层 demo 壳文件,例如 `components/lime-*/lime-*.(vue|uvue)`
- `uni_modules/lime-color/common/test.uts`
- `uni_modules/lime-checkbox/components/l-checkbox/l-checkbox_old.uvue`
## 保留原则
- 保留业务直接使用的模块与组件。
- 保留被依赖链间接使用的模块,例如 `lime-cascader` 依赖的一组 `lime-*` 基础模块。
- 保留运行时资源文件例如字体、图标映射、GIF、富文本高亮资源。
- 保留平台兼容相关文件,避免影响 H5、微信小程序和 App 多端行为。
## 验证结果
- 已对删除目标做残留引用检索。
- 当前未发现新增悬空引用。
- 当前未发现新增 linter 报错。
## 后续建议
- 如需继续瘦身,优先继续盘点 `static/``docs/``unpackage/` 等目录中的可清理文件。
- 不建议继续直接删除 `uni_modules` 中的运行时代码或资源,除非先完成更细的依赖与平台验证。