Merge pull request #1 from let5sne/codex/h5-scenario-redesign-20260706
Align H5 scenario training design
@@ -55,7 +55,7 @@
|
||||
- 资料处理:`frontend/src/views/knowledge/processing.vue`,路由 `/knowledge/processing`。
|
||||
- 模型配置:`frontend/src/views/system/model/index.vue`,路由 `/system/model`。
|
||||
- MVP 阶段侧栏只保留“首页 / AI面试 / 三角色对练 / 案例沉淀 / SOP知识库 / 资料处理 / 系统设置-模型配置”。过滤逻辑在 `frontend/src/layout/components/Sidebar/index.vue`,不要在未明确要求时恢复若依默认全量菜单。
|
||||
- 移动端三端首页:`mobile/src/App.vue`,路由 `/h5/user`、`/h5/candidate`、`/h5/supervisor`,分别对应用户/员工端、候选人端、主管端;兼容 `/h5/employee`、`/h5/admin`,但移动端不要把主管端等同后台系统管理员。员工端首页是「学/复盘/用/报/成长」五个情境区;底部 tab 是整页互斥切换(isHomeTab + activeUserPanel/activeSupervisorPanel 条件),**不要把面板改回插入首页长页面的叠加渲染**;未实现入口必须走 `tap()` 的「建设中」toast,不留无反应按钮。查 SOP 全屏层(sopPanel)调 `/api/knowledge/search`(category='sop' 全库),关闭后回到进入前 tab。
|
||||
- 移动端三端首页:`mobile/src/App.vue`,路由 `/h5/user`、`/h5/candidate`、`/h5/supervisor`,分别对应用户/员工端、候选人端、主管端;兼容 `/h5/employee`、`/h5/admin`,但移动端不要把主管端等同后台系统管理员。员工端首页是「师傅区→今日安排→我的地图→我的成长摘要」(视觉基准 `docs/feedback/2026070423/design/` + REVIEW.md 修正项,修正项优先于图面);师傅四状态(idle/listening/thinking/speaking)只从 `sopRecording/sopLoading/sopSpeaking` 推导,**不要新造状态源**;员工端底部 tab 钉死「今日/练/问/我」(visibleTabs 强制,不跟随后端 home 旧文案),整页互斥切换(isHomeTab + activeUserPanel/activeSupervisorPanel 条件),**不要把面板改回插入首页长页面的叠加渲染**;未实现入口进「建设中」整页(openBuildingPage),不留无反应按钮。查 SOP 全屏层(sopPanel)调 `/api/knowledge/search`(category='sop' 全库),关闭后回到进入前 tab;「生成总结卡」按需调 `/api/knowledge/summary-card`,失败明确报错可重试,不兜假数据。
|
||||
- 移动端首页接口:`GET /api/aihr/mobile/home/{role}`,当前 `@SaIgnore` 公开只读 seed,用于 H5 首屏 API 优先 + 本地 fallback;后续确定小程序登录后再接移动端 token,不复用管理后台登录态。
|
||||
- 移动端手机号登录:验证码发送走 `GET /resource/sms/code?phonenumber=...`,登录走 `POST /auth/mobile/sms-login`;短信发送复用 `sms.blends.config1` 阿里云配置。手机号不存在时自动注册 `app_user`,备注为“移动端短信自动注册”。模板 ID 用 `AIHR_SMS_LOGIN_TEMPLATE_ID`,AccessKey/Secret/签名用 `ALIYUN_SMS_ACCESS_KEY_ID`、`ALIYUN_SMS_ACCESS_KEY_SECRET`、`ALIYUN_SMS_SIGN_NAME`,本地放 `.env.local`。dev 兜底:`aihr.sms.dev-fixed-code` 非空则验证码固定(dev 默认 `123456`)、不真发短信;prod profile 代码级强制失效,不要移除该闸门。
|
||||
- 移动端员工训练闭环:员工端首页“开始训练”复用 `POST /api/train/practice/start`、`/turn`、`/finish`,请求必须带移动端登录返回的 `Authorization` 与 `clientid`;`mode=mobile` 完成后写入 `aihr_practice_session`,并更新主管端首页完训率、“待复盘对练”、员工训练历史和能力画像。主管端复盘详情查 `GET /api/aihr/mobile/practice/reviews/{id}`,标记复盘用 `POST /api/aihr/mobile/practice/reviews/{id}/reviewed`。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- 品牌:前端标题为“物业AI人力资源系统”,侧栏 logo 使用银城物业图标。
|
||||
- 已验证:租户列表、验证码、`000000 / admin / admin123` 加密登录链路。
|
||||
- 管理端 MVP:首页与五个关键页面已落地,路由为 `/index`、`/recruit/interview`、`/train/practice`、`/knowledge/cases`、`/knowledge/sop`、`/knowledge/processing`。
|
||||
- 移动端 MVP:三端首页已拆到独立工程 `mobile/`;路由为 `/h5/user`、`/h5/candidate`、`/h5/supervisor`,并兼容 `/h5/employee`、`/h5/admin`;已接手机号短信登录、`GET /api/aihr/mobile/home/{role}` 首页 API。员工端首页按「今天要学/练完要复盘/工作中要用/碰到问题要报/我的成长」五个情境区组织,底部 tab 整页切换;“开始训练”可完成两轮三角色对练,记录落 `aihr_practice_session` 并刷新主管端待复盘列表、复盘详情、标记已复盘状态和员工能力画像;「查 SOP」支持文本/语音提问并返回带引用片段的答案;候选人端“开始面试/面试练习”复用 AI 面试 API 拉题、作答、评分,“补充资料”走 OSS + `aihr_candidate_material` 入库,HR 可在 AI 面试页审核通过/驳回;前端保留本地 fallback。
|
||||
- 移动端 MVP:三端首页已拆到独立工程 `mobile/`;路由为 `/h5/user`、`/h5/candidate`、`/h5/supervisor`,并兼容 `/h5/employee`、`/h5/admin`;已接手机号短信登录、`GET /api/aihr/mobile/home/{role}` 首页 API。员工端首页为「数字师傅」形态(师傅头像四状态+按住说话/打字问+快捷chips→今日安排→我的地图四色块→成长摘要,视觉基准 `docs/feedback/2026070423/design/`),底部 tab 今日/练/问/我整页切换;“开始训练”可完成两轮三角色对练,记录落 `aihr_practice_session` 并刷新主管端待复盘列表、复盘详情、标记已复盘状态和员工能力画像;「查 SOP」文本/语音提问返回结构化概要+引用片段,可按需生成信息图总结卡(听一遍/开始跟读);候选人端“开始面试/面试练习”复用 AI 面试 API 拉题、作答、评分,“补充资料”走 OSS + `aihr_candidate_material` 入库,HR 可在 AI 面试页审核通过/驳回;前端保留本地 fallback。
|
||||
- 英雄路径:AI 面试(真模型出题/评分)、三角色对练(真 LLM 客户回复+评分,asr/tts 配置后支持语音)、案例沉淀(真实语音上传+ASR 转写+模型整理)均为真模型优先、未配置或失败回退本地兜底;SOP 知识库已接中文关键词 + MySQL Fulltext + Qdrant 混合召回 + rerank 重排,支持 txt/md/PDF/Word/Excel/PPT/图片/视频上传解析入库(图片走 vision OCR,视频走 ffmpeg 抽音轨转写+关键帧描述);模型配置页可查看向量库维度/点数并一键重建。
|
||||
- 资料处理:已新增解析任务状态页,按 `aihr_knowledge_attach` 和 `aihr_knowledge_fragment` 展示等待解析、解析中、已完成、失败、片段数和向量化状态;页面支持多文件/浏览器目录选择,也支持从后端 `AIHR_IMPORT_ROOT` 下启动后台目录导入任务、轮询进度并取消运行中任务。
|
||||
- 侧栏:MVP 阶段只展示“首页 / AI面试 / 三角色对练 / 案例沉淀 / SOP知识库 / 资料处理 / 系统设置-模型配置”,其余若依默认菜单暂时隐藏。
|
||||
|
||||
@@ -7,6 +7,8 @@ import org.dromara.aihr.domain.AihrSopDto.LocalImportTaskResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.ProcessingOverviewResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.SearchRequest;
|
||||
import org.dromara.aihr.domain.AihrSopDto.SearchResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.SummaryCardRequest;
|
||||
import org.dromara.aihr.domain.AihrSopDto.SummaryCardResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.UploadEnqueueResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.UploadItemResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.UploadResponse;
|
||||
@@ -61,6 +63,11 @@ public class AihrSopController {
|
||||
return R.ok(sopSeedService.search(request));
|
||||
}
|
||||
|
||||
@PostMapping("/summary-card")
|
||||
public R<SummaryCardResponse> summaryCard(@RequestBody SummaryCardRequest request) {
|
||||
return R.ok(sopSeedService.summaryCard(request));
|
||||
}
|
||||
|
||||
@PostMapping("/doc/upload")
|
||||
public R<UploadResponse> uploadDoc(@RequestPart("file") MultipartFile file, @RequestParam("category") String category) {
|
||||
return R.ok(sopSeedService.uploadDoc(file, category));
|
||||
|
||||
@@ -10,6 +10,18 @@ public final class AihrSopDto {
|
||||
public record SearchRequest(String queryText, String category, Integer limit) {
|
||||
}
|
||||
|
||||
public record SummaryCardRequest(String queryText, String category) {
|
||||
}
|
||||
|
||||
public record SummaryCardResponse(String title, List<CardStep> steps, List<CardObjection> objections, List<String> scripts, List<String> reminders) {
|
||||
}
|
||||
|
||||
public record CardStep(String label, String desc) {
|
||||
}
|
||||
|
||||
public record CardObjection(String concern, String response) {
|
||||
}
|
||||
|
||||
public record UploadResponse(String docId, Long ossId, String fileName, String category, Integer fragments, String summary, List<String> tags, List<SnippetResponse> snippets) {
|
||||
}
|
||||
|
||||
@@ -65,7 +77,9 @@ public final class AihrSopDto {
|
||||
List<DocResponse> docs,
|
||||
List<SnippetResponse> snippets,
|
||||
List<String> training,
|
||||
List<RecordResponse> records
|
||||
List<RecordResponse> records,
|
||||
List<String> keyPoints,
|
||||
List<String> cautions
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.aihr.domain.AihrSopDto.CategoryStatResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.CardObjection;
|
||||
import org.dromara.aihr.domain.AihrSopDto.CardStep;
|
||||
import org.dromara.aihr.domain.AihrSopDto.DocResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.LocalImportItemResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.LocalImportRequest;
|
||||
@@ -21,6 +23,8 @@ import org.dromara.aihr.domain.AihrSopDto.RecordResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.SearchRequest;
|
||||
import org.dromara.aihr.domain.AihrSopDto.SearchResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.SnippetResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.SummaryCardRequest;
|
||||
import org.dromara.aihr.domain.AihrSopDto.SummaryCardResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.UploadResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.VectorIndexStatusResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.VectorizeResponse;
|
||||
@@ -123,10 +127,61 @@ public class AihrSopSeedService {
|
||||
seed.docs(),
|
||||
seed.snippets(),
|
||||
seed.training(),
|
||||
records()
|
||||
records(),
|
||||
List.of(),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
public SummaryCardResponse summaryCard(SummaryCardRequest request) {
|
||||
String queryText = request == null ? "" : Optional.ofNullable(request.queryText()).orElse("").trim();
|
||||
if (isBlank(queryText)) {
|
||||
throw new ServiceException("请先输入问题");
|
||||
}
|
||||
String category = request == null ? "sop" : firstNonBlank(request.category(), "sop");
|
||||
List<KnowledgeHit> hits;
|
||||
try {
|
||||
hits = dbHits(dbCategory(category), queryText, 5);
|
||||
} catch (DataAccessException e) {
|
||||
hits = List.of();
|
||||
}
|
||||
if (hits.isEmpty()) {
|
||||
throw new ServiceException("知识库中未检索到相关内容");
|
||||
}
|
||||
Optional<ChatRuntime> runtime = chatRuntime();
|
||||
if (runtime.isEmpty()) {
|
||||
throw new ServiceException("未配置对话模型,无法生成总结卡");
|
||||
}
|
||||
|
||||
StringBuilder context = new StringBuilder();
|
||||
int index = 1;
|
||||
for (KnowledgeHit hit : hits.stream().limit(5).toList()) {
|
||||
context.append("片段").append(index++).append("(来源:").append(hit.title()).append("):\n")
|
||||
.append(truncate(hit.content(), 1500)).append("\n\n");
|
||||
}
|
||||
try {
|
||||
String content = callSummaryCardModel(runtime.get(), queryText, context.toString());
|
||||
JsonNode root = objectMapper.readTree(jsonObjectText(content));
|
||||
String title = truncate(root.path("title").asText("").strip(), 14);
|
||||
List<CardStep> steps = jsonCardSteps(root.path("steps"));
|
||||
if (isBlank(title) || steps.isEmpty()) {
|
||||
throw new IllegalStateException("summary card missing title or steps");
|
||||
}
|
||||
return new SummaryCardResponse(
|
||||
title,
|
||||
steps,
|
||||
jsonCardObjections(root.path("objections")),
|
||||
jsonStrings(root.path("scripts"), 3, 90),
|
||||
jsonStrings(root.path("reminders"), 5, 20)
|
||||
);
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("summary card failed: {}", e.getMessage());
|
||||
throw new ServiceException("总结卡生成失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
public UploadResponse uploadDoc(MultipartFile file, String category) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new ServiceException("上传文件不能为空");
|
||||
@@ -1565,10 +1620,7 @@ public class AihrSopSeedService {
|
||||
|
||||
private SearchResponse dbSearch(String category, String queryText, Integer limit) {
|
||||
try {
|
||||
int limitValue = limitValue(limit);
|
||||
List<KnowledgeHit> keywordHits = likeHits(category, queryText, limitValue);
|
||||
List<KnowledgeHit> textHits = rrf(keywordHits, fulltextHits(category, queryText, limitValue), limitValue);
|
||||
List<KnowledgeHit> hits = rerankHits(queryText, rrf(textHits, vectorHits(category, queryText, limitValue), limitValue));
|
||||
List<KnowledgeHit> hits = dbHits(category, queryText, limit);
|
||||
if (hits.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@@ -1578,6 +1630,13 @@ public class AihrSopSeedService {
|
||||
}
|
||||
}
|
||||
|
||||
private List<KnowledgeHit> dbHits(String category, String queryText, Integer limit) {
|
||||
int limitValue = limitValue(limit);
|
||||
List<KnowledgeHit> keywordHits = likeHits(category, queryText, limitValue);
|
||||
List<KnowledgeHit> textHits = rrf(keywordHits, fulltextHits(category, queryText, limitValue), limitValue);
|
||||
return rerankHits(queryText, rrf(textHits, vectorHits(category, queryText, limitValue), limitValue));
|
||||
}
|
||||
|
||||
private List<KnowledgeHit> vectorHits(String category, String queryText, int limit) {
|
||||
for (EmbeddingRuntime runtime : embeddingRuntimes()) {
|
||||
try {
|
||||
@@ -1726,7 +1785,7 @@ public class AihrSopSeedService {
|
||||
}
|
||||
}
|
||||
|
||||
private static SearchResponse responseFromHits(String category, String queryText, List<KnowledgeHit> hits) {
|
||||
private SearchResponse responseFromHits(String category, String queryText, List<KnowledgeHit> hits) {
|
||||
KnowledgeHit first = hits.get(0);
|
||||
String responseCategory = isBlank(category) ? first.category() : category;
|
||||
List<SnippetResponse> snippets = hits.stream()
|
||||
@@ -1736,18 +1795,203 @@ public class AihrSopSeedService {
|
||||
for (KnowledgeHit hit : hits) {
|
||||
docs.putIfAbsent(hit.title(), new DocResponse(hit.title(), hit.description(), "命中 " + scoreText(hit.score()), "pill-success"));
|
||||
}
|
||||
SearchDigest digest = searchDigest(queryText, hits);
|
||||
return new SearchResponse(
|
||||
queryText,
|
||||
responseCategory,
|
||||
"根据已入库 SOP:" + first.content(),
|
||||
digest != null ? digest.answer() : "根据已入库 SOP:" + first.content(),
|
||||
"引用 " + docs.size() + " 份文档 / " + snippets.size() + " 个片段",
|
||||
List.copyOf(docs.values()),
|
||||
snippets,
|
||||
List.of("题 1:请按命中 SOP 复述处理步骤。", "题 2:哪些信息必须留痕并可追溯?"),
|
||||
records()
|
||||
records(),
|
||||
digest != null ? digest.keyPoints() : List.of(),
|
||||
digest != null ? digest.cautions() : List.of()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* RAG 生成环节:把命中片段交给 chat 模型压成「直接回答+要点+注意事项」结构化概要。
|
||||
* 模型未配置、超时或解析失败返回 null,由调用方回退片段原文拼接——检索永不因概要失败而报错。
|
||||
*/
|
||||
private SearchDigest searchDigest(String queryText, List<KnowledgeHit> hits) {
|
||||
Optional<ChatRuntime> runtime = chatRuntime();
|
||||
if (runtime.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder context = new StringBuilder();
|
||||
int index = 1;
|
||||
for (KnowledgeHit hit : hits.stream().limit(5).toList()) {
|
||||
context.append("片段").append(index++).append("(来源:").append(hit.title()).append("):\n")
|
||||
.append(truncate(hit.content(), 1500)).append("\n\n");
|
||||
}
|
||||
try {
|
||||
String content = callDigestModel(runtime.get(), queryText, context.toString());
|
||||
JsonNode root = objectMapper.readTree(jsonObjectText(content));
|
||||
String answer = root.path("answer").asText("");
|
||||
if (isBlank(answer)) {
|
||||
return null;
|
||||
}
|
||||
return new SearchDigest(
|
||||
truncate(answer, 200),
|
||||
jsonStrings(root.path("keyPoints"), 6, 120),
|
||||
jsonStrings(root.path("cautions"), 3, 120)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("search digest failed, keep raw answer: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String callDigestModel(ChatRuntime runtime, String queryText, String context) throws Exception {
|
||||
ObjectNode body = objectMapper.createObjectNode();
|
||||
body.put("model", runtime.modelName());
|
||||
body.put("temperature", 0);
|
||||
body.put("stream", false);
|
||||
|
||||
ArrayNode messages = body.putArray("messages");
|
||||
ObjectNode system = messages.addObject();
|
||||
system.put("role", "system");
|
||||
system.put("content", "你是物业一线员工的SOP助手。只返回 JSON,不要 markdown。字段固定为 answer, keyPoints, cautions。"
|
||||
+ "answer 是对问题的直接回答(不超过60字,含关键数字/时限);keyPoints 是3到6条可执行要点(每条一句话,含具体数字);"
|
||||
+ "cautions 是0到3条注意事项或红线。硬约束:只能基于给定片段内容作答,片段中没有的信息不得编造;"
|
||||
+ "若片段无法回答问题,answer 固定为「未在已入库 SOP 中找到明确依据」且 keyPoints 为空数组。");
|
||||
|
||||
ObjectNode user = messages.addObject();
|
||||
user.put("role", "user");
|
||||
user.put("content", """
|
||||
员工的问题:%s
|
||||
|
||||
检索命中的 SOP 片段:
|
||||
%s
|
||||
""".formatted(queryText, context));
|
||||
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(normalizeBaseUrl(runtime.baseUrl()) + "/chat/completions"))
|
||||
.timeout(Duration.ofSeconds(15))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)));
|
||||
if (!isBlank(runtime.apiKey())) {
|
||||
builder.header("Authorization", "Bearer " + runtime.apiKey());
|
||||
}
|
||||
HttpResponse<String> response = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build()
|
||||
.send(builder.build(), HttpResponse.BodyHandlers.ofString());
|
||||
if (!ok(response.statusCode())) {
|
||||
throw new IllegalStateException("digest HTTP " + response.statusCode());
|
||||
}
|
||||
JsonNode choices = objectMapper.readTree(response.body()).path("choices");
|
||||
if (!choices.isArray() || choices.isEmpty()) {
|
||||
throw new IllegalStateException("digest response missing choices");
|
||||
}
|
||||
return choices.get(0).path("message").path("content").asText();
|
||||
}
|
||||
|
||||
private String callSummaryCardModel(ChatRuntime runtime, String queryText, String context) throws Exception {
|
||||
ObjectNode body = objectMapper.createObjectNode();
|
||||
body.put("model", runtime.modelName());
|
||||
body.put("temperature", 0);
|
||||
body.put("stream", false);
|
||||
|
||||
ArrayNode messages = body.putArray("messages");
|
||||
ObjectNode system = messages.addObject();
|
||||
system.put("role", "system");
|
||||
system.put("content", """
|
||||
你是物业一线员工的SOP信息图总结助手。只返回 JSON,不要 markdown、代码块或解释。
|
||||
输出格式固定为:
|
||||
{"title":"XX总结卡","steps":[{"label":"步骤名","desc":"一句话"}],"objections":[{"concern":"异议","response":"应对"}],"scripts":["可直接开口的完整话术"],"reminders":["注意事项"]}
|
||||
字段约束:title ≤14字;steps 3-5条,label ≤6字,desc ≤20字;objections 2-4条,concern ≤8字,response ≤20字;scripts 2-3条;reminders 3-5条,每条≤20字。
|
||||
硬约束:只能基于给定片段内容归纳,不得编造片段中没有的流程、时限、承诺、金额、政策或话术。
|
||||
""");
|
||||
|
||||
ObjectNode user = messages.addObject();
|
||||
user.put("role", "user");
|
||||
user.put("content", """
|
||||
员工的问题:%s
|
||||
|
||||
检索命中的知识片段:
|
||||
%s
|
||||
""".formatted(queryText, context));
|
||||
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(normalizeBaseUrl(runtime.baseUrl()) + "/chat/completions"))
|
||||
.timeout(Duration.ofSeconds(30))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)));
|
||||
if (!isBlank(runtime.apiKey())) {
|
||||
builder.header("Authorization", "Bearer " + runtime.apiKey());
|
||||
}
|
||||
HttpResponse<String> response = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build()
|
||||
.send(builder.build(), HttpResponse.BodyHandlers.ofString());
|
||||
if (!ok(response.statusCode())) {
|
||||
throw new IllegalStateException("summary card HTTP " + response.statusCode());
|
||||
}
|
||||
JsonNode choices = objectMapper.readTree(response.body()).path("choices");
|
||||
if (!choices.isArray() || choices.isEmpty()) {
|
||||
throw new IllegalStateException("summary card response missing choices");
|
||||
}
|
||||
return choices.get(0).path("message").path("content").asText();
|
||||
}
|
||||
|
||||
private static List<String> jsonStrings(JsonNode node, int maxItems, int maxChars) {
|
||||
if (!node.isArray()) {
|
||||
return List.of();
|
||||
}
|
||||
List<String> values = new ArrayList<>();
|
||||
for (JsonNode item : node) {
|
||||
String text = item.asText("").strip();
|
||||
if (!text.isEmpty()) {
|
||||
values.add(truncate(text, maxChars));
|
||||
}
|
||||
if (values.size() >= maxItems) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return List.copyOf(values);
|
||||
}
|
||||
|
||||
private static List<CardStep> jsonCardSteps(JsonNode node) {
|
||||
if (!node.isArray()) {
|
||||
return List.of();
|
||||
}
|
||||
List<CardStep> values = new ArrayList<>();
|
||||
for (JsonNode item : node) {
|
||||
String label = item.path("label").asText("").strip();
|
||||
String desc = item.path("desc").asText("").strip();
|
||||
if (!label.isEmpty() && !desc.isEmpty()) {
|
||||
values.add(new CardStep(truncate(label, 6), truncate(desc, 20)));
|
||||
}
|
||||
if (values.size() >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return List.copyOf(values);
|
||||
}
|
||||
|
||||
private static List<CardObjection> jsonCardObjections(JsonNode node) {
|
||||
if (!node.isArray()) {
|
||||
return List.of();
|
||||
}
|
||||
List<CardObjection> values = new ArrayList<>();
|
||||
for (JsonNode item : node) {
|
||||
String concern = item.path("concern").asText("").strip();
|
||||
String response = item.path("response").asText("").strip();
|
||||
if (!concern.isEmpty() && !response.isEmpty()) {
|
||||
values.add(new CardObjection(truncate(concern, 8), truncate(response, 20)));
|
||||
}
|
||||
if (values.size() >= 4) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return List.copyOf(values);
|
||||
}
|
||||
|
||||
private record SearchDigest(String answer, List<String> keyPoints, List<String> cautions) {
|
||||
}
|
||||
|
||||
private static String dbCategory(String category) {
|
||||
return "sop".equalsIgnoreCase(Optional.ofNullable(category).orElse("").trim()) ? "" : category;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
# AI 陪练二期开发推进计划
|
||||
|
||||
> 版本:v0.2 | 日期:2026-07-06
|
||||
> 输入源:《AI陪练功能优化与缺口分析(修订版 · 2026-07-06)》+《物业行业 AI 人力资源系统 · BRD v1.1》。
|
||||
> v0.2 修订:与两份输入源逐项复核后补齐 6 处缺漏——#6/#9/#16/师徒演练的明确处置(§2)、#13 定向再练的里程碑落点(M4)、画像雷达图与 BRD L1「留」段验收承载(M4)、TechSpec P1 表映射与岗位身份前置(M1)、场景管理权限口径(M1/§7)、G6 成本上限量化(§7)。
|
||||
> 结论:二期不做“24 个缺口全量铺开”,先把 AI 陪练从 MVP 演示闭环推进到**生活顾问岗位试点可用**。
|
||||
|
||||
## 1. 二期目标
|
||||
|
||||
二期的交付主题是:围绕“生活顾问”岗位,把 AI 陪练做成可训练、可复盘、可派发、可衡量的“数字师傅”训练闭环。
|
||||
|
||||
业务上承接 BRD 的“育 / 用 / 留”三段:
|
||||
|
||||
| BRD 主线 | 二期落点 |
|
||||
|---|---|
|
||||
| 用 | 岗位 SOP 与训练场景绑定,训练前给预习卡和红线话术 |
|
||||
| 育 | 语音对手戏、多场景训练、求助、打断纠偏、训练记录 |
|
||||
| 留 | 画像扩展、复盘标注、主管预警、定向再练 |
|
||||
|
||||
二期不追求招聘、薪酬、绩效、知识图谱、原生 APP 的全量扩张。AI 分数仍定位为“辅助参考,人可否决”。
|
||||
|
||||
## 2. 范围切割
|
||||
|
||||
### 必做
|
||||
|
||||
| 工作包 | 覆盖缺口 | 价值 |
|
||||
|---|---|---|
|
||||
| 场景地基 | #1 #2 #3 #13 #17 #19 | 把代码 seed 迁到表,让岗位、场景、分发可运营 |
|
||||
| 对练体验增强 | #4 #5 #7 #8 #10 | 训练前有准备、训练中有情绪变化和纠偏、语音体验更像真实业主 |
|
||||
| 复盘与记录 | #11 #12 #14 #20 | 训练后能回放、标注、人工校准,形成可复盘资产 |
|
||||
| 画像与主管侧 | #15 #17 #19 | 员工看成长,主管看风险并派专项 |
|
||||
| 数据治理底线 | #22 #23 | Rubric 有版本,文本入库前脱敏,为试点和合规留余地 |
|
||||
|
||||
### 暂缓
|
||||
|
||||
| 暂缓项 | 原因 |
|
||||
|---|---|
|
||||
| 实时主管旁听(#18) | WebSocket + 低延迟音频工程量大;先做事后回放 |
|
||||
| 录像存储 | 涉员工隐私和 G3/G4,二期只存录音 |
|
||||
| 完整离线对练(#24) | 无 LLM 不可离线;只做 SOP/预习/题目缓存 |
|
||||
| 纯视觉向量检索 | 依赖素材规模和供应商合规;现有关键帧 caption 已可覆盖大部分检索 |
|
||||
| 原生 APP、SSO、知识图谱、绩效挂钩 | BRD 已明确为二期后或生产闸门项 |
|
||||
| 图片/视频情境输入(#9) | BRD 4.3.3 明列(拍照报修),vision 模型已就绪;但场景素材是瓶颈,待 12 条文本场景跑稳后独立评估,可与 B3 文生图联动 |
|
||||
| 环境噪音模拟(#6) | 前端混音成本不高但非试点闭环必需;噪音音源授权未解决 |
|
||||
| 自动案例沉淀(#16) | 挂 BRD 4.6/4.7 共创与 review_task 审核链;二期只保证"贡献度"有人工计分入口(见 M4),自动沉淀待案例量起来再评估 |
|
||||
| 师徒演练(BRD 4.4) | 依赖 mentor_assignment(P1)与真人师父运营;二期由"主管复盘 + 派发"承载师徒最小语义 |
|
||||
| 动态难度调级(#1 后半 / BRD 4.5) | 场景表保留 difficulty 字段即可;自动升降级依赖画像数据稳定,分发规则 v1 不做 |
|
||||
|
||||
## 3. 技术路线
|
||||
|
||||
### 3.1 数据地基先行
|
||||
|
||||
二期第一优先级是把训练运营对象落库。当前现状是 2 个 seed 场景 + `aihr_practice_session` 汇总表,足够演示,不够试点。
|
||||
|
||||
建议新增或补齐以下表,统一使用 `aihr_*` 前缀:
|
||||
|
||||
| 表 | 用途 |
|
||||
|---|---|
|
||||
| `aihr_practice_scenario` | 训练场景、岗位、项目类型、难度、人设、SOP 绑定 |
|
||||
| `aihr_practice_rubric` / `aihr_practice_rubric_dimension` | Rubric 版本、维度、权重、启停 |
|
||||
| `aihr_practice_assignment` | 主管/规则引擎派发每日小训、专项训练 |
|
||||
| `aihr_practice_annotation` | finish 后逐句标注、好/差示例、证据 turn |
|
||||
| `aihr_practice_calibration` | 人工修正评分,沉淀一致率校准集 |
|
||||
| `aihr_practice_audio` 或 `audio_oss_id` 字段 | 对练录音 MinIO/sys_oss 回放 |
|
||||
|
||||
注意:旧 TechSpec 中出现过 `practice_scenario` 等无前缀名称,二期实现时应同步修正文档或在 DDL 中明确采用 `aihr_practice_*`,避免和若依/未来模块冲突。
|
||||
|
||||
与 TechSpec P1 表的映射(不另建平行体系):
|
||||
|
||||
- `aihr_practice_assignment` 吸收 `daily_drill`(每日一练)与 `training_camp`(专项训练营)的语义,用 `source`(daily/camp/retry/manual)+ `reason` 字段区分来源,不再单独建这两张表。
|
||||
- `mistake_book`(错题本)语义由“低分维度 → 定向再练派发”承载(见 M4),`assignment.source='retry'` 即错题重练记录。
|
||||
- 跨期五维画像仍走 TechSpec M6 的 `competency_assessment`,本计划只做单场四维分与四项画像聚合,勿与跨期评估混成一个数(BACKLOG B5 口径)。
|
||||
|
||||
### 3.2 复用现有能力
|
||||
|
||||
- 语音识别继续复用 `POST /api/ai/asr`,不新建移动端专用 ASR。
|
||||
- TTS 继续复用 `POST /api/ai/tts`,二期把 `voice` 扩成 `voice_profile`。
|
||||
- 文件与录音存储复用 `sys_oss` + MinIO。
|
||||
- SOP 求助复用 `/api/knowledge/search` 和 `/api/knowledge/summary-card`。
|
||||
- 模型配置继续走 `aihr_model_config` 的 `chat/vector/rerank/asr/tts/vision` 分类。
|
||||
- 员工端训练仍复用 `/api/train/practice/start|turn|finish`,不要另起一套移动端训练后端。
|
||||
|
||||
## 4. 里程碑计划
|
||||
|
||||
### M0. 二期基线冻结(2 天)
|
||||
|
||||
目标:确认二期不再追 MVP 演示壳,而是追试点可用闭环。
|
||||
|
||||
- 锁定首个岗位:生活顾问。
|
||||
- 锁定首批场景:投诉处理、催费/停车费、报修跟进、突发求助。
|
||||
- 每类至少 3 条场景,二期首批共 12 条。
|
||||
- 每条场景必须有:人设、开场、成功条件、雷点/爽点、SOP 引用、Rubric。
|
||||
- 明确试点指标:20 名员工、每人 10 次对练、完训率 80%、满意度 4/5。
|
||||
|
||||
验收:
|
||||
|
||||
- `docs/BACKLOG.md` 和本计划无冲突。
|
||||
- 内容负责人能提供或确认 12 条场景素材。
|
||||
- 开发不再把“实时旁听、录像、知识图谱、原生 APP”拉进二期。
|
||||
|
||||
### M1. 场景与 Rubric 地基(第 1-2 周)
|
||||
|
||||
目标:让“场景管理”和“评分标准版本”从代码 seed 变成可运营数据。
|
||||
|
||||
后端:
|
||||
|
||||
- `backend/script/sql/aihr_practice_mysql8.sql`:补二期表或新增同目录二期 DDL。
|
||||
- `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java`:补 scenario/rubric/assignment DTO。
|
||||
- 新增 `AihrPracticeScenarioService` 或扩展 `AihrPracticeSeedService`:优先读表,失败回退 seed。
|
||||
- `AihrPracticeController`:新增场景列表、详情、保存、启停、Rubric 查询接口。
|
||||
- `scripts/reset-dev-db.sh`:导入新 DDL 和 12 条生活顾问 seed。
|
||||
|
||||
管理端:
|
||||
|
||||
- `frontend/src/api/aihr/practice.ts`:补场景/Rubric API。
|
||||
- `frontend/src/views/train/practice.vue`:先加轻量场景选择和 Rubric 预览,不做复杂 CRUD。
|
||||
|
||||
移动端:
|
||||
|
||||
- `mobile/src/App.vue`:“开始训练”根据岗位/今日安排传 `scenarioId`,没有数据时回退当前 seed。
|
||||
|
||||
验收:
|
||||
|
||||
- 管理端能选择不同生活顾问场景并开始对练。
|
||||
- API 可返回场景绑定的 SOP、Rubric、persona。
|
||||
- 模型失败或表为空时,原 MVP 对练仍可跑。
|
||||
- 场景 CRUD 接口挂 `hr_operator` 角色权限(BACKLOG B1“出题”职责),普通员工端只读。
|
||||
- 员工进入训练时能按登录身份取到岗位(`position`),场景列表按岗位过滤——这是 M4 派发与分发规则的身份前置。
|
||||
|
||||
### M2. 训练前与训练中增强(第 3-4 周)
|
||||
|
||||
目标:把体验从“回合制答题”推进到“有准备、有情绪、有求助”的语音对手戏。
|
||||
|
||||
功能:
|
||||
|
||||
- 训练前预习卡:从场景 SOP 生成“要点 / 红线 / 可说话术”。
|
||||
- 每轮 emotion 由 LLM 输出并落入 turn/session,不再只用 seed 锚点。
|
||||
- 求助按钮:训练中弹出该场景 SOP 提示,记录一次求助事件。
|
||||
- 主动打断:COACH_CHECK 命中红线时中断本轮,提示纠正后重来。
|
||||
- 方言/多语气:先支持 `voice_profile.speed/emotion/dialect`,不做语音克隆。
|
||||
|
||||
文件落点:
|
||||
|
||||
- `AihrPracticeLlmService.java`:扩展 turn 输出 JSON,包含 `emotion/trustDelta/redFlag/coachHint`。
|
||||
- `AihrPracticeSeedService.java`:seed fallback 也返回同样字段。
|
||||
- `AihrSpeechDto.java` / `AihrSpeechService.java`:扩展 TTS voice 参数,保留旧字符串兼容。
|
||||
- `frontend/src/views/train/practice.vue`:加预习卡、情绪条、求助按钮、红线提示。
|
||||
- `mobile/src/App.vue`:复用训练前卡和求助逻辑,语音失败仍退文本。
|
||||
|
||||
验收:
|
||||
|
||||
- 员工开始训练前能看到该场景预习卡。
|
||||
- 至少 3 条场景能看到情绪随话术变化。
|
||||
- 求助按钮命中 SOP 并显示引用。
|
||||
- 红线话术触发纠偏,但不在训练中做逐句评分。
|
||||
|
||||
### M3. 录音回放与复盘标注(第 5 周)
|
||||
|
||||
目标:训练完成后能复盘,但不做实时旁听。
|
||||
|
||||
功能:
|
||||
|
||||
- 训练中员工音频和 TTS 音频按需存 MinIO,记录 `oss_id`。
|
||||
- 主管复盘详情展示音频回放。
|
||||
- finish 后对整场 dialogue 做一次性逐句标注,输出“问题句 / 推荐改写 / 命中或漏掉的 SOP 点”。
|
||||
- 标杆对话先用导师改写生成“更优示例”,不等案例库自然积累。
|
||||
|
||||
文件落点:
|
||||
|
||||
- `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java`:保存 session 时写 audio/annotation 信息。
|
||||
- 新增 `AihrPracticeReviewService.java`:生成和查询逐句标注。
|
||||
- `AihrMobileController.java`:复盘详情返回 audio 和 annotations。
|
||||
- `mobile/src/App.vue`:主管复盘详情增加回放和逐句标注区。
|
||||
|
||||
验收:
|
||||
|
||||
- 完成一场训练后,主管端能打开回放。
|
||||
- 逐句标注只在 finish 后生成一次,刷新不重复算分。
|
||||
- 没有录音权限时仍保存文本 transcript。
|
||||
|
||||
### M4. 画像、校准与主管派发(第 6-7 周)
|
||||
|
||||
目标:把训练结果变成员工成长与主管管理动作。
|
||||
|
||||
功能:
|
||||
|
||||
- 员工画像扩展为四项:训练时长、贡献度、测评分、AI 等级(BACKLOG B5;贡献度二期用人工计分入口记 `incentive_point`,不做自动案例沉淀)。
|
||||
- 员工端画像以雷达图 + 趋势呈现单场四维分聚合(BRD 4.5 可视化口径,数据留跨期 `competency_assessment` 的入口)。
|
||||
- 主管端预警:连续低分、超期未训、分数下滑。
|
||||
- 批量训练任务派发:主管按岗位/短板派发专项。
|
||||
- 定向再练(#13):低分维度自动生成 `source='retry'` 的待训任务,员工“今日安排”可见——错题本语义的最小实现。
|
||||
- 人工评分校准:HR/主管可修正 AI 评分,形成校准集。
|
||||
- 训练数据脱敏:入库前处理姓名、手机号、房号等明显 PII。
|
||||
|
||||
文件落点:
|
||||
|
||||
- `AihrMobileSeedService.java`:扩展 `profile()` 聚合。
|
||||
- `AihrMobileDto.java`:补画像、预警、派发 DTO。
|
||||
- `AihrMobileController.java`:新增/扩展主管预警、派发接口。
|
||||
- `frontend/src/views/train/practice.vue` 或新增管理端页:提供人工校准入口。
|
||||
- `mobile/src/App.vue`:员工画像和主管待办展示新增字段。
|
||||
|
||||
验收:
|
||||
|
||||
- 员工端能看到四项画像,不做同侪排名。
|
||||
- 主管端能看到待处理预警并派发训练。
|
||||
- 低分场景完成后,员工待训列表出现对应的再练任务。
|
||||
- 人工修正评分后,保留原 AI 分和修正分。
|
||||
- 入库文本中明显手机号/房号被脱敏。
|
||||
|
||||
### M5. 试点收口(第 8 周)
|
||||
|
||||
目标:二期可进入 1-2 个住宅项目小范围试点。
|
||||
|
||||
验收清单:
|
||||
|
||||
- 生活顾问 12 条场景可用。
|
||||
- 员工从“今日安排”进入训练,完成后画像和主管端同步。
|
||||
- 主管能复盘、听录音、看逐句标注、派发专项。
|
||||
- AI 与人工评分分档一致率用不少于 20 条样本评估,目标 ≥70%。
|
||||
- 住宅类 SOP 问答人工评审可用率目标 ≥80%。
|
||||
- 语音单轮响应目标 ≤5 秒;超过时有文本降级。
|
||||
- 试点数据可导出:训练次数、完训率、平均分、预警数、主管复盘率。
|
||||
|
||||
## 5. 二期 API 建议
|
||||
|
||||
| 能力 | 接口 |
|
||||
|---|---|
|
||||
| 场景列表 | `GET /api/train/practice/scenarios?position=&scenarioType=` |
|
||||
| 场景详情 | `GET /api/train/practice/scenarios/{id}` |
|
||||
| 保存场景 | `POST /api/train/practice/scenarios` |
|
||||
| Rubric 版本 | `GET /api/train/practice/rubrics?scenarioId=` |
|
||||
| 训练预习卡 | `GET /api/train/practice/scenarios/{id}/prep-card` |
|
||||
| 逐句标注 | `POST /api/train/practice/sessions/{sessionId}/annotations` |
|
||||
| 评分校准 | `POST /api/train/practice/sessions/{sessionId}/calibration` |
|
||||
| 主管预警 | `GET /api/aihr/mobile/practice/alerts` |
|
||||
| 派发训练 | `POST /api/aihr/mobile/practice/assignments` |
|
||||
| 员工待训 | `GET /api/aihr/mobile/practice/assignments` |
|
||||
|
||||
这些接口都应保留 seed fallback 或空数据兜底,但真实验收必须以数据库链路为准。
|
||||
|
||||
## 6. 内容准备清单
|
||||
|
||||
二期能不能推进,最大风险不是代码,是场景内容。每个场景至少准备:
|
||||
|
||||
| 字段 | 示例 |
|
||||
|---|---|
|
||||
| 岗位 | 生活顾问 |
|
||||
| 场景类型 | 投诉 / 催费 / 报修 / 突发求助 |
|
||||
| 业主人设 | 年龄、身份、说话风格、情绪、偏见 |
|
||||
| 开场白 | “你们物业到底管不管楼上噪音?” |
|
||||
| 显性诉求 | 投诉噪音 |
|
||||
| 隐性诉求 | 想确认物业是否愿意持续跟进 |
|
||||
| 雷点 | 被敷衍、先讲规定、推给业主自己协调 |
|
||||
| 爽点 | 先共情、给首次反馈时间、明确责任人 |
|
||||
| SOP 引用 | 对应知识库片段 ID |
|
||||
| Rubric | 合规、沟通、情绪、营销四维权重 |
|
||||
| 标杆话术 | 1 条优秀版本、1 条反例 |
|
||||
|
||||
## 7. 决策待确认
|
||||
|
||||
| 决策 | 推荐口径 |
|
||||
|---|---|
|
||||
| 二期时长 | 8 周,每两周一个可验收增量 |
|
||||
| 首个岗位 | 生活顾问,四保一服后置 |
|
||||
| 录音合规 | 二期只录音不录像;进入训练前给明确提示 |
|
||||
| 方言/TTS | 先扩 `voice_profile`,不做克隆;供应商稳定后再评估克隆 |
|
||||
| AI 排序 | 分发引擎 v1 用规则表,不上 AI 排序 |
|
||||
| 排名 | 先做自我对比和主管预警,不做同侪排名 |
|
||||
| 场景管理权限 | 场景/Rubric CRUD 归 `hr_operator` 与管理员;主管只派发不编场景 |
|
||||
| G6 成本上限 | 试点期设 LLM/ASR/TTS 月度调用金额上限与告警阈值(具体数额由 HR 负责人与供应商账单确认);超限降级为文本对练 + 关闭逐句标注 |
|
||||
|
||||
## 8. 主要风险
|
||||
|
||||
| 风险 | 控制 |
|
||||
|---|---|
|
||||
| 内容不足 | M0 先锁 12 条生活顾问场景,内容没到位不进 M2 |
|
||||
| 移动端过大 | M1 同步拆 `mobile/src/App.vue` 的训练/画像/主管面板,不继续堆单文件 |
|
||||
| 成本失控 | 逐句标注只在 finish 后一次性做;总结卡/视频按需触发 |
|
||||
| 评分争议 | 原 AI 分和人工修正分并存;AI 分不挂绩效 |
|
||||
| 隐私问题 | 只做录音,不做录像;文本脱敏;音频存储权限明确 |
|
||||
| 范围膨胀 | 实时旁听、原生 APP、知识图谱、纯视觉检索全部不进二期主线 |
|
||||
|
||||
## 9. 交付物
|
||||
|
||||
| 类型 | 交付 |
|
||||
|---|---|
|
||||
| 产品 | 员工端训练前卡、语音对手戏、求助、回放、画像;主管端复盘、预警、派发 |
|
||||
| 后端 | 场景/Rubric/Assignment/Annotation/Calibration 数据与 API |
|
||||
| 管理端 | 场景选择/轻管理、Rubric 预览、评分校准 |
|
||||
| 内容 | 12 条生活顾问场景、SOP 引用、Rubric、标杆话术 |
|
||||
| 验收 | 20 条评分校准样本、试点数据导出、演示脚本更新 |
|
||||
|
||||
## 10. 第一批实施任务
|
||||
|
||||
### Task 1: 二期 DDL 与 seed
|
||||
|
||||
**Files**
|
||||
|
||||
- Modify: `backend/script/sql/aihr_practice_mysql8.sql`
|
||||
- Modify: `scripts/reset-dev-db.sh`
|
||||
- Verify: `./scripts/dev.sh --reset`
|
||||
|
||||
**Steps**
|
||||
|
||||
- [ ] 增加 `aihr_practice_scenario`、`aihr_practice_rubric`、`aihr_practice_rubric_dimension`。
|
||||
- [ ] 写入 12 条生活顾问场景 seed。
|
||||
- [ ] reset 后确认场景表和 rubric 表有数据。
|
||||
|
||||
### Task 2: 场景读取替换 seed
|
||||
|
||||
**Files**
|
||||
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java`
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java`
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPracticeController.java`
|
||||
|
||||
**Steps**
|
||||
|
||||
- [ ] `start` 优先按 `scenarioId` 读数据库场景。
|
||||
- [ ] 数据库无场景时回退现有 seed。
|
||||
- [ ] `turn/finish` 保持当前契约不破坏前端。
|
||||
|
||||
### Task 3: 训练前卡和求助
|
||||
|
||||
**Files**
|
||||
|
||||
- Modify: `frontend/src/api/aihr/practice.ts`
|
||||
- Modify: `frontend/src/views/train/practice.vue`
|
||||
- Modify: `mobile/src/App.vue`
|
||||
- Reuse: `POST /api/knowledge/search`
|
||||
|
||||
**Steps**
|
||||
|
||||
- [ ] 开始训练前展示场景预习卡。
|
||||
- [ ] 训练中加“求助”入口,用场景 category/SOP 引用查知识库。
|
||||
- [ ] 求助失败显示明确错误,不兜假答案。
|
||||
|
||||
### Task 4: 动态情绪和红线纠偏
|
||||
|
||||
**Files**
|
||||
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeLlmService.java`
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java`
|
||||
- Modify: `frontend/src/views/train/practice.vue`
|
||||
- Modify: `mobile/src/App.vue`
|
||||
|
||||
**Steps**
|
||||
|
||||
- [ ] LLM turn 输出增加 `emotion/trust/redFlag/coachHint`。
|
||||
- [ ] 前端展示情绪条和红线纠偏。
|
||||
- [ ] 解析失败时回退现有 coachHint,不阻断训练。
|
||||
|
||||
### Task 5: 回放与逐句标注
|
||||
|
||||
**Files**
|
||||
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSpeechService.java`
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java`
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java`
|
||||
- Modify: `mobile/src/App.vue`
|
||||
|
||||
**Steps**
|
||||
|
||||
- [ ] 对练音频写 `sys_oss`/MinIO 并关联 session。
|
||||
- [ ] 主管复盘详情显示音频回放。
|
||||
- [ ] finish 后生成逐句标注,刷新不重复生成。
|
||||
|
||||
### Task 6: 画像与主管派发
|
||||
|
||||
**Files**
|
||||
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrMobileSeedService.java`
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/vo/AihrMobileDto.java`
|
||||
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java`
|
||||
- Modify: `mobile/src/App.vue`
|
||||
|
||||
**Steps**
|
||||
|
||||
- [ ] 画像补训练时长、贡献度、测评分、AI 等级。
|
||||
- [ ] 主管端补预警列表。
|
||||
- [ ] 主管可派发专项训练,员工端今日安排可见。
|
||||
|
||||
## 11. 二期完成定义
|
||||
|
||||
二期完成不是“所有缺口清零”,而是满足以下条件:
|
||||
|
||||
- 生活顾问岗位训练从 seed 演示变成数据库驱动。
|
||||
- 员工能完成“预习 → 语音/文本训练 → 求助 → 评分 → 复盘”的闭环。
|
||||
- 主管能完成“看风险 → 听回放 → 看标注 → 派专项”的闭环。
|
||||
- 画像能支撑“下一步练什么”的规则分发。
|
||||
- 试点所需的合规、成本、人工校准底线已经留好。
|
||||
@@ -0,0 +1,90 @@
|
||||
# AI 陪练功能优化与缺口分析(修订版 · 2026-07-06)
|
||||
|
||||
> 基于《AI陪练功能优化与缺口分析 补充功能.docx》逐条对照代码库核实修订。
|
||||
> 修订内容:更正 4 处现状盘点错误、划掉/降级 3 条已实现或半实现的"缺口"、
|
||||
> 标注 2 处与既定架构决策的冲突、每条缺口标注对应 BACKLOG/规格锚点。
|
||||
> 原 docx 保留归档;后续排期以本文为准。
|
||||
|
||||
## 一、现有功能盘点(已按代码实况更正)
|
||||
|
||||
| 能力 | 状态 | 更正说明 |
|
||||
|---|---|---|
|
||||
| 员工端对练 /start /turn /finish 闭环 | ✅ | 移动端带 Authorization+clientid,mode=mobile 落 `aihr_practice_session` |
|
||||
| AI 扮演业主(情绪/身份/诉求人设) | ✅ | 真 LLM 生成客户回复(seed 剧本作剧情锚点);**情绪值仍走 seed 锚点,未纳入 LLM 输出** |
|
||||
| 语音输入 ASR → LLM → TTS 播报 | ✅(部分) | **原文"多语气播报"系夸大**:TTS 单一默认音色,多语气/方言/语速=BACKLOG B2 待办 |
|
||||
| ASR 转写可编辑后提交 | ✅ | **原文漏计且缺口表误列为缺失**:转写先填入输入框,员工可修改再提交 |
|
||||
| 每轮 AI 教练提示(coachHint) | ✅ | **原文漏计**:每轮 turn 返回教练提示(卡壳/流程偏差提醒) |
|
||||
| 训练完成多维评分 | ✅ | 4 维:合规/沟通/情绪/营销 + 导师改写 + 点评;LLM temperature=0 结构化输出,评分仅 /finish 一次 |
|
||||
| 训练历史与画像聚合 | ✅(部分) | **原文"时长"系错误**:画像只聚合分数均值,无 duration 字段与时长统计(=BACKLOG B5 已记缺口) |
|
||||
| 主管端复盘列表/详情/导师改写/标记复盘 | ✅ | `GET/POST /api/aihr/mobile/practice/reviews*` |
|
||||
| 场景管理(后台配置) | ❌ | **原文错误**:场景为代码写死的 2 个 seed(投诉/催费),无管理端 CRUD,`practice_scenario` 表未建(TechSpec P0 表设计在,未实施) |
|
||||
|
||||
## 二、缺口分析(修订)
|
||||
|
||||
图例:🔴 确认缺口 | 🟡 半有(降级表述) | ⛔ 已实现(划掉) | ⚠️ 与既定决策冲突(按决策口径执行)
|
||||
|
||||
### (一)训练前侧
|
||||
|
||||
| # | 项 | 状态 | 修订说明与锚点 |
|
||||
|---|---|---|---|
|
||||
| 1 | 岗位能力模型配置(按岗位设维度权重、按职级设难度) | 🔴 | ≈ BACKLOG B1(数据权限/角色)+ B8(岗位×阶段);`practice_scenario.position` 字段 TechSpec 已设计。前置依赖:岗位身份(H5_REDESIGN §5) |
|
||||
| 2 | 智能训练计划引擎(每日微训练推送) | 🔴 | = B8 个性化分发引擎;蓝本已定 v1 用规则表不上 AI 排序。"推送"受限 H5 无推送通道,一期做首页「今日安排」拉取式 |
|
||||
| 3 | 场景分类筛选与搜索 | 🔴 | 前置:先把场景从代码 seed 迁到 `practice_scenario` 表并做管理端 CRUD(即补上"现状盘点"第 9 行缺口),再谈筛选 |
|
||||
| 4 | 训练前预习卡片 | 🟡 | **素材链路已通**:查SOP 结构化概要+总结卡即现成的"SOP要点+红线话术"卡;缺的只是挂到对练 /start 前的流程串接,前端活 |
|
||||
|
||||
### (二)训练中侧
|
||||
|
||||
| # | 项 | 状态 | 修订说明与锚点 |
|
||||
|---|---|---|---|
|
||||
| 5 | 业主情绪动态系统 + 情绪仪表盘 | 🟡 | 客户回复已随员工话术真实变化;缺口收窄为:①emotion 纳入 LLM 每轮输出(小改)②前端情绪仪表盘 UI(真增量)。勿按"从零建系统"排期 |
|
||||
| 6 | 环境噪音模拟 | 🔴 | 新增量。实现建议:TTS 播放时前端混音本地噪音音轨(Web Audio),不动后端;音源需授权素材 |
|
||||
| 7 | 训练中求助按钮 | 🔴 | 新增量。可复用查SOP检索,弹出该场景 SOP 提示;扣分策略进 rubric 配置 |
|
||||
| 8 | AI 教练主动打断/引导 | 🟡 | **原文定性"无"不实**:每轮已有 coachHint 轻提示;缺的是"红线触发→中断本轮→纠正后重来"机制(COACH_CHECK 规则判定已在 TechSpec 6 章设计内) |
|
||||
| 9 | 图片/视频情境输入 | 🔴 | 新增量。vision 模型已配(Qwen3-VL),技术就绪;场景素材是瓶颈,可与 B3 文生图联动 |
|
||||
| 10 | 方言切换 | 🔴 | = BACKLOG B2 既有项(方言 TTS/克隆);ASR 方言识别 SenseVoice 原生支持,评分侧不区分方言 |
|
||||
|
||||
### (三)训练后侧
|
||||
|
||||
| # | 项 | 状态 | 修订说明与锚点 |
|
||||
|---|---|---|---|
|
||||
| 11 | 逐句评分与标注 | ⚠️🔴 | **与 TechSpec §6 既定决策冲突**("每轮不评分,评分仅 /finish 一次",防漂移+G6 成本)。修订口径:**finish 后的复盘阶段对整场 dialogue 一次性逐句标注**(单次 LLM 调用),不做训练中逐句打分 |
|
||||
| 12 | 标杆对话对比(好/差示例并排) | 🔴 | 依赖案例库积累;"优秀示例"可先用导师改写充当,MVP 成本低 |
|
||||
| 13 | 薄弱点定向再练(变体场景) | 🔴 | ≈ B8 分发引擎的"错题重练";变体生成可用 chat 模型改写 persona(如愤怒→委屈) |
|
||||
| 14 | 训练全程录音存储与回放 | 🔴 | **文档独有亮点**。对练音频现用完即弃;存 MinIO 复用 sys_oss 链路即可。⚠️ 录像(摄像头)涉员工隐私,须过 G3/G4,建议一期只做录音 |
|
||||
| 15 | 画像扩展(成长曲线/雷达/排名/错题集) | 🔴 | = BACKLOG B5 + TechSpec M6(competency_assessment/mistake_book P1 表);排名功能注意 G4 接受度,建议先做"自我对比"不做"同侪排名" |
|
||||
| 16 | 自动案例沉淀(优秀/典型错误→教学案例) | 🔴 | 与 BRD 4.7 共创机制契合;复用案例审核链(review_task),脱敏走 G3 |
|
||||
|
||||
### (四)主管/管理侧
|
||||
|
||||
| # | 项 | 状态 | 修订说明与锚点 |
|
||||
|---|---|---|---|
|
||||
| 17 | 训练异常预警(连续不合格/超期未训/分数下滑) | 🔴 | ≈ B8 主管监控面板;H5 无推送,一期做主管端首页预警列表 |
|
||||
| 18 | 主管旁听/陪练模式 | 🔴 | **文档独有亮点**,契合"数字师傅+真师傅"带教叙事;实时旁听需 WebSocket,工程量大,建议先做"事后回放"(依赖 #14)再评估实时 |
|
||||
| 19 | 批量训练任务派发 | 🔴 | = B8 主管面板"指派专项"(设计基准 key-interactions 图已画);依赖任务表(TechSpec M5 daily_drill/training_camp P1) |
|
||||
| 20 | 评分校准工具(人工修正回流) | 🔴 | **文档独有亮点**;与 TechSpec"一致率≥70% 校准集+人工评审"(P1)同源,修正数据即校准集来源。挂钩 G1(AI 分仅辅助参考) |
|
||||
|
||||
### (五)技术/数据层
|
||||
|
||||
| # | 项 | 状态 | 修订说明与锚点 |
|
||||
|---|---|---|---|
|
||||
| 21 | 语音识别结果实时纠错 | ⛔ | **已实现**:对练页与查SOP 的 ASR 转写均先入文本框、可编辑后提交。整条划掉 |
|
||||
| 22 | 评分标准版本控制 | 🔴 | = TechSpec §6 既有设计(固定 prompt_version+评分缓存已实现前半);缺 rubric 后台配置+版本历史(M6 P1) |
|
||||
| 23 | 训练数据脱敏 | 🔴 | = 生产前闸门 G3 的落地项;对练文本涉业主房号/姓名时自动脱敏再入库 |
|
||||
| 24 | 离线训练包 | 🔴 | **文档独有亮点**,契合弱网铁律(H5_REDESIGN §1-6);但"离线对练"无 LLM 不可行,修订口径:**离线仅覆盖预习/SOP缓存/题目浏览**,对练必须在线,弱网降级文本 |
|
||||
|
||||
## 三、排期建议(按依赖与性价比)
|
||||
|
||||
1. **随手做**(依赖已就绪,单点小改):#4 预习卡串接、#5 emotion 入 LLM 输出、#7 求助按钮、#21(无需做)
|
||||
2. **P2 一并做**(挂 B2/B5 既有排期):#10 方言、#14 录音存储回放、#15 画像扩展、#12 标杆对比
|
||||
3. **P3 挂 B8**:#1/#2/#3/#13/#17/#19 —— 全部依赖"场景表落库+岗位身份+分发规则表"三块地基
|
||||
4. **独立评估**:#11 逐句标注(按修订口径)、#20 评分校准、#16 自动沉淀、#6 噪音、#9 图片情境
|
||||
5. **暂缓**:#18 实时旁听(先回放后实时)、#24 离线包(按修订口径收窄)、录像存储(G3/G4 未过)
|
||||
|
||||
## 四、原 docx 的错误清单(留档防再引用)
|
||||
|
||||
1. "TTS 多语气播报"——未实现,B2 待办
|
||||
2. "场景管理(后台配置训练场景)"——不存在,场景是代码 seed
|
||||
3. "画像聚合含时长"——无时长字段与聚合,B5 已记
|
||||
4. 漏计:每轮教练提示、ASR 可编辑提交(后者还被误列为缺口 #21)
|
||||
5. "逐句评分"未注意与 TechSpec §6"评分仅 finish 一次"决策的冲突
|
||||
6. "业主情绪动态"未反映真 LLM 已上线后的现状
|
||||
@@ -60,7 +60,8 @@ SOP 文档上传第三片已经落最小后端边界:
|
||||
| 缺失向量补跑 | `POST /api/knowledge/doc/vectorize-missing` | 扫描缺少 `embedding_json` 的片段,复用同一向量化与 Qdrant 入库链路,适合模型配置变更后或历史资料补齐向量 |
|
||||
| 向量库状态与重建 | `GET /api/knowledge/doc/vector-index-status`、`POST /api/knowledge/doc/rebuild-vector-index` | 模型配置页展示当前 vector 模型维度、Qdrant collection 维度、点数和片段向量数;维度不一致时可一键清空旧 embedding、删除 collection 并按当前模型重建 |
|
||||
| Qdrant 向量索引 | 同一上传接口内机会性执行 | embedding 写入 MySQL 后尽力 upsert 到 Qdrant;同名文档替换会尽力删除旧 points;Qdrant 不可用不影响上传和 MySQL 检索;外部 embedding 成功但 collection 维度不一致时不静默降级为 `local-hash-v1`,通过状态接口和重建入口处理 |
|
||||
| 混合检索 | `POST /api/knowledge/search` | 先跑中文关键词 `LIKE` 打分和 MySQL Fulltext,再生成 query embedding 走 Qdrant,RRF 融合后交给 `category='rerank'` 模型(如硅基流动 bge-reranker-v2-m3)按语义相关性重排;rerank 未配置或失败保持 RRF 顺序,Qdrant 或外部向量接口不可用时保留关键词/全文检索 |
|
||||
| 混合检索 | `POST /api/knowledge/search` | 先跑中文关键词 `LIKE` 打分和 MySQL Fulltext,再生成 query embedding 走 Qdrant,RRF 融合后交给 `category='rerank'` 模型(如硅基流动 bge-reranker-v2-m3)按语义相关性重排;rerank 未配置或失败保持 RRF 顺序,Qdrant 或外部向量接口不可用时保留关键词/全文检索。命中后若 chat 模型可用,自动生成结构化轻概要写入响应 `answer/keyPoints/cautions`(≤60字直答+3-6要点+0-3注意;temperature=0,15s 超时,硬约束不得编造、答不了明示无依据);概要失败回退片段原文拼接,检索不报错 |
|
||||
| 检索总结卡 | `POST /api/knowledge/summary-card`(JSON `{queryText, category}`) | 按需生成信息图式总结卡 `{title, steps[], objections[], scripts[], reminders[]}`:复用检索命中片段(最多5条)调 chat 模型,temperature=0、30s 超时;无命中/无 chat 模型/生成失败均抛明确错误供前端重试,不兜假数据。移动端查SOP结果页「生成总结卡」按钮触发,底部弹层展示并可 TTS 朗读 |
|
||||
| 解析状态聚合 | `GET /api/knowledge/processing/overview` | 聚合 `aihr_knowledge_attach.status`、fragment 数、embedding 数、`sys_oss.ext1.fileSize`,生成资料处理页指标、分类、任务、链路和事件列表 |
|
||||
| 服务端目录导入 | `POST /api/knowledge/doc/import-local` | JSON `{ directory, category, limit }`;`directory` 只能是 `AIHR_IMPORT_ROOT` / `aihr.import.root` 下的相对目录,默认根目录为 `./.data/import`;逐文件复用上传解析链路,同步执行,保留给小批量/调试 |
|
||||
| 服务端导入任务 | `POST /api/knowledge/doc/import-local-task`、`GET /api/knowledge/doc/import-tasks`、`POST /api/knowledge/doc/import-tasks/{id}/cancel` | 启动后台目录导入并返回任务;任务写入 `aihr_knowledge_import_task`,页面轮询查看总数、成功数、失败数、当前文件和进度;运行中任务可取消;重试当前按同目录重新启动一轮 |
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
- 方言 → 粤语/川话预置音色,或语音克隆接口用 ~15s 真实录音复刻(方言 TTS 本来就在一期集成清单第 4 项)
|
||||
- 吵架 → `emotion: angry` + `speed: 1.2`
|
||||
- 求救听不清 → TTS 出 `emotion: sad/fearful` 底音 + ffmpeg 后期加噪/压音量/断续("信道劣化"无现成 API,后期处理最可控)
|
||||
- **2026-07-04 反馈增补**(截图见 `feedback/2026070423/`,解读见其 ANALYSIS.md):
|
||||
- **连贯语音对手戏**:对练从"回合制答题"升级为实时情景对话——ASR→LLM(情绪人设+对话记忆)→TTS 低延迟循环,"很凶的老太太吵架"是验收场景;
|
||||
- **场景库分岗位**:**生活顾问为英雄岗位优先做深**(日常小区服务/物业费停车费催缴/投诉处理全面覆盖,"老带新"式陪练;现有投诉/催费 seed 场景即属该岗位),四保一服(保安/保洁/保绿/保修/客服)其次做广;启用 `practice_scenario.position/project_type` 字段,内容不混岗。
|
||||
|
||||
### B3. AI 一键生成场景培训图片(优先级 3)
|
||||
|
||||
@@ -48,6 +51,7 @@
|
||||
2. **数字人口播**(案例讲解视频主力):腾讯智影 / 百度曦灵 / 硅基智能,按分钟计费,比文生视频便宜一个量级
|
||||
3. **文生视频**(片头/场景氛围镜头):可灵 Kling / 火山即梦 Seedance / MiniMax 海螺 / 通义万相
|
||||
- 选型倾向:二期先只接 **MiniMax 一家**打通(LLM + TTS + 文生图 + 视频一个 key 一套鉴权,对 2 人团队集成量最小,且同时覆盖 B2 语气声);视频质量不满意再单换可灵/即梦——全部封装在自有 provider 抽象后(参照 siliconflow embedding provider 先例)。
|
||||
- **2026-07-04 反馈:优先级上调**(解读见 `feedback/2026070423/ANALYSIS.md`)——视频不是"锦上添花的生成玩具",而是**表演型/动作类隐性知识的主传递媒介**(巡逻、查卫生、礼仪、上门催费:"几个人去、带什么、开口怎么说,经验上的东西没办法说,好的领导现场表演一遍,现在都可以用 AI 做出来")。切入点:催费上门等 1-2 条示范视频先行,验证价值再铺量。
|
||||
|
||||
### B5. 员工四维画像:训练时长 / 贡献度 / 测评分 / AI 等级
|
||||
|
||||
@@ -79,6 +83,23 @@
|
||||
- 前置:火山引擎开户 + G3/G6 供应商合规评估;图片/视频资料成规模、以图搜图成为真实高频需求。
|
||||
- 已做的替代缓解(2026-07-04):视频关键帧 caption(vision prompt 带画面描述)+ rerank(bge-reranker-v2-m3)已上线,覆盖大部分检索需求;B7 只补最后一段纯视觉语义。
|
||||
|
||||
### B8. 数字师傅:学习旅程模块化 + 个性化分发引擎(2026-07-04 反馈,方向级)
|
||||
|
||||
来源:2026-07-04 用户反馈(截图与完整解读见 `feedback/2026070423/ANALYSIS.md`)。核心洞察:系统的对标物是"会带人的老师傅"——把言传(话术语音)、身教(动作视频)、判断(该学什么)用 AI 规模化;用户原话背书:"感觉像老带新的感觉"(截图 10)。
|
||||
|
||||
三个子块,可独立排期:
|
||||
|
||||
1. **员工端五段学习旅程模块化**:入职前面试考核 → 岗前培训 → **每日小训(5-10 分钟,独立模块)** → 棘手问题即时求助("发一个语音,给我两三个 SOP 或话术"——检索输出加话术层)→ 场景模拟。角色主页做成"职责地图"(板块化菜单,不是卡片流)。
|
||||
2. **主管端学习监控面板**:员工×活动(看了哪些视频/做了哪些互动)×时长×效果,衔接 B5 四维画像与视频观看时长统计(心跳+区间合并方案,见 2026-07-04 会话讨论)。
|
||||
3. **个性化分发引擎**(系统的大脑):岗位(生活顾问为先,四保一服其后)× 阶段(入职前/岗前/在岗)× 画像短板 → 决定"今天该看什么/练什么";内容多了以后分发比生产更难("用 AI 分析该让他们看到哪些东西")。闭环:AI 生产内容 → AI 匹配媒介 → AI 决定给谁看 → 数据回流画像。
|
||||
|
||||
配套知识模型扩容:知识类型维度(SOP=标准 / 案例=参照 / 话术=直接能说 / 经验策略=火候),检索按类型分层供给;案例沉淀链路是隐性知识的采集入口,战略地位上调。
|
||||
|
||||
### 零散待办
|
||||
|
||||
- **总结卡「保存图片」按钮**(设计基准 summary-modal 图内有):需 html2canvas 类依赖做 DOM 转图,2026-07-05 P0-2 实现时按"不加依赖"红线砍掉;引依赖或后端出图方案定型后补。
|
||||
- **员工端「学」板块**(我的地图橙色块):当前进「建设中」整页,承接 B8 五段旅程的岗前课程/每日小训模块。
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Model Classification Version Governance
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
| 顺序 | 页面 | 点击路径 | 必须看到 |
|
||||
|---|---|---|---|
|
||||
| 1 | 首页 `/index` | 打开首页 | 只展示“首页 / AI面试 / 三角色对练 / 案例沉淀 / SOP知识库 / 资料处理 / 系统设置-模型配置” |
|
||||
| 2 | 移动端三端 `http://127.0.0.1:5174/h5/user`、`/h5/candidate`、`/h5/supervisor` | 打开后先手机号登录;已配置 `AIHR_SMS_LOGIN_TEMPLATE_ID` 时可真实收短信,新手机号会自动注册;员工端首页为「今天要学/练完要复盘/工作中要用/碰到问题要报/我的成长」五个情境区,点“开始训练”完成两轮提交,再打开主管端;点「遇到问题,问一句」可文本/语音查 SOP 并看到引用片段;候选人端点“开始面试/面试练习”和“补充资料”;管理端 `/recruit/interview` 审核候选资料 | 员工端显示评分、“训练完成,主管端待复盘已更新”、训练历史和能力画像;底部 tab 为整页切换(首页/训练/知识/我的互斥渲染、切换回顶);未实现入口点击弹「本期演示未开放」toast,无死按钮;主管端完训人数、“待复盘对练”计数和待复盘列表增加;训练页语音输入在浏览器录音失败时应切到“选择语音”而不是死按钮;点进待复盘记录可看评分、话术、导师改写,并可标记已复盘;候选人端可拉题/答题/评分,资料上传后显示“已提交资料”、`OSS ...`、`待审核`;HR 审核后候选人端显示 `已通过/已驳回`;接口不可用时走本地 fallback |
|
||||
| 2 | 移动端三端 `http://127.0.0.1:5174/h5/user`、`/h5/candidate`、`/h5/supervisor` | 打开后先手机号登录;已配置 `AIHR_SMS_LOGIN_TEMPLATE_ID` 时可真实收短信,新手机号会自动注册;员工端首页为「师傅区(头像+气泡+按住说话/打字问+快捷chips)→今日安排→我的地图(问/练/学/报四色块)→我的成长摘要」,点“开始训练”完成两轮提交,再打开主管端;点快捷 chip 或「问」可文本/语音查 SOP,结果页点「生成总结卡·约10秒」弹出信息图卡(四步法/异议应对/开口话术/关键提醒+听一遍/开始跟读);候选人端点“开始面试/面试练习”和“补充资料”;管理端 `/recruit/interview` 审核候选资料 | 员工端显示评分、“训练完成,主管端待复盘已更新”、训练历史和能力画像;底部 tab 整页切换(员工端为 今日/练/问/我,切换回顶),滚动后师傅头像收缩为右下悬浮球;未实现入口进「建设中」整页(返回首页按钮),无死按钮;主管端完训人数、“待复盘对练”计数和待复盘列表增加;训练页语音输入在浏览器录音失败时应切到“选择语音”而不是死按钮;点进待复盘记录可看评分、话术、导师改写,并可标记已复盘;候选人端可拉题/答题/评分,资料上传后显示“已提交资料”、`OSS ...`、`待审核`;HR 审核后候选人端显示 `已通过/已驳回`;接口不可用时走本地 fallback |
|
||||
| 3 | AI面试 `/recruit/interview` | 生成题目 → 填满参考回答或输入真实回答 → 完成评分 | 配置 chat 模型时真实生成题目和评分;未配置时用本地 Rubric 估分,不返回固定候选人分数;应看到新增面试记录 |
|
||||
| 4 | 三角色对练 `/train/practice` | 开始对练 → 填入参考回复或输入真实回复 → 继续一轮 → 结束并评分 | 已完成闭环、导师改写、新增对练记录;数据库已启用 chat 模型时,客户回复与评分为真实 LLM 生成(回复不再是固定台词,分数随话术变化),未配置时使用本地剧本兜底 |
|
||||
| 5 | 案例沉淀 `/knowledge/cases` | 上传真实语音 → ASR 转写 → AI 整理 → 送审 → 入库 → 查看样片 | 已完成闭环,案例列表新增真实转写生成的记录;视频仍用样片兜底 |
|
||||
|
||||
@@ -129,7 +129,7 @@ curl -k -s https://peilian.njzhmj.top/h5/supervisor | sed -n '1,20p'
|
||||
- 案例沉淀:进入 `/knowledge/cases`,上传真实语音 → “AI 整理” → “送审” → “入库”,应看到“已完成闭环”和新增案例记录;上传必须先完成 ASR 转写。
|
||||
- SOP知识库:进入 `/knowledge/sop`,可上传 txt/md/PDF/Word/Excel/PPT 文档入库;点击“检索” → “生成训练题”,应看到“已完成闭环”、命中数据库 SOP 原文片段和训练题;数据库不可用时页面回退 seed。
|
||||
- 资料处理:进入 `/knowledge/processing`,应看到资料总量、解析任务表、处理链路、规则与风险;可用少量文件验证“批量导入/选择目录/服务端导入”。**批量导入走异步队列**:提交即返回,页面出现“本次批量上传”进度面板(排队/加工中/完成/失败 + 单条重试),后台 worker 并发 2 逐条解析入库;暂存目录默认 `./.data/staging`(`aihr.upload.staging` 覆盖)。服务端导入读取 `./.data/import` 下的相对目录,启动后台任务并在页面显示进度,运行中任务可点“取消”;目录导入的重试粒度是同目录重新导入,批量上传的重试粒度是单文件。
|
||||
- 移动端员工训练:进入 `http://127.0.0.1:5174/h5/user`,手机号登录(dev 验证码固定 `123456`)。员工端首页为「今天要学/练完要复盘/工作中要用/碰到问题要报/我的成长」五个情境区,底部 tab(首页/训练/知识/我的)为整页切换;未实现入口点击弹「建设中」toast。“开始训练” → 两次“填入建议回复 / 提交本轮”,应看到评分、导师改写、训练历史和能力画像;切到主管端后完训人数与“待复盘对练”计数增加,并展示待复盘列表;点进单条可看评分、话术、导师改写,并可标记已复盘。训练页有“语音输入”和“播报”按钮,需启用 asr/tts 模型后生效;iOS/WebView 若无法直接访问麦克风,会切到“选择语音”并用系统录音/音频文件继续走 ASR,否则降级文本。「遇到问题,问一句」打开查 SOP 面板:文本/语音提问 → 返回回答+依据+引用片段(需后端在跑;语音需 asr 模型)。
|
||||
- 移动端员工训练:进入 `http://127.0.0.1:5174/h5/user`,手机号登录(dev 验证码固定 `123456`)。员工端首页为「师傅区(mentor.png 头像+气泡+按住说话/打字问+快捷chips)→今日安排→我的地图四色块→我的成长摘要」(视觉基准 `feedback/2026070423/design/`),底部 tab(今日/练/问/我)整页切换,滚动后师傅头像收缩为右下悬浮球;未实现入口进「建设中」整页。“开始训练” → 两次“填入建议回复 / 提交本轮”,应看到评分、导师改写、训练历史和能力画像;切到主管端后完训人数与“待复盘对练”计数增加,并展示待复盘列表;点进单条可看评分、话术、导师改写,并可标记已复盘。训练页有“语音输入”和“播报”按钮,需启用 asr/tts 模型后生效;iOS/WebView 若无法直接访问麦克风,会切到“选择语音”并用系统录音/音频文件继续走 ASR,否则降级文本。快捷 chip 或「问」打开查 SOP 面板:文本/语音提问 → 轻概要(回答+要点+注意)+依据折叠;点「生成总结卡·约10秒」弹出信息图卡(四步法/异议/话术/提醒+听一遍/开始跟读),需 chat 模型;语音需 asr 模型。
|
||||
- 移动端候选人:进入 `http://127.0.0.1:5174/h5/candidate`,手机号登录(dev 验证码固定 `123456`)→ “开始面试/面试练习”完成答题评分;“补充资料”选择 PDF/Word/图片后上传,应看到“已提交资料”、`OSS ...`、`待审核`。再进入管理端 `/recruit/interview` 底部“候选资料审核”点“通过/驳回”,候选人端刷新后应显示新状态。Codex 内置浏览器不支持本地文件选择时,用真实 HTTP multipart smoke 代替浏览器文件选择。
|
||||
- 真 LLM 激活:在 `/system/model` 给供应商填 api_host/api_key 并启用 `category=chat` 模型后,三角色对练的客户回复与评分即为真实 LLM 生成;再启用 `category=asr/tts`(如硅基流动 SenseVoice/CosyVoice2)语音路径生效。未配置时全链路自动回退 seed。
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
# H5 前端重构设计蓝本(v1.1 · 2026-07-05)
|
||||
|
||||
> 依据:BRD 选用育留主线 + `feedback/2026070423/ANALYSIS.md`(用户 13 条反馈解读)+ 2026-07-03/04 已落地的体验修复。
|
||||
> 定位一句话:**每个一线员工口袋里的老师傅**(用户原话背书:"感觉像老带新的感觉",feedback 截图 10)。
|
||||
> 本文是 P1 开工蓝本,每节可拆成独立执行规格;工程现状与红线见 `AGENTS.md` 移动端条目。
|
||||
> v1.1 变更:新增 §2.0 数字师傅形象层(甲方构想:数字形象 + 今日安排/我的地图两面板 + 语音/文字/按钮三交互),员工端首页布局随之修订,路线表 P1/P2 更新。
|
||||
> 视觉基准:`feedback/2026070423/design/` 5 张效果图 + 其 `REVIEW.md` 修正项(修正项优先于图面)。
|
||||
|
||||
## 0. 从"工具箱"到"随身师傅"
|
||||
|
||||
现状前端是**功能陈列**:知识库、对练、面试是并列工具,用户要自己想"该用哪个"。
|
||||
"数字师傅"认知下,产品应像**一个人**:你问他答(言传)、他做给你看(身教)、
|
||||
他安排你今天练什么(判断)、他记得你的成长。所有设计从这个心智推导。
|
||||
|
||||
主角色:**生活顾问**(英雄岗位,沟通密集型:日常小区服务/物业费停车费催缴/投诉处理)。
|
||||
demo 数据、今日安排样例、问师傅 placeholder、对练入口场景,全部用生活顾问真实工作填充
|
||||
(seed 人设"王敏·客服管家"即该岗位,内容口径钉死,不向泛化漂移)。
|
||||
四保一服(保安/保洁/保绿/保修/客服)后置,以视频教学为主、对练为辅。
|
||||
|
||||
## 1. 六条设计铁律
|
||||
|
||||
全部从用户反馈和一线处境反推,不是审美偏好:
|
||||
|
||||
| # | 铁律 | 依据 |
|
||||
|---|---|---|
|
||||
| 1 | **媒介即答案**:规则数字→信息图卡;话术→语音+大字;动作流程→视频。文字是给办公室的,用户戴着手套站在地库里 | ANALYSIS 跃迁一 |
|
||||
| 2 | **结论先行**:任何回答 3 秒内给可执行结论,深度内容层层下钻,永不倾泻原文 | 反馈 #1/#2 |
|
||||
| 3 | **一屏一事**:导航一律整页切换;弹出/页内展开/底部追加不用 | 反馈 #3"页面一定要切换" |
|
||||
| 4 | **可点即明显**:按钮长得像按钮,入口行行有色差;像介绍的就不该能点 | 反馈 #4 |
|
||||
| 5 | **贵操作按需触发**:AI 生成类(总结图/视频)显式按钮+预期等待动画,点了才花钱 | 反馈 #2、G6 成本闸门 |
|
||||
| 6 | **弱网可用**:高频 SOP 本地缓存;请求失败有可读兜底,绝不白屏 | 一线场景(地库/电梯间) |
|
||||
|
||||
## 2. 信息架构:三端三种形态
|
||||
|
||||
三端不是三份同构 tab 页,按各自使用心智取形态。
|
||||
|
||||
### 2.0 数字师傅形象层(v1.1 新增,甲方构想)
|
||||
|
||||
**构想**:前端做一个数字形象,配合「今日安排」「我的地图」两个面板,语音、文字、按钮三种交互。
|
||||
|
||||
**为什么对**:形象的真正价值不是好看,是**给所有系统行为一个"发出者"**——
|
||||
"系统推送了 2 个任务"是监工,"师傅今天给你排了两件事"是带教。"老带新的感觉"靠信息
|
||||
架构只能到 60 分,剩下 40 分在这个"人"身上:任务是师傅安排的、点评是师傅说的、话术
|
||||
是师傅教的。同时直接化解 G4 员工接受度风险("被 AI 监考"→"师傅带我")。
|
||||
|
||||
**必须做轻(红线)**:BRD 一期明确不做数字人视频生成(慢且贵,G6)。本层 = **2D 插画
|
||||
形象 + 四状态状态机**,与数字人视频成本差两个数量级:
|
||||
|
||||
| 要素 | 方案 |
|
||||
|---|---|
|
||||
| 形象 | 一张"老师傅"人设插画:可亲、专业、40+ 岁感(用户是中年一线员工,不要二次元吉祥物);用 B3 文生图快速出候选给甲方选 |
|
||||
| 状态机 | **待机**(呼吸/眨眼,CSS 动画)/ **聆听**(录音中,波纹)/ **思考**(检索中)/ **说话**(TTS 播放中,音波);驱动状态的代码已存在——`sopRecording`/`sopLoading`/`sopSpeaking` 即状态源,只差绑一个可视化形象。不做嘴型同步、不做视频、不加依赖 |
|
||||
| 三交互 | **按钮**(最高频):快捷问题 chips 按岗位配置(生活顾问:催费话术/投诉应对/收费标准)+ 今日安排 CTA + 地图入口,零打字路径;**语音**(现场急用):长按师傅说话→ASR→答案卡+师傅读话术,链路全部已通;**文字**(安静环境/精确提问):查SOP面板已有 |
|
||||
|
||||
**两条克制原则**:
|
||||
1. **形象是氛围不是路障**:默认占屏 ≤1/5,滚动收缩为右下悬浮小头像(即原"问师傅"
|
||||
悬浮球的升维合体);老用户要效率;形象加载失败时静态头像兜底(铁律 6)。
|
||||
2. **人格一致**:气泡文案、答案卡口吻("你可以这么说")、对练点评、TTS 音色统一为
|
||||
同一个"师傅"——人格不一致比没有人格更出戏。**师傅人设卡**(名字/语气/口头禅)
|
||||
邀甲方参与定稿。
|
||||
|
||||
**分阶段**:P1 静态形象+四状态+快捷 chips;P2 配合语音对手戏,师傅"演业主"时明示
|
||||
人设切换(避免角色混乱);数字人视频远期视试点反馈再议(维持 BRD 决策)。
|
||||
|
||||
### 2.1 员工端(核心,几千日活)——师傅形象 + 双引擎首页
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ ┌────┐ 「王敏,今天给你 │ ← 师傅形象+气泡(≤1/5屏)
|
||||
│ │师傅 │ 排了两件事」 │ 滚动收缩为右下悬浮头像
|
||||
│ └────┘ 🎤长按说话 ⌨️打字 │ 状态机:待机/聆听/思考/说话
|
||||
│ [催费话术][投诉应对][...] │ ← 快捷 chips,按岗位配置
|
||||
│ │
|
||||
│ ▍今日安排(师傅排的) │ ← 引擎一:回答"我该干什么"
|
||||
│ ● 每日小训:催费沟通 8min │ 叙述者=师傅
|
||||
│ ● 待复盘:昨天的投诉对练 │
|
||||
│ │
|
||||
│ ▍我的地图(全量菜单) │ ← 引擎二:回答"我能干什么"
|
||||
│ [问] 查SOP 求助话术 │ 生活顾问视图:[问][练]为大区
|
||||
│ [练] 场景模拟 错题重练 │ 每区专属色,按钮化(铁律4)
|
||||
│ [学] 岗前课程 每日小训 │
|
||||
│ [报] 上报案例 拍照记录 │
|
||||
└──────────────────────────┘
|
||||
tab: 今日 | 练 | 问 | 我
|
||||
```
|
||||
|
||||
**为什么双引擎分开**:用户既要"AI 分析该让他们看什么"(安排),又要"很明确看到我可以
|
||||
对哪几个方面操作"(地图,反馈 #5)——两个不同心理诉求,混在一起谁都不满足。
|
||||
师傅形象层(§2.0)统摄两个面板:今日安排以师傅口吻叙述,问答从师傅气泡发起。
|
||||
|
||||
**职责地图按岗位配置渲染**(不写死布局):岗位 → 模块权重/排序/入口集,查配置表。
|
||||
生活顾问 [问][练] 大区;保安/保洁将来 [学](视频)为大区。
|
||||
这张配置表即分发引擎规则版 v1 的第一块积木(见 §4)。
|
||||
|
||||
**「问师傅」= 产品灵魂交互**(入口即 §2.0 形象层的语音/文字/chips)。回答统一用**答案卡系统**:
|
||||
|
||||
```
|
||||
[话术卡] 你可以这么说 → 2-3 条直接能开口的话(大字,语音可播)
|
||||
[要点卡] 记住这 3 点 → 信息图式排版(徽章数字/色块分区)
|
||||
[依据] 折叠,点开看原文片段(现有分层已实现)
|
||||
[视频] 有相关演示则给时间戳跳转
|
||||
操作:听一遍 | 跟读练一遍 | 生成总结图(按需,反馈 #2 小窗)
|
||||
```
|
||||
|
||||
**"跟读练一遍"把问和练焊接**:刚问完催费话术,一键进催费对练场景——师傅的教法,
|
||||
说完就让你练。这也是"问→练"数据闭环的入口(§4)。
|
||||
|
||||
### 2.2 候选人端——线性漏斗,不用 tab
|
||||
|
||||
低频、目标单一(通过面试入职):进度条驱动的流程页
|
||||
**面试 → 补资料 → 岗前学习 → 等通知**,每步只有一个主按钮。不加社交/个人中心。
|
||||
|
||||
### 2.3 主管端——监工驾驶舱
|
||||
|
||||
主管不是来"使用功能"的,是来**看人和派活**的(反馈 #7):
|
||||
- 今日团队动态流:谁练了什么、效果如何(时间线形态);
|
||||
- 待办:复盘 / 指派;
|
||||
- 团队短板热力图 → 点短板一键给相关员工指派专项。
|
||||
衔接 BACKLOG B5 四维画像、视频观看时长统计(心跳+区间合并)。
|
||||
|
||||
## 3. 答案卡的知识类型分层
|
||||
|
||||
知识模型加类型维度(ANALYSIS 跃迁二),检索输出按类型供给:
|
||||
|
||||
| 类型 | 定位 | 呈现 |
|
||||
|---|---|---|
|
||||
| 话术 | 直接能说的话 | 话术卡(大字+语音),生活顾问最高频 |
|
||||
| SOP | 标准 | 要点卡(信息图式) |
|
||||
| 案例 | 参照 | 折叠区"看看别人怎么处理" |
|
||||
| 经验策略 | 火候 | 融入话术卡备注("如果业主还是不满意,再…") |
|
||||
|
||||
digest prompt 增加 `scripts`(话术 2-3 条)字段;案例沉淀链路是隐性知识采集入口,
|
||||
供给侧战略地位上调。
|
||||
|
||||
## 4. 贯穿闭环:问 → 学 → 练 → 评 → 再分发
|
||||
|
||||
员工一周内问了 3 次催费相关问题 → 分发引擎把"催费沟通专项"排进他明天的今日安排
|
||||
→ 练完评分入画像 → 主管端看到短板收敛。
|
||||
**问答行为是最真实的短板信号**——这条数据回流是产品从"工具"变"师傅"的分水岭。
|
||||
|
||||
**克制**:分发引擎 v1 用**规则表**(岗位 × 阶段 × 近期问题类目查表),不上真 AI 排序。
|
||||
规则版一周能落地、可解释、可演示;数据够了再换模型。
|
||||
|
||||
## 5. 前置依赖:岗位身份
|
||||
|
||||
整个岗位化设计(内容口径/模块权重/场景过滤)依赖岗位字段,而 H5 登录只有手机号。
|
||||
**轻方案(必须进 P1)**:首次登录后选一次岗位(生活顾问/保安/保洁/保修/客服),
|
||||
存用户 profile;组织快照(`aihr_org_snapshot`)同步后自动带出,选择页仅对无快照记录者出现。
|
||||
|
||||
## 6. 视觉方向:蓝领工具感
|
||||
|
||||
不是企业 SaaS 后台感。参照美团骑手端/顺丰小哥端(已验证一线作业者可用性范式):
|
||||
- 正文 ≥16px,触点 ≥44px,高对比度(阳光下可读);
|
||||
- 实心按钮+箭头,可点元素与介绍文字视觉分离(铁律 4);
|
||||
- 每情境区专属强调色;
|
||||
- 「建设中」提示从 toast 升级为整页/居中大提示(反馈 #3)。
|
||||
|
||||
## 7. 工程还债(与 P1 并行,先行)
|
||||
|
||||
`mobile/src/App.vue` 已 2500+ 行,是所有改动越来越贵的根因:
|
||||
- 重构为 vue-router 多路由 + 组件化(按端拆目录)+ design tokens;
|
||||
- 高频 SOP 进 localStorage(铁律 6);
|
||||
- 为小程序迁移(uni-app/Taro,AGENTS.md 既定方向)留接缝;
|
||||
- 迁移期间 demo-check.sh 标记与三端演示流不可回归。
|
||||
|
||||
## 8. 路线与度量(尊重 2 人团队)
|
||||
|
||||
| 阶段 | 交付 | 验证信号 |
|
||||
|---|---|---|
|
||||
| P0(立即) | 发布现有版本上线 | 用户基于新版的下一轮反馈 |
|
||||
| P1(约一周) | 员工端新 IA(师傅形象层:静态形象+四状态+快捷 chips;今日安排+职责地图+问师傅答案卡)、岗位选择、工程拆分 | 问答采纳率(点"听/练"的比例) |
|
||||
| P2 | 语音对手戏(师傅"演业主"人设切换)+ **生活顾问场景库做深**(日常服务/催费/投诉三大类) | 人均对练次数(验收 D2≥10 次) |
|
||||
| P3 | 主管驾驶舱 + 规则版分发引擎 | 每日小训完成率(验收 D3 完训 80%) |
|
||||
| P3 后 | 四保一服场景做广(视频为主) | — |
|
||||
|
||||
**刻意砍掉的**(每加一个模块都在稀释"师傅"心智):
|
||||
- 案例学习不做独立模块(折进"练"与"问"的供给侧);
|
||||
- 候选人端不加社交/个人中心;
|
||||
- 积分商城不碰(B5 画像先行,激励规则后置)。
|
||||
@@ -13,7 +13,10 @@
|
||||
| [AI人力资源系统一期MVP版作战清单.md](AI人力资源系统一期MVP版作战清单.md) | MVP 作战图、切割线、排期、验收承诺 |
|
||||
| [物业AI人力资源系统业务需求文档BRD.md](物业AI人力资源系统业务需求文档BRD.md) | 业务需求、范围边界、角色、风险与验收 |
|
||||
| [物业AI人力资源系统开发规格TechSpec.md](物业AI人力资源系统开发规格TechSpec.md) | 工程结构、数据模型、页面路由、API 和核心实现规格 |
|
||||
| [BACKLOG.md](BACKLOG.md) | 需求池与延后事项(含 2026-07-03 甲方反馈 B1–B5:数据权限/人设语音/生成图片/生成视频/员工四维画像) |
|
||||
| [BACKLOG.md](BACKLOG.md) | 需求池与延后事项(B1–B8:数据权限/人设语音/生成图片/生成视频/员工画像/视觉检索/数字师傅) |
|
||||
| [H5_REDESIGN.md](H5_REDESIGN.md) | H5 前端重构设计蓝本(随身师傅定位、双引擎首页、答案卡、岗位化、路线图) |
|
||||
| [AI陪练功能优化与缺口分析(修订).md](AI陪练功能优化与缺口分析(修订).md) | 陪练功能缺口 24 项核实修订与排期建议(原 docx 的代码实况校对版,排期以此为准) |
|
||||
| [feedback/](feedback/) | 用户反馈原始截图与解读(按日期归档,含 ANALYSIS.md) |
|
||||
| [CODEBASE_ORIGINS.md](CODEBASE_ORIGINS.md) | 代码库来源与上游仓库说明 |
|
||||
| [prototypes/ai-hr-dashboard.png](prototypes/ai-hr-dashboard.png) | 后台首页高保真目标图 |
|
||||
| [prototypes/ai-hr-p0-pages-composite.png](prototypes/ai-hr-p0-pages-composite.png) | P0 四个页面组合高保真原型图 |
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
design/.DS_Store
|
||||
|
After Width: | Height: | Size: 575 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 345 KiB |
|
After Width: | Height: | Size: 203 KiB |
@@ -0,0 +1,131 @@
|
||||
# 2026-07-04 23:21 用户反馈解读(张立·微信语音转文字,截图 1-9)
|
||||
|
||||
> 原始截图见本目录 1-9.png。反馈时用户使用的是线上 `peilian.njzhmj.top` **旧版**——
|
||||
> 2026-07-03/04 本地已完成的死按钮 toast、整页 tab 切换、情境化首页、结构化概要均未发布。
|
||||
> 采纳反馈前先发布新版,避免对旧版问题重复投入。
|
||||
|
||||
## 一、逐条解码:他说的 → 他真正要的
|
||||
|
||||
### 1. 喜报图示范(截图 1-2)——要"设计过的排版",不是"生成图片"
|
||||
|
||||
用户拿南师附中录取喜报做示范:同样的信息,两段长文字"又多又长又臭",做成图后
|
||||
"条理清晰、布局有逻辑、一眼看清重点"。并自己点破实现方式:**"整理成 HTML 版本的,
|
||||
是不是就不用花钱?"**(他对生图 API 成本敏感,自己在充值)。
|
||||
|
||||
→ 要的是**信息图式 HTML 卡片**:大标题、徽章式重点数字、分栏列表、色块分区。
|
||||
当前概要卡是"素文字列表",差的是视觉设计,不是数据结构。
|
||||
|
||||
### 2. 按需生成 + 小窗口(截图 3-4)——交互设计他已给全
|
||||
|
||||
原话拆解出三个明确决策:
|
||||
- **总结入口是显式按钮**("旁边有个按钮,想看总结时再点一下"),不是默认自动;
|
||||
- **呈现载体是弹出的小窗/卡片**(和"查到的原文"分开,有仪式感);
|
||||
- **按需调用**("不用提前搞,提前搞成本高、风险大"),点了才花钱,
|
||||
loading 有预期所以可接受("他会有一个预期的时间,不会太耗时")。
|
||||
|
||||
→ 轻概要可保留自动;**重排版总结卡改为按钮触发 + 小窗呈现**,还省 token。
|
||||
|
||||
### 3. "页面一定要切换"(截图 7)——弹出式反馈不算反馈
|
||||
|
||||
"按钮点完一定要跳转页面,要不然很怪。底下可能弹出来了,但我会很诧异为什么点了
|
||||
没反应,因为弹出的部分不明显。"
|
||||
|
||||
→ 底部 toast 可见度不够;页内展开/底部追加模式都要改成**明确的页面切换**。
|
||||
整页 tab 已符合;「建设中」toast 需升级为整页或居中大提示。
|
||||
|
||||
### 4. 可点的要长得像可点的(截图 7)
|
||||
|
||||
"每一行可以用不同的颜色,有差异性,好看也好找;同一个颜色看起来像介绍,
|
||||
不像按钮不像超链接,要更像我能点的东西。"
|
||||
|
||||
→ 入口行做视觉分化:每区不同强调色、按钮化(边框/箭头/底色)。
|
||||
|
||||
### 5. 角色主页 = 职责地图(截图 5)
|
||||
|
||||
"不论什么身份进来,希望很明确看到我可以对哪几个方面进行安排/学习/操作,
|
||||
界面分布不清晰"+"目前的感受是过于简单了,拿咱们方案再细化"。
|
||||
|
||||
→ **板块化、菜单化的角色主页**,每个角色的"我能干什么"直接摆出来,不是滚动卡片流。
|
||||
|
||||
### 6. 员工端五段旅程(截图 6)——他给出的产品主线
|
||||
|
||||
① 入职前:面试/考核(视频或语音文字)
|
||||
② 岗前培训
|
||||
③ **每日小训(5-10 分钟)**——要独立成模块,当前归在"今天要学"粒度不够
|
||||
④ 碰到棘手问题:**"发一个语音,给我两三个 SOP 或话术"**——期待输出是拿来就能说的话术,不是知识片段
|
||||
⑤ 场景模拟
|
||||
|
||||
### 7. 主管端定位 = 学习监工(截图 5)
|
||||
|
||||
"检查我管理的员工最近看了哪些视频、做了哪些互动学习、做了多长时间、反馈内容的
|
||||
有效性。"→ 主管端首页应是**员工学习活动监控面板**(人×活动×时长×效果)。
|
||||
与 BACKLOG B5(四维画像)、视频观看时长统计(心跳+区间合并方案)直接衔接。
|
||||
|
||||
### 8. 场景模拟分行业、分"四保一服"(截图 7)
|
||||
|
||||
保安/保洁/保绿/保修/客服按岗位出对应场景,"内容要分得清,不要乱"。
|
||||
→ 对练场景库加**岗位维度**;TechSpec `practice_scenario` 本有 `position/project_type`
|
||||
字段设计,应启用。
|
||||
|
||||
### 9. 多维训练 + 知识类型扩容(截图 8)
|
||||
|
||||
"不光是 SOP,还有案例的应对、经验策略、方案步骤"——知识模型需要**类型维度**:
|
||||
SOP(标准)/ 案例(参照)/ 话术(直接能说)/ 经验策略(火候)。检索输出按类型分层供给。
|
||||
|
||||
### 10. 连贯语音对手戏(截图 8)
|
||||
|
||||
"模拟很凶的老头老太太过来吵架,说一堆话,你怎么回复?回复完他那边还要有一个回复,
|
||||
最好是有连贯性的语音交互。"→ 对练不是"出题-作答-评分"的考试,是**实时情景对手戏**:
|
||||
ASR→LLM(情绪人设+对话记忆)→TTS 低延迟循环 + 情绪化音色(BACKLOG B2)。
|
||||
|
||||
### 11. 动作类知识 = 视频教学(截图 8-9)
|
||||
|
||||
巡逻、查卫生、收文档、礼仪、上门催费——"几个人去、带什么、开口怎么说,很多经验上
|
||||
的东西没办法说,好的领导可以给你现场表演一遍,**现在都可以用 AI 把它做出来**。"
|
||||
|
||||
### 12. 内容分发要 AI 决策(截图 9)
|
||||
|
||||
"是不是还得用 AI 再分析分析,到底应该让他们看到哪些东西。"——内容多了以后
|
||||
**分发比生产更难**,需要个性化学习路径引擎。
|
||||
|
||||
### 13. 生活顾问 = 英雄岗位;"老带新"验证产品北极星(截图 10,2026-07-05 00:02 补充)
|
||||
|
||||
"跟业主打交道的生活顾问,大量的工作是解决日常各类小区服务工作,以及物业费、
|
||||
停车费催缴工作,客户投诉工作,还有其他的,针对这个岗位也要考虑**全面的 AI 陪练**,
|
||||
**感觉像老带新的感觉**。"
|
||||
|
||||
两个关键信号:
|
||||
- **"老带新"是用户原话**——第二节拔高的"数字师傅"定位被用户本人验证,可作为产品北极星的依据引用;
|
||||
- **生活顾问不是"第 6 个岗位"而是英雄岗位**:项目最初名称即《物业管家与生活顾问 AI 陪练系统》。四保一服偏动作型知识(视频为主),生活顾问是**沟通密集型**(催费/投诉/日常服务),正是三角色对练价值最大处——现有 seed 场景(投诉接待、催费沟通)本就属于该岗位。
|
||||
- → 场景库岗位策略:**生活顾问优先做深(日常服务/催费/投诉全面覆盖),四保一服其次做广**。
|
||||
|
||||
## 二、拔高:三个产品定位跃迁
|
||||
|
||||
**系统的真正对标物不是"知识库+练习工具",而是"老师傅"。**
|
||||
物业知识传承的现实是师徒制;张立要的是把公司最稀缺的资源——会带人的老师傅——
|
||||
用 AI 克隆并规模化("数字师傅")。这正是 BRD"经验资产化中央厨房"的具象化。
|
||||
**用户原话背书(截图 10):"感觉像老带新的感觉"。**
|
||||
|
||||
| 跃迁 | 从 | 到 | 依据 |
|
||||
|---|---|---|---|
|
||||
| 媒介 | 文字为主的呈现 | **知识类型决定媒介**:规则数字→信息图;话术→语音示范;动作流程→视频表演 | 用户是几千一线员工,文字对系统最便宜、对他们最贵 |
|
||||
| 知识模型 | SOP 库 | **经验资产库**:显性(SOP)+隐性(案例/话术/策略);案例沉淀链路是隐性知识采集入口 | 截图 8"不光是 SOP" |
|
||||
| 对练形态 | 回合制答题 | **有情绪、有连贯性的语音对手戏** | 截图 8"吵架的老太太" |
|
||||
|
||||
**收束点**:最后"用 AI 分析该让他们看什么"= 系统的大脑——
|
||||
AI 生产内容 → AI 匹配媒介 → AI 决定给谁看 → 数据回流画像,闭环。
|
||||
|
||||
## 三、行动清单(2026-07-05 起)
|
||||
|
||||
| 优先级 | 事项 | 状态/去向 |
|
||||
|---|---|---|
|
||||
| P0 | 发布本地版本到 peilian.njzhmj.top,让用户基于新版反馈 | 待执行 |
|
||||
| P0 | 检索总结卡:按钮触发 + 小窗 + 信息图式 HTML 排版 | 待执行 |
|
||||
| P1 | 可点元素视觉分化;「建设中」toast 升级整页/居中提示 | 待执行 |
|
||||
| P1 | 问 SOP 输出加"推荐话术 2-3 条"(digest prompt 加 scripts 字段) | 待执行 |
|
||||
| P2 | 员工端五段旅程模块化、每日小训独立、主管学习监控面板 | BACKLOG B8 |
|
||||
| P2 | 个性化学习分发引擎(岗位×阶段×画像短板→内容推荐) | BACKLOG B8 |
|
||||
| P2 | 连贯语音对手戏 + 情绪人设 | BACKLOG B2 增补 |
|
||||
| P2 | 场景库分四保一服岗位维度 | BACKLOG B2 增补 |
|
||||
| P2 | 表演型知识视频(催费上门等 1-2 条示范先行) | BACKLOG B4 优先级上调 |
|
||||
| P2 | 知识类型维度(SOP/案例/话术/策略)与分层检索供给 | BACKLOG B8 |
|
||||
@@ -0,0 +1,25 @@
|
||||
# 设计图评审结论(2026-07-05)
|
||||
|
||||
> 5 张图由 gpt-image-2 基于 `docs/H5_REDESIGN.md` v1.1 + `ANALYSIS.md` 生成。
|
||||
> **结论:采纳为 P1 视觉基准**,以下修正项优先于图面。
|
||||
|
||||
## 采纳基准
|
||||
|
||||
| 图 | 用途 |
|
||||
|---|---|
|
||||
| h5-redesign-employee-home.png | 员工端首页布局基准(师傅区/语音条/chips/今日安排/我的地图四色/tab) |
|
||||
| h5-feedback-summary-modal.png | 查SOP结果页 + 总结卡(按钮触发+底部弹层+信息图排版+保存/听/跟读) |
|
||||
| h5-feedback-voice-roleplay.png | P2 语音对手戏基准(业主/师傅双角色、对话记忆、师傅提醒) |
|
||||
| h5-feedback-key-interactions-board.png | 建设中整页、问师傅答案卡(话术+SOP+策略分层)、主管驾驶舱 |
|
||||
| h5-redesign-effect-board.png | **师傅人设基准(老年男性、蓝色工装、插画风)**、答案卡、候选人漏斗、驾驶舱 |
|
||||
|
||||
## 修正项(实现时以此为准,不照抄图面)
|
||||
|
||||
1. **师傅形象全线统一**为效果总板的老师傅(男性 40-55、蓝工装、插画风)。
|
||||
员工首页图中的中年女性写实形象**不采用**——她像用户本人,气泡却对"王敏"说话,
|
||||
"发出者"逻辑(§2.0)破裂;写实照片风也有恐怖谷+加载重问题。
|
||||
2. **首页形象占屏压回 ≤1/5 并保留滚动收缩**为右下悬浮头像(图中约 1/3 屏仅取首屏氛围意象)。
|
||||
3. **对手戏图的"本轮评分参考:沟通态度80/解决效率70"不做每轮真评分**——
|
||||
TechSpec §6 既定:评分只在 /finish 一次;该位置只能放教练轻提示(高/中/低档),防设计倒逼引擎。
|
||||
4. **"全部/SOP/案例/话术"四类 tab 依赖知识类型维度字段(B8 配套)**,后端未落前
|
||||
该 tab 组先隐藏或用现有分类近似,不做假 tab。
|
||||
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="12" y1="8" x2="86" y2="90" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E9F1FF"/>
|
||||
<stop offset="1" stop-color="#94AAD1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="48" cy="48" r="46" fill="url(#bg)"/>
|
||||
<path d="M22 80c4-19 14-29 26-29s22 10 26 29" fill="#244B78"/>
|
||||
<path d="M31 44c1-16 8-25 17-25s16 9 17 25c1 15-7 27-17 27S30 59 31 44Z" fill="#F1C8A8"/>
|
||||
<path d="M30 36c2-15 9-24 19-24 11 0 18 9 20 24-14-8-26-8-39 0Z" fill="#26334A"/>
|
||||
<path d="M37 49c2-2 5-2 7 0M52 49c2-2 5-2 7 0" stroke="#172235" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M37 62c7 4 15 4 22 0" stroke="#172235" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M39 56h18" stroke="#6E3D2D" stroke-width="4" stroke-linecap="round"/>
|
||||
<path d="M34 78h28l-4 9H38z" fill="#F8FBFF"/>
|
||||
<path d="M21 80h54l6 14H15z" fill="#1D3E67"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 950 B |
@@ -0,0 +1,25 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="12" y1="8" x2="86" y2="90" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFE4D7"/>
|
||||
<stop offset="1" stop-color="#FF987A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="hair" x1="24" y1="14" x2="72" y2="76" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8B3A22"/>
|
||||
<stop offset="1" stop-color="#4B1B12"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="48" cy="48" r="46" fill="url(#bg)"/>
|
||||
<path d="M22 82c4-18 14-29 26-29s22 11 26 29" fill="#E9523E"/>
|
||||
<path d="M25 42c0-18 10-30 24-30 15 0 25 12 25 31 0 14-5 25-13 31H35c-7-6-10-18-10-32Z" fill="url(#hair)"/>
|
||||
<path d="M31 46c1-15 8-25 17-25s16 10 17 25c1 15-7 27-17 27S30 61 31 46Z" fill="#FFD0B6"/>
|
||||
<path d="M30 40c9-2 15-8 18-18 4 9 11 14 20 16-2-14-10-22-20-22-12 0-20 9-18 24Z" fill="#6F2B1C"/>
|
||||
<path d="M30 47c-6-1-8 9-2 12M66 47c6-1 8 9 2 12" stroke="#F2B391" stroke-width="4" stroke-linecap="round"/>
|
||||
<path d="M36 44c4-4 8-4 12-1M60 44c-4-4-8-4-12-1" stroke="#2E1C17" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="40" cy="51" r="3" fill="#2E1C17"/>
|
||||
<circle cx="56" cy="51" r="3" fill="#2E1C17"/>
|
||||
<ellipse cx="48" cy="63" rx="8" ry="6" fill="#8E1F1C"/>
|
||||
<path d="M42 60h12" stroke="#FFF2EA" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M36 78h24l-4 9H40z" fill="#FFF7F2"/>
|
||||
<path d="M23 82h50l6 14H17z" fill="#D94133"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="12" y1="8" x2="86" y2="90" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#DDF8F2"/>
|
||||
<stop offset="1" stop-color="#5CC9B5"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="48" cy="48" r="46" fill="url(#bg)"/>
|
||||
<path d="M23 80c3-19 13-29 25-29s22 10 25 29" fill="#2072B8"/>
|
||||
<path d="M31 44c1-16 8-24 17-24s16 8 17 24c1 15-7 27-17 27S30 59 31 44Z" fill="#FFD2B8"/>
|
||||
<path d="M28 31c4-11 12-18 22-18 12 0 20 8 23 19-14-1-27-4-45-1Z" fill="#19558D"/>
|
||||
<path d="M26 31h46v12H26z" fill="#2E7CCD"/>
|
||||
<path d="M39 25h18v8H39z" fill="#D9ECFF"/>
|
||||
<circle cx="40" cy="49" r="3" fill="#172235"/>
|
||||
<circle cx="56" cy="49" r="3" fill="#172235"/>
|
||||
<path d="M40 60c5 4 11 4 16 0" stroke="#172235" stroke-width="4" stroke-linecap="round"/>
|
||||
<path d="M34 79h28l-5 8H39z" fill="#F8FBFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 903 B |
|
After Width: | Height: | Size: 310 KiB |