chore: update frontend tooling and Chinese copy
This commit is contained in:
697
admin-frontend/package-lock.json
generated
697
admin-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,11 +18,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.1.0",
|
"@vitejs/plugin-vue": "^5.1.0",
|
||||||
"autoprefixer": "^10.4.0",
|
"autoprefixer": "^10.5.0",
|
||||||
"postcss": "^8.4.0",
|
"postcss": "^8.4.0",
|
||||||
"tailwindcss": "^3.4.0",
|
"tailwindcss": "^3.4.0",
|
||||||
"typescript": "^5.6.0",
|
"typescript": "^5.6.0",
|
||||||
"vite": "^5.4.0",
|
"vite": "^6.4.2",
|
||||||
"vue-tsc": "^2.1.0"
|
"vue-tsc": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ const props = withDefaults(
|
|||||||
{
|
{
|
||||||
tone: 'light',
|
tone: 'light',
|
||||||
title: '生成轨迹',
|
title: '生成轨迹',
|
||||||
description: '查看生成、资源补全和 Provider 调用事件,便于演示时解释状态来源与失败恢复。',
|
description: '查看生成、资源补全和供应商调用事件,便于演示时解释状态来源与失败恢复。',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -123,9 +123,9 @@ function eventLabel(eventType: string) {
|
|||||||
context_prepared: '上下文准备',
|
context_prepared: '上下文准备',
|
||||||
narrative_generated: '正文生成',
|
narrative_generated: '正文生成',
|
||||||
story_saved: '故事保存',
|
story_saved: '故事保存',
|
||||||
provider_call_started: 'Provider 调用',
|
provider_call_started: '供应商调用',
|
||||||
provider_call_succeeded: 'Provider 成功',
|
provider_call_succeeded: '供应商成功',
|
||||||
provider_call_failed: 'Provider 失败',
|
provider_call_failed: '供应商失败',
|
||||||
cover_image_started: '封面开始',
|
cover_image_started: '封面开始',
|
||||||
cover_image_succeeded: '封面就绪',
|
cover_image_succeeded: '封面就绪',
|
||||||
cover_image_failed: '封面失败',
|
cover_image_failed: '封面失败',
|
||||||
@@ -314,7 +314,7 @@ defineExpose({ refresh })
|
|||||||
class="grid gap-3 md:grid-cols-4"
|
class="grid gap-3 md:grid-cols-4"
|
||||||
>
|
>
|
||||||
<div class="rounded-lg border p-3" :class="panelClass">
|
<div class="rounded-lg border p-3" :class="panelClass">
|
||||||
<div class="text-xs" :class="mutedClass">Provider 成功率</div>
|
<div class="text-xs" :class="mutedClass">供应商成功率</div>
|
||||||
<div class="mt-1 text-xl font-semibold">{{ providerSuccessRate }}%</div>
|
<div class="mt-1 text-xl font-semibold">{{ providerSuccessRate }}%</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border p-3" :class="panelClass">
|
<div class="rounded-lg border p-3" :class="panelClass">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<header class="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
|
<header class="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold gradient-text">引擎调度中心</h1>
|
<h1 class="text-3xl font-bold gradient-text">引擎调度中心</h1>
|
||||||
<p class="text-sm text-gray-500 mt-1">Provider Orchestration & Strategy</p>
|
<p class="text-sm text-gray-500 mt-1">供应商编排与策略</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="bg-blue-50 text-blue-700 px-3 py-1 rounded-full text-xs font-medium flex items-center gap-1">
|
<div class="bg-blue-50 text-blue-700 px-3 py-1 rounded-full text-xs font-medium flex items-center gap-1">
|
||||||
@@ -33,13 +33,13 @@
|
|||||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-start xl:justify-between">
|
<div class="flex flex-col gap-5 xl:flex-row xl:items-start xl:justify-between">
|
||||||
<div class="max-w-2xl">
|
<div class="max-w-2xl">
|
||||||
<div class="flex flex-wrap items-center gap-3">
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
<h2 class="text-xl font-bold text-gray-900">当前环境 Provider 运营摘要</h2>
|
<h2 class="text-xl font-bold text-gray-900">当前环境供应商运营摘要</h2>
|
||||||
<span class="rounded-full bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700">
|
<span class="rounded-full bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700">
|
||||||
跨用户 / 当前环境
|
跨用户 / 当前环境
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-sm leading-6 text-gray-500">
|
<p class="mt-2 text-sm leading-6 text-gray-500">
|
||||||
这里展示的是当前部署环境内所有生成任务留下的 Provider 调用轨迹,便于运营和排障。
|
这里展示的是当前部署环境内所有生成任务留下的供应商调用轨迹,便于运营和排障。
|
||||||
跨环境对比仍需要后续独立汇聚层。
|
跨环境对比仍需要后续独立汇聚层。
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-4 flex flex-wrap gap-2">
|
<div class="mt-4 flex flex-wrap gap-2">
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
<div class="mt-1 text-lg font-semibold text-gray-900">{{ formatLatency(analytics.avg_latency_ms) }}</div>
|
<div class="mt-1 text-lg font-semibold text-gray-900">{{ formatLatency(analytics.avg_latency_ms) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-xl border border-gray-100 bg-white px-4 py-3">
|
<div class="rounded-xl border border-gray-100 bg-white px-4 py-3">
|
||||||
<div class="text-xs text-gray-500">配置中 Provider</div>
|
<div class="text-xs text-gray-500">配置中供应商</div>
|
||||||
<div class="mt-1 text-lg font-semibold text-gray-900">{{ enabledProviderCount }}/{{ providers.length }}</div>
|
<div class="mt-1 text-lg font-semibold text-gray-900">{{ enabledProviderCount }}/{{ providers.length }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -178,8 +178,8 @@
|
|||||||
<div class="rounded-2xl border border-gray-100 bg-white">
|
<div class="rounded-2xl border border-gray-100 bg-white">
|
||||||
<div class="flex items-center justify-between border-b border-gray-100 px-5 py-4">
|
<div class="flex items-center justify-between border-b border-gray-100 px-5 py-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-semibold text-gray-900">Provider 调用明细</h3>
|
<h3 class="font-semibold text-gray-900">供应商调用明细</h3>
|
||||||
<p class="mt-1 text-xs text-gray-500">按能力和 adapter 聚合的当前环境视图</p>
|
<p class="mt-1 text-xs text-gray-500">按能力和驱动聚合的当前环境视图</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-gray-400">{{ analyticsProviderRows.length }} 个组合</span>
|
<span class="text-xs text-gray-400">{{ analyticsProviderRows.length }} 个组合</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="analyticsProviderRows.length === 0" class="px-5 py-8 text-sm text-gray-500">
|
<div v-if="analyticsProviderRows.length === 0" class="px-5 py-8 text-sm text-gray-500">
|
||||||
当前筛选条件下还没有 Provider 调用样本。
|
当前筛选条件下还没有供应商调用样本。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -308,7 +308,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
|
|
||||||
<BaseCard padding="md" title="可用驱动 (Adapters)">
|
<BaseCard padding="md" title="可用驱动">
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span v-for="adapter in availableAdapters" :key="adapter"
|
<span v-for="adapter in availableAdapters" :key="adapter"
|
||||||
class="px-2 py-1 text-xs bg-indigo-50 text-indigo-700 rounded-full border border-indigo-100">
|
class="px-2 py-1 text-xs bg-indigo-50 text-indigo-700 rounded-full border border-indigo-100">
|
||||||
@@ -410,21 +410,21 @@
|
|||||||
|
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="form.adapter"
|
v-model="form.adapter"
|
||||||
label="驱动程序 (Adapter)"
|
label="驱动程序"
|
||||||
:options="adapterOptions"
|
:options="adapterOptions"
|
||||||
required
|
required
|
||||||
description="选择底层的 API 驱动协议"
|
description="选择底层的 API 驱动协议"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BaseInput v-model="form.model" label="模型名称 (Model)" placeholder="如: gpt-4o, minimax-v2" description="具体调用的模型ID" />
|
<BaseInput v-model="form.model" label="模型名称" placeholder="如: gpt-4o, minimax-v2" description="具体调用的模型 ID" />
|
||||||
|
|
||||||
<BaseInput v-model.number="form.priority" label="优先级 (0-100)" type="number" description="数字越大越优先" />
|
<BaseInput v-model.number="form.priority" label="优先级 (0-100)" type="number" description="数字越大越优先" />
|
||||||
|
|
||||||
<div class="md:col-span-2 p-4 bg-gray-50 rounded-xl border border-gray-100 space-y-4">
|
<div class="md:col-span-2 p-4 bg-gray-50 rounded-xl border border-gray-100 space-y-4">
|
||||||
<h3 class="text-sm font-bold text-gray-700">密钥与连接</h3>
|
<h3 class="text-sm font-bold text-gray-700">密钥与连接</h3>
|
||||||
<BaseInput v-model="form.api_key" label="API Key" type="password" placeholder="留空则使用 .env 配置" :required="!form.id && !form.config_ref" />
|
<BaseInput v-model="form.api_key" label="API 密钥" type="password" placeholder="留空则使用 .env 配置" :required="!form.id && !form.config_ref" />
|
||||||
<BaseInput v-model="form.api_base" label="API Endpoint / Group ID" placeholder="https://... 或 Group ID" />
|
<BaseInput v-model="form.api_base" label="API 地址 / 分组 ID" placeholder="https://... 或 Group ID" />
|
||||||
<BaseInput v-model="form.config_ref" label="Fallback Env Var" placeholder="如: OPENAI_API_KEY (高级)" />
|
<BaseInput v-model="form.config_ref" label="兜底环境变量" placeholder="如: OPENAI_API_KEY (高级)" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MiniMax Specific Config -->
|
<!-- MiniMax Specific Config -->
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ watch([selectedWindow, selectedCapability], () => {
|
|||||||
>
|
>
|
||||||
<div class="flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between">
|
<div class="flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-xl font-bold text-gray-800">Provider 运营摘要</h2>
|
<h2 class="text-xl font-bold text-gray-800">供应商运营摘要</h2>
|
||||||
<p class="mt-2 text-sm leading-6 text-gray-500">
|
<p class="mt-2 text-sm leading-6 text-gray-500">
|
||||||
生成、资源补全和失败恢复留下的供应商调用轨迹。
|
生成、资源补全和失败恢复留下的供应商调用轨迹。
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -2,6 +2,55 @@
|
|||||||
|
|
||||||
这份记录用于演示前快速说明“当前本地 Docker 环境已经验证到什么程度”。新的验证记录按时间倒序追加。
|
这份记录用于演示前快速说明“当前本地 Docker 环境已经验证到什么程度”。新的验证记录按时间倒序追加。
|
||||||
|
|
||||||
|
## 2026-04-28 拉取后回归与 Voice Studio 文案收敛
|
||||||
|
|
||||||
|
- 已拉取远端 `main` 到 `55ca098 Add voice analytics filters and metrics` 后完成本地回归。
|
||||||
|
- 后端复用仓库内 Windows `.venv` 执行全量测试,`118 passed`。
|
||||||
|
- 后端 `ruff check app/ tests/` 通过。
|
||||||
|
- 用户端与管理端 `npm run build` 均通过;依赖和文案收敛后再次构建通过,且不再出现 `baseline-browser-mapping` 数据偏旧提示。
|
||||||
|
- Voice Studio、生成轨迹、故事库和供应商管理页已将用户可见的 `session`、`turn`、`attention`、`fallback`、`Finalize`、`Provider` 等工程词收敛为中文表达,并补充转写来源、语音事件类型和事件状态的中文展示。
|
||||||
|
- 用户端与管理端执行依赖安全收敛后,`vite` 升至 `6.4.2`,`esbuild` 升至 `0.25.12`,`autoprefixer` 升至 `10.5.0`,`postcss` 升至 `8.5.12`,`baseline-browser-mapping` 升至 `2.10.23`。
|
||||||
|
- 用户端与管理端完整 `npm audit --registry=https://registry.npmjs.org` 均为 0 vulnerabilities。
|
||||||
|
- Alembic 当前只有一个 head:`0013_add_voice_sessions_phase_a`;迁移链从 `0012_story_text_status` 到 head 连续。
|
||||||
|
- `scripts/demo_smoke.sh` shell 语法检查通过,`curl` 与 `jq` 可用。
|
||||||
|
- 当前 WSL 发行版未启用 Docker Desktop 集成,且本地 `52000/52800/52080` 未监听;本轮无法执行完整 `SMOKE_VOICE=1 ./scripts/demo_smoke.sh`。
|
||||||
|
|
||||||
|
验证命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend && .venv/Scripts/python.exe -m pytest
|
||||||
|
cd backend && .venv/Scripts/python.exe -m ruff check app/ tests/
|
||||||
|
cd frontend && npm run build
|
||||||
|
cd admin-frontend && npm run build
|
||||||
|
cd frontend && npm audit fix --registry=https://registry.npmjs.org
|
||||||
|
cd admin-frontend && npm audit fix --registry=https://registry.npmjs.org
|
||||||
|
cd frontend && npm install autoprefixer@latest -D --registry=https://registry.npmjs.org
|
||||||
|
cd admin-frontend && npm install autoprefixer@latest -D --registry=https://registry.npmjs.org
|
||||||
|
cd frontend && npm install vite@^6.4.2 -D --registry=https://registry.npmjs.org
|
||||||
|
cd admin-frontend && npm install vite@^6.4.2 -D --registry=https://registry.npmjs.org
|
||||||
|
cd frontend && npm audit --omit=dev --registry=https://registry.npmjs.org
|
||||||
|
cd admin-frontend && npm audit --omit=dev --registry=https://registry.npmjs.org
|
||||||
|
cd frontend && npm audit --registry=https://registry.npmjs.org
|
||||||
|
cd admin-frontend && npm audit --registry=https://registry.npmjs.org
|
||||||
|
cd backend && .venv/Scripts/python.exe -m compileall -q app tests
|
||||||
|
cd backend && .venv/Scripts/python.exe -m alembic heads
|
||||||
|
cd backend && .venv/Scripts/python.exe -m alembic history --verbose -r 0012_story_text_status:head
|
||||||
|
bash -n scripts/demo_smoke.sh
|
||||||
|
git diff --check
|
||||||
|
docker compose config --quiet
|
||||||
|
curl -fsS --max-time 2 http://localhost:52000/health
|
||||||
|
curl -fsS --max-time 2 http://localhost:52800/health
|
||||||
|
curl -fsS --max-time 2 http://localhost:52080/health
|
||||||
|
```
|
||||||
|
|
||||||
|
结果:
|
||||||
|
|
||||||
|
- `pytest` 通过,118 passed,耗时约 4 分 25 秒。
|
||||||
|
- 后端 lint、Python compileall、用户端构建、管理端构建均通过;代码/文档 diff 空白检查通过,lockfile 保持仓库既有 CRLF 行尾风格。
|
||||||
|
- 用户端与管理端构建不再出现 `baseline-browser-mapping` 数据偏旧提示。
|
||||||
|
- 用户端与管理端完整 audit 均返回 0 vulnerabilities。
|
||||||
|
- `docker compose config --quiet` 因当前 WSL 找不到 `docker` 命令未执行成功;完整 Docker demo smoke 待启用 Docker Desktop WSL 集成后补跑。
|
||||||
|
|
||||||
## 2026-04-24
|
## 2026-04-24
|
||||||
|
|
||||||
补充验证:
|
补充验证:
|
||||||
|
|||||||
693
frontend/package-lock.json
generated
693
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,11 +18,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.1.0",
|
"@vitejs/plugin-vue": "^5.1.0",
|
||||||
"autoprefixer": "^10.4.0",
|
"autoprefixer": "^10.5.0",
|
||||||
"postcss": "^8.4.0",
|
"postcss": "^8.4.0",
|
||||||
"tailwindcss": "^3.4.0",
|
"tailwindcss": "^3.4.0",
|
||||||
"typescript": "^5.6.0",
|
"typescript": "^5.6.0",
|
||||||
"vite": "^5.4.0",
|
"vite": "^6.4.2",
|
||||||
"vue-tsc": "^2.1.0"
|
"vue-tsc": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const props = withDefaults(
|
|||||||
{
|
{
|
||||||
tone: 'light',
|
tone: 'light',
|
||||||
title: '生成轨迹',
|
title: '生成轨迹',
|
||||||
description: '每次生成和资源重试都会留下事件流,便于确认当前结果来自哪次任务、Provider 是否成功、失败是否可恢复。',
|
description: '每次生成和资源重试都会留下事件流,便于确认当前结果来自哪次任务、供应商是否成功、失败是否可恢复。',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -119,9 +119,9 @@ function getEventLabel(eventType: string) {
|
|||||||
audio_started: '音频开始',
|
audio_started: '音频开始',
|
||||||
audio_succeeded: '音频就绪',
|
audio_succeeded: '音频就绪',
|
||||||
audio_failed: '音频失败',
|
audio_failed: '音频失败',
|
||||||
provider_call_started: 'Provider 调用',
|
provider_call_started: '供应商调用',
|
||||||
provider_call_succeeded: 'Provider 成功',
|
provider_call_succeeded: '供应商成功',
|
||||||
provider_call_failed: 'Provider 失败',
|
provider_call_failed: '供应商失败',
|
||||||
asset_retry_started: '资源重试开始',
|
asset_retry_started: '资源重试开始',
|
||||||
asset_retry_completed: '资源重试完成',
|
asset_retry_completed: '资源重试完成',
|
||||||
asset_retry_failed: '资源重试失败',
|
asset_retry_failed: '资源重试失败',
|
||||||
@@ -301,7 +301,7 @@ defineExpose({ refresh })
|
|||||||
class="grid gap-3 md:grid-cols-4"
|
class="grid gap-3 md:grid-cols-4"
|
||||||
>
|
>
|
||||||
<div class="rounded-lg border p-3" :class="panelClass">
|
<div class="rounded-lg border p-3" :class="panelClass">
|
||||||
<div class="text-xs" :class="mutedTextClass">Provider 成功率</div>
|
<div class="text-xs" :class="mutedTextClass">供应商成功率</div>
|
||||||
<div class="mt-1 text-xl font-semibold">{{ providerSuccessRate }}%</div>
|
<div class="mt-1 text-xl font-semibold">{{ providerSuccessRate }}%</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border p-3" :class="panelClass">
|
<div class="rounded-lg border p-3" :class="panelClass">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<header class="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
|
<header class="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold gradient-text">引擎调度中心</h1>
|
<h1 class="text-3xl font-bold gradient-text">引擎调度中心</h1>
|
||||||
<p class="text-sm text-gray-500 mt-1">Provider Orchestration & Strategy</p>
|
<p class="text-sm text-gray-500 mt-1">供应商编排与策略</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="bg-blue-50 text-blue-700 px-3 py-1 rounded-full text-xs font-medium flex items-center gap-1">
|
<div class="bg-blue-50 text-blue-700 px-3 py-1 rounded-full text-xs font-medium flex items-center gap-1">
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
|
|
||||||
<BaseCard padding="md" title="可用驱动 (Adapters)">
|
<BaseCard padding="md" title="可用驱动">
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span v-for="adapter in availableAdapters" :key="adapter"
|
<span v-for="adapter in availableAdapters" :key="adapter"
|
||||||
class="px-2 py-1 text-xs bg-indigo-50 text-indigo-700 rounded-full border border-indigo-100">
|
class="px-2 py-1 text-xs bg-indigo-50 text-indigo-700 rounded-full border border-indigo-100">
|
||||||
@@ -154,21 +154,21 @@
|
|||||||
|
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="form.adapter"
|
v-model="form.adapter"
|
||||||
label="驱动程序 (Adapter)"
|
label="驱动程序"
|
||||||
:options="adapterOptions"
|
:options="adapterOptions"
|
||||||
required
|
required
|
||||||
description="选择底层的 API 驱动协议"
|
description="选择底层的 API 驱动协议"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BaseInput v-model="form.model" label="模型名称 (Model)" placeholder="如: gpt-4o, minimax-v2" description="具体调用的模型ID" />
|
<BaseInput v-model="form.model" label="模型名称" placeholder="如: gpt-4o, minimax-v2" description="具体调用的模型 ID" />
|
||||||
|
|
||||||
<BaseInput v-model.number="form.priority" label="优先级 (0-100)" type="number" description="数字越大越优先" />
|
<BaseInput v-model.number="form.priority" label="优先级 (0-100)" type="number" description="数字越大越优先" />
|
||||||
|
|
||||||
<div class="md:col-span-2 p-4 bg-gray-50 rounded-xl border border-gray-100 space-y-4">
|
<div class="md:col-span-2 p-4 bg-gray-50 rounded-xl border border-gray-100 space-y-4">
|
||||||
<h3 class="text-sm font-bold text-gray-700">密钥与连接</h3>
|
<h3 class="text-sm font-bold text-gray-700">密钥与连接</h3>
|
||||||
<BaseInput v-model="form.api_key" label="API Key" type="password" placeholder="留空则使用 .env 配置" :required="!form.id && !form.config_ref" />
|
<BaseInput v-model="form.api_key" label="API 密钥" type="password" placeholder="留空则使用 .env 配置" :required="!form.id && !form.config_ref" />
|
||||||
<BaseInput v-model="form.api_base" label="API Endpoint / Group ID" placeholder="https://... 或 Group ID" />
|
<BaseInput v-model="form.api_base" label="API 地址 / 分组 ID" placeholder="https://... 或 Group ID" />
|
||||||
<BaseInput v-model="form.config_ref" label="Fallback Env Var" placeholder="如: OPENAI_API_KEY (高级)" />
|
<BaseInput v-model="form.config_ref" label="兜底环境变量" placeholder="如: OPENAI_API_KEY (高级)" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="md:col-span-2 flex justify-end gap-3 pt-4 border-t border-gray-100">
|
<div class="md:col-span-2 flex justify-end gap-3 pt-4 border-t border-gray-100">
|
||||||
|
|||||||
@@ -395,13 +395,13 @@ watch([selectedWindow, selectedCapability, selectedVoiceWindow], () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border border-white/80 bg-white px-3 py-3">
|
<div class="rounded-lg border border-white/80 bg-white px-3 py-3">
|
||||||
<div class="text-xs text-gray-500">Finalize 转化率</div>
|
<div class="text-xs text-gray-500">保存转化率</div>
|
||||||
<div class="mt-1 text-lg font-semibold text-emerald-700">
|
<div class="mt-1 text-lg font-semibold text-emerald-700">
|
||||||
{{ voiceFinalizeRate }}%
|
{{ voiceFinalizeRate }}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border border-white/80 bg-white px-3 py-3">
|
<div class="rounded-lg border border-white/80 bg-white px-3 py-3">
|
||||||
<div class="text-xs text-gray-500">ASR / TTS 失败</div>
|
<div class="text-xs text-gray-500">转写 / 语音失败</div>
|
||||||
<div class="mt-1 text-lg font-semibold text-gray-800">
|
<div class="mt-1 text-lg font-semibold text-gray-800">
|
||||||
{{ voiceAnalytics.asr_failures }} / {{ voiceAnalytics.tts_failures }}
|
{{ voiceAnalytics.asr_failures }} / {{ voiceAnalytics.tts_failures }}
|
||||||
</div>
|
</div>
|
||||||
@@ -446,7 +446,7 @@ watch([selectedWindow, selectedCapability, selectedVoiceWindow], () => {
|
|||||||
>
|
>
|
||||||
<div class="flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between">
|
<div class="flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-xl font-bold text-gray-800">Provider 运营摘要</h2>
|
<h2 class="text-xl font-bold text-gray-800">供应商运营摘要</h2>
|
||||||
<p class="mt-2 text-sm leading-6 text-gray-500">
|
<p class="mt-2 text-sm leading-6 text-gray-500">
|
||||||
最近生成和资源补全留下的供应商调用轨迹。
|
最近生成和资源补全留下的供应商调用轨迹。
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -116,10 +116,10 @@ const universeOptions = computed(() =>
|
|||||||
universes.value.map((universe) => ({ value: universe.id, label: universe.name })),
|
universes.value.map((universe) => ({ value: universe.id, label: universe.name })),
|
||||||
)
|
)
|
||||||
const analyticsProviderOptions = [
|
const analyticsProviderOptions = [
|
||||||
{ value: 'fallback', label: '文本 fallback' },
|
{ value: 'fallback', label: '文本兜底' },
|
||||||
{ value: 'demo', label: 'Demo ASR' },
|
{ value: 'demo', label: '演示转写' },
|
||||||
{ value: 'openai', label: 'OpenAI ASR' },
|
{ value: 'openai', label: 'OpenAI 转写' },
|
||||||
{ value: 'openai_asr', label: 'OpenAI ASR Adapter' },
|
{ value: 'openai_asr', label: 'OpenAI 转写适配器' },
|
||||||
]
|
]
|
||||||
const analyticsStatusOptions = [
|
const analyticsStatusOptions = [
|
||||||
{ value: 'draft', label: '草稿' },
|
{ value: 'draft', label: '草稿' },
|
||||||
@@ -333,7 +333,7 @@ const transcriptionProviderSummary = computed(() => {
|
|||||||
const counts = voiceAnalytics.value?.transcription_provider_counts ?? {}
|
const counts = voiceAnalytics.value?.transcription_provider_counts ?? {}
|
||||||
const entries = Object.entries(counts).sort((left, right) => right[1] - left[1])
|
const entries = Object.entries(counts).sort((left, right) => right[1] - left[1])
|
||||||
if (!entries.length) return '暂无转写来源'
|
if (!entries.length) return '暂无转写来源'
|
||||||
return entries.map(([provider, count]) => `${provider} ${count} 次`).join(',')
|
return entries.map(([provider, count]) => `${formatTranscriptionProvider(provider)} ${count} 次`).join(',')
|
||||||
})
|
})
|
||||||
const analyticsWindowLabel = computed(() =>
|
const analyticsWindowLabel = computed(() =>
|
||||||
formatAnalyticsWindowLabel(voiceAnalytics.value?.window_days ?? null),
|
formatAnalyticsWindowLabel(voiceAnalytics.value?.window_days ?? null),
|
||||||
@@ -345,7 +345,7 @@ const transcriptionModeDescription = computed(() => {
|
|||||||
case 'disabled':
|
case 'disabled':
|
||||||
return '当前环境禁用了真实语音转写,请先使用文本共创或填写开发转写提示。'
|
return '当前环境禁用了真实语音转写,请先使用文本共创或填写开发转写提示。'
|
||||||
default:
|
default:
|
||||||
return '当前默认是 demo 转写模式。若本地未接真实 ASR,可在下方填写转写提示辅助开发验证。'
|
return '当前默认是演示转写模式。若本地未接真实 ASR,可在下方填写转写提示辅助开发验证。'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const isSessionProcessing = computed(
|
const isSessionProcessing = computed(
|
||||||
@@ -438,6 +438,69 @@ function formatConfidence(value: number | null | undefined) {
|
|||||||
return `${Math.round(value * 100)}%`
|
return `${Math.round(value * 100)}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatTranscriptionProvider(provider: string | null | undefined) {
|
||||||
|
switch (provider) {
|
||||||
|
case 'fallback':
|
||||||
|
return '文本兜底'
|
||||||
|
case 'demo':
|
||||||
|
return '演示转写'
|
||||||
|
case 'openai':
|
||||||
|
return 'OpenAI 转写'
|
||||||
|
case 'openai_asr':
|
||||||
|
return 'OpenAI 转写适配器'
|
||||||
|
case 'unknown':
|
||||||
|
return '未知来源'
|
||||||
|
default:
|
||||||
|
return provider || '未知来源'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatVoiceEventType(eventType: string) {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
assistant_audio_failed: '助手语音生成失败',
|
||||||
|
assistant_audio_ready: '助手语音已就绪',
|
||||||
|
assistant_audio_retry_failed: '助手语音重试失败',
|
||||||
|
assistant_audio_retry_succeeded: '助手语音重试成功',
|
||||||
|
assistant_text_ready: '助手文本已就绪',
|
||||||
|
intent_resolved: '意图已识别',
|
||||||
|
safety_intervention_requested: '触发安全介入',
|
||||||
|
session_abandoned: '会话已放弃',
|
||||||
|
session_cover_generation_failed: '封面补全失败',
|
||||||
|
session_cover_generation_queued: '封面补全已排队',
|
||||||
|
session_created: '会话已创建',
|
||||||
|
session_failed: '会话处理失败',
|
||||||
|
session_finalizing: '会话保存中',
|
||||||
|
session_saved_as_story: '会话已保存为故事',
|
||||||
|
story_patch_applied: '故事内容已更新',
|
||||||
|
turn_audio_uploaded: '用户录音已上传',
|
||||||
|
turn_confirmation_accepted: '家长已确认',
|
||||||
|
turn_confirmation_requested: '需要家长确认',
|
||||||
|
turn_confirmation_retry_recording: '选择重新录音',
|
||||||
|
turn_confirmation_switch_to_text: '选择改为文本',
|
||||||
|
turn_received: '已收到新回合',
|
||||||
|
turn_transcribed: '转写已完成',
|
||||||
|
turn_transcription_failed: '转写失败',
|
||||||
|
}
|
||||||
|
return labels[eventType] ?? eventType
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatVoiceEventStatus(status: string) {
|
||||||
|
switch (status) {
|
||||||
|
case 'received':
|
||||||
|
return '已接收'
|
||||||
|
case 'succeeded':
|
||||||
|
return '成功'
|
||||||
|
case 'failed':
|
||||||
|
return '失败'
|
||||||
|
case 'blocked':
|
||||||
|
return '已拦截'
|
||||||
|
case 'processing':
|
||||||
|
return '处理中'
|
||||||
|
default:
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function truncateNarrative(value: string | null, maxLength = 90) {
|
function truncateNarrative(value: string | null, maxLength = 90) {
|
||||||
if (!value) return null
|
if (!value) return null
|
||||||
const normalized = value.replace(/\s+/g, ' ').trim()
|
const normalized = value.replace(/\s+/g, ' ').trim()
|
||||||
@@ -1467,7 +1530,7 @@ onBeforeUnmount(() => {
|
|||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg font-semibold text-gray-900">最近会话</h2>
|
<h2 class="text-lg font-semibold text-gray-900">最近会话</h2>
|
||||||
<p class="mt-1 text-sm text-gray-500">优先把需要家长确认或安全回看的 session 先拎出来处理。</p>
|
<p class="mt-1 text-sm text-gray-500">优先把需要家长确认或安全回看的会话先拎出来处理。</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-gray-400">{{ filteredSessions.length }} 个</span>
|
<span class="text-xs text-gray-400">{{ filteredSessions.length }} 个</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1689,7 +1752,7 @@ onBeforeUnmount(() => {
|
|||||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
<div class="text-sm leading-6">
|
<div class="text-sm leading-6">
|
||||||
{{ attentionCompletionNotice.completedReasonLabel }} 已处理完。
|
{{ attentionCompletionNotice.completedReasonLabel }} 已处理完。
|
||||||
你可以继续切到下一类 attention,或先回到最近会话总览。
|
你可以继续切到下一类关注事项,或先回到最近会话总览。
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<button
|
<button
|
||||||
@@ -1777,7 +1840,7 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-4 grid grid-cols-2 gap-3 xl:grid-cols-4">
|
<div class="mt-4 grid grid-cols-2 gap-3 xl:grid-cols-4">
|
||||||
<div class="rounded-xl border border-gray-100 bg-gray-50 px-4 py-3">
|
<div class="rounded-xl border border-gray-100 bg-gray-50 px-4 py-3">
|
||||||
<div class="text-xs text-gray-500">Turn 成功率</div>
|
<div class="text-xs text-gray-500">回合成功率</div>
|
||||||
<div class="mt-1 text-lg font-semibold text-gray-900">{{ turnSuccessRateLabel }}</div>
|
<div class="mt-1 text-lg font-semibold text-gray-900">{{ turnSuccessRateLabel }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-xl border border-gray-100 bg-gray-50 px-4 py-3">
|
<div class="rounded-xl border border-gray-100 bg-gray-50 px-4 py-3">
|
||||||
@@ -1790,7 +1853,7 @@ onBeforeUnmount(() => {
|
|||||||
<div class="mt-1 text-lg font-semibold text-rose-700">{{ voiceAnalytics.safety_interventions }}</div>
|
<div class="mt-1 text-lg font-semibold text-rose-700">{{ voiceAnalytics.safety_interventions }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-xl border border-gray-100 bg-gray-50 px-4 py-3">
|
<div class="rounded-xl border border-gray-100 bg-gray-50 px-4 py-3">
|
||||||
<div class="text-xs text-gray-500">Finalize 转化率</div>
|
<div class="text-xs text-gray-500">保存转化率</div>
|
||||||
<div class="mt-1 text-lg font-semibold text-emerald-700">{{ finalizeConversionRateLabel }}</div>
|
<div class="mt-1 text-lg font-semibold text-emerald-700">{{ finalizeConversionRateLabel }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-xl border border-gray-100 bg-gray-50 px-4 py-3">
|
<div class="rounded-xl border border-gray-100 bg-gray-50 px-4 py-3">
|
||||||
@@ -2216,7 +2279,7 @@ onBeforeUnmount(() => {
|
|||||||
<div id="voice-text-composer" class="rounded-2xl border border-gray-100 bg-white p-4">
|
<div id="voice-text-composer" class="rounded-2xl border border-gray-100 bg-white p-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h3 class="font-semibold text-gray-900">文本共创回合</h3>
|
<h3 class="font-semibold text-gray-900">文本共创回合</h3>
|
||||||
<span class="text-xs text-gray-400">最稳的 fallback 路径</span>
|
<span class="text-xs text-gray-400">最稳的文本兜底路径</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 space-y-4">
|
<div class="mt-4 space-y-4">
|
||||||
<BaseTextarea
|
<BaseTextarea
|
||||||
@@ -2241,7 +2304,7 @@ onBeforeUnmount(() => {
|
|||||||
<div class="rounded-2xl border border-gray-100 bg-white p-4">
|
<div class="rounded-2xl border border-gray-100 bg-white p-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h3 class="font-semibold text-gray-900">录音共创回合</h3>
|
<h3 class="font-semibold text-gray-900">录音共创回合</h3>
|
||||||
<span class="text-xs text-gray-400">已支持上传音频 turn</span>
|
<span class="text-xs text-gray-400">已支持上传音频回合</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-sm text-gray-500">
|
<p class="mt-2 text-sm text-gray-500">
|
||||||
{{ transcriptionModeDescription }}
|
{{ transcriptionModeDescription }}
|
||||||
@@ -2313,7 +2376,7 @@ onBeforeUnmount(() => {
|
|||||||
<div class="rounded-2xl border border-gray-100 bg-white p-4">
|
<div class="rounded-2xl border border-gray-100 bg-white p-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h3 class="font-semibold text-gray-900">共创过程</h3>
|
<h3 class="font-semibold text-gray-900">共创过程</h3>
|
||||||
<span class="text-xs text-gray-400">{{ activeTurnList.length }} 条最近 turn</span>
|
<span class="text-xs text-gray-400">{{ activeTurnList.length }} 条最近回合</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 space-y-4">
|
<div class="mt-4 space-y-4">
|
||||||
@@ -2329,7 +2392,7 @@ onBeforeUnmount(() => {
|
|||||||
<span>{{ formatTurnStatus(turn.status) }}</span>
|
<span>{{ formatTurnStatus(turn.status) }}</span>
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
<span>{{ formatIntent(turn.detected_intent) }}</span>
|
<span>{{ formatIntent(turn.detected_intent) }}</span>
|
||||||
<span v-if="turn.transcription_provider">· {{ turn.transcription_provider }}</span>
|
<span v-if="turn.transcription_provider">· {{ formatTranscriptionProvider(turn.transcription_provider) }}</span>
|
||||||
<span v-if="turn.user_audio_duration_ms">· 用户语音 {{ formatDurationMs(turn.user_audio_duration_ms) }}</span>
|
<span v-if="turn.user_audio_duration_ms">· 用户语音 {{ formatDurationMs(turn.user_audio_duration_ms) }}</span>
|
||||||
<span v-if="turn.assistant_audio_duration_ms">· 助手语音 {{ formatDurationMs(turn.assistant_audio_duration_ms) }}</span>
|
<span v-if="turn.assistant_audio_duration_ms">· 助手语音 {{ formatDurationMs(turn.assistant_audio_duration_ms) }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -2458,11 +2521,11 @@ onBeforeUnmount(() => {
|
|||||||
class="rounded-xl border border-gray-100 bg-gray-50 px-3 py-3"
|
class="rounded-xl border border-gray-100 bg-gray-50 px-3 py-3"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between gap-3 text-xs text-gray-400">
|
<div class="flex items-center justify-between gap-3 text-xs text-gray-400">
|
||||||
<span>{{ event.event_type }}</span>
|
<span>{{ formatVoiceEventType(event.event_type) }}</span>
|
||||||
<span>{{ formatDate(event.created_at) }}</span>
|
<span>{{ formatDate(event.created_at) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-sm font-medium text-gray-800">
|
<div class="mt-1 text-sm font-medium text-gray-800">
|
||||||
{{ event.message || event.status }}
|
{{ event.message || formatVoiceEventStatus(event.status) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2475,7 +2538,7 @@ onBeforeUnmount(() => {
|
|||||||
<EmptyState
|
<EmptyState
|
||||||
:icon="SparklesIcon"
|
:icon="SparklesIcon"
|
||||||
title="创建或恢复一个语音共创会话"
|
title="创建或恢复一个语音共创会话"
|
||||||
description="左侧可以直接创建新会话,也可以恢复最近仍在等待下一轮的 session。"
|
description="左侧可以直接创建新会话,也可以恢复最近仍在等待下一轮的会话。"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user