refactor(frontend): clarify toVM/PROCEED 提取到 clarify.ts 共享(WFW-9 M2)
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
PROCEED,
|
||||||
foldClarifications,
|
foldClarifications,
|
||||||
needsClarifyGate,
|
needsClarifyGate,
|
||||||
|
toVM,
|
||||||
type ClarifyAnswer,
|
type ClarifyAnswer,
|
||||||
} from "./clarify";
|
} from "./clarify";
|
||||||
|
|
||||||
@@ -67,6 +69,70 @@ describe("foldClarifications", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("toVM", () => {
|
||||||
|
it("snake_case ClarifyDecision → camelCase VM,逐字段映射", () => {
|
||||||
|
const vm = toVM({
|
||||||
|
need_clarification: true,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "想更冷峻还是更抒情?",
|
||||||
|
options: [
|
||||||
|
{ label: "冷峻", value: "改得更冷峻克制" },
|
||||||
|
{ label: "抒情", value: "改得更抒情" },
|
||||||
|
],
|
||||||
|
allow_free_text: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verification: "我这样理解对吗",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(vm).toEqual({
|
||||||
|
needClarification: true,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "想更冷峻还是更抒情?",
|
||||||
|
options: [
|
||||||
|
{ label: "冷峻", value: "改得更冷峻克制" },
|
||||||
|
{ label: "抒情", value: "改得更抒情" },
|
||||||
|
],
|
||||||
|
allowFreeText: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verification: "我这样理解对吗",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("字段缺省时取安全默认(空/false/undefined),不信任外部数据", () => {
|
||||||
|
const vm = toVM({});
|
||||||
|
expect(vm).toEqual({
|
||||||
|
needClarification: false,
|
||||||
|
questions: [],
|
||||||
|
verification: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("questions/options 内字段缺省时逐项补默认", () => {
|
||||||
|
const vm = toVM({
|
||||||
|
need_clarification: true,
|
||||||
|
questions: [{ question: undefined, options: [{}], allow_free_text: undefined }],
|
||||||
|
verification: null,
|
||||||
|
});
|
||||||
|
expect(vm).toEqual({
|
||||||
|
needClarification: true,
|
||||||
|
questions: [
|
||||||
|
{ question: "", options: [{ label: "", value: "" }], allowFreeText: false },
|
||||||
|
],
|
||||||
|
verification: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("PROCEED", () => {
|
||||||
|
it("放行常量:不反问、无问题", () => {
|
||||||
|
expect(PROCEED).toEqual({ needClarification: false, questions: [] });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("needsClarifyGate", () => {
|
describe("needsClarifyGate", () => {
|
||||||
it("去空白后长度小于 minChars 时需要预检", () => {
|
it("去空白后长度小于 minChars 时需要预检", () => {
|
||||||
expect(needsClarifyGate("改一下", 8)).toBe(true);
|
expect(needsClarifyGate("改一下", 8)).toBe(true);
|
||||||
|
|||||||
@@ -62,3 +62,33 @@ export function foldClarifications(
|
|||||||
export function needsClarifyGate(instruction: string, minChars: number): boolean {
|
export function needsClarifyGate(instruction: string, minChars: number): boolean {
|
||||||
return instruction.trim().length < minChars;
|
return instruction.trim().length < minChars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 澄清预检结论为「放行」(直接去 refine/rewrite,不反问)——后端 error/前端异常时的确定性回退。
|
||||||
|
export const PROCEED: ClarifyDecisionVM = { needClarification: false, questions: [] };
|
||||||
|
|
||||||
|
// 后端 snake_case ClarifyDecision 的原始形状(外部数据,全部可选——不信任、显式取值 + 默认)。
|
||||||
|
export interface ClarifyDecisionRaw {
|
||||||
|
need_clarification?: boolean;
|
||||||
|
questions?: {
|
||||||
|
question?: string;
|
||||||
|
options?: { label?: string; value?: string }[];
|
||||||
|
allow_free_text?: boolean;
|
||||||
|
}[];
|
||||||
|
verification?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后端 snake_case ClarifyDecision → 前端 camelCase VM(纯映射,refine/rewrite 两侧预检共用)。
|
||||||
|
export function toVM(data: ClarifyDecisionRaw): ClarifyDecisionVM {
|
||||||
|
return {
|
||||||
|
needClarification: Boolean(data.need_clarification),
|
||||||
|
questions: (data.questions ?? []).map((q) => ({
|
||||||
|
question: q.question ?? "",
|
||||||
|
options: (q.options ?? []).map((o) => ({
|
||||||
|
label: o.label ?? "",
|
||||||
|
value: o.value ?? "",
|
||||||
|
})),
|
||||||
|
allowFreeText: Boolean(q.allow_free_text),
|
||||||
|
})),
|
||||||
|
verification: data.verification ?? undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,7 @@
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
import { api } from "@/lib/api/client";
|
import { api } from "@/lib/api/client";
|
||||||
import type { ClarifyDecisionVM } from "./clarify";
|
import { PROCEED, toVM, type ClarifyDecisionVM } from "./clarify";
|
||||||
|
|
||||||
// 澄清预检结论为「放行」(直接去 refine,不反问)——后端 error/前端异常时的确定性回退。
|
|
||||||
const PROCEED: ClarifyDecisionVM = { needClarification: false, questions: [] };
|
|
||||||
|
|
||||||
export type ClarifyStatus = "idle" | "checking" | "asking" | "proceed" | "error";
|
export type ClarifyStatus = "idle" | "checking" | "asking" | "proceed" | "error";
|
||||||
|
|
||||||
@@ -24,30 +21,6 @@ export interface UseClarify {
|
|||||||
reset: () => void;
|
reset: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 后端 snake_case ClarifyDecision → 前端 camelCase VM(不信任外部数据,全部显式取值 + 默认)。
|
|
||||||
function toVM(data: {
|
|
||||||
need_clarification?: boolean;
|
|
||||||
questions?: {
|
|
||||||
question?: string;
|
|
||||||
options?: { label?: string; value?: string }[];
|
|
||||||
allow_free_text?: boolean;
|
|
||||||
}[];
|
|
||||||
verification?: string | null;
|
|
||||||
}): ClarifyDecisionVM {
|
|
||||||
return {
|
|
||||||
needClarification: Boolean(data.need_clarification),
|
|
||||||
questions: (data.questions ?? []).map((q) => ({
|
|
||||||
question: q.question ?? "",
|
|
||||||
options: (q.options ?? []).map((o) => ({
|
|
||||||
label: o.label ?? "",
|
|
||||||
value: o.value ?? "",
|
|
||||||
})),
|
|
||||||
allowFreeText: Boolean(q.allow_free_text),
|
|
||||||
})),
|
|
||||||
verification: data.verification ?? undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// AI 反问澄清预检(WFW-9 M1,路线A 两阶段之「问题」阶段)。
|
// AI 反问澄清预检(WFW-9 M1,路线A 两阶段之「问题」阶段)。
|
||||||
// 「再沟通」意见含糊/极短时先调独立非流式端点让 AI 反问;needClarification=false 或失败=放行去既有 refine。
|
// 「再沟通」意见含糊/极短时先调独立非流式端点让 AI 反问;needClarification=false 或失败=放行去既有 refine。
|
||||||
export function useClarify(): UseClarify {
|
export function useClarify(): UseClarify {
|
||||||
|
|||||||
Reference in New Issue
Block a user