M4(文风): style-auditor 双轨(提取指纹/漂移第四审)+ jobs 长任务框架(zombie reaper) + 回炉 refine + GET /style read-back。 M5(生成+扩展): worldbuilder/character-gen(入库 continuity 409 gate + partition_writes 白名单 + schema→JSONB 形变); 网关多 provider 回退链/熔断/能力降级(Anthropic/Gemini 适配器);Skill registry + 表权限沙箱 + 规则; 前端 角色生成器/世界观/Codex/规则页/技能库/⌘K 命令面板。 K1(Kimi Code 订阅接入): OAuth device-flow(kimi-code)+ 静态 Console key(kimi-code-key)两路径; coding 端点 KimiCLI 伪造头(实测 UA allow-list 门禁,缺则 403)+ JSON 模式结构化(thinking ⊥ tool_choice)。 本地联调修复: CORS 中间件;assemble 注入 premise+「写第N章」指令(修空 prompt 400); GET /outline·/draft read-back + 大纲/工作台/审稿页重载;写页 client/server 常量边界 + notFound 健壮化; 字数 toLocaleString locale 水合;审稿页终稿从已存草稿 seed(修 accept 422)。 门禁: backend ruff/mypy(157)/alembic 无漂移/pytest 451 · frontend lint/tsc/vitest/build。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
"use client";
|
||
|
||
import { useCallback, useState } from "react";
|
||
|
||
import { api } from "@/lib/api/client";
|
||
import { useToast } from "@/components/Toast";
|
||
import type { RefineResponse } from "@/lib/api/types";
|
||
import { buildRefineRequest } from "./style";
|
||
|
||
export type RefineStatus = "idle" | "refining" | "done" | "error";
|
||
|
||
export interface RefineResult {
|
||
original: string;
|
||
refined: string;
|
||
}
|
||
|
||
export interface UseRefine {
|
||
status: RefineStatus;
|
||
result: RefineResult | null;
|
||
// 回炉重写某段(可选改写指令)→ 返回 {original, refined} 或 null(失败)。
|
||
refine: (
|
||
projectId: string,
|
||
chapterNo: number,
|
||
segment: string,
|
||
instruction?: string,
|
||
) => Promise<RefineResult | null>;
|
||
reset: () => void;
|
||
}
|
||
|
||
function errorCode(error: unknown): string | undefined {
|
||
if (typeof error !== "object" || error === null) return undefined;
|
||
const env = error as { error?: { code?: unknown } };
|
||
return typeof env.error?.code === "string" ? env.error.code : undefined;
|
||
}
|
||
|
||
// 回炉(POST .../refine):同步返回新旧 diff,不写库(不变量#3)。
|
||
// 503 LLM_UNAVAILABLE(无凭据)优雅提示去设置;其余失败 toast。
|
||
export function useRefine(): UseRefine {
|
||
const [status, setStatus] = useState<RefineStatus>("idle");
|
||
const [result, setResult] = useState<RefineResult | null>(null);
|
||
const toast = useToast();
|
||
|
||
const refine = useCallback<UseRefine["refine"]>(
|
||
async (projectId, chapterNo, segment, instruction) => {
|
||
setStatus("refining");
|
||
setResult(null);
|
||
try {
|
||
const { data, error } = await api.POST(
|
||
"/projects/{project_id}/chapters/{chapter_no}/refine",
|
||
{
|
||
params: {
|
||
path: { project_id: projectId, chapter_no: chapterNo },
|
||
},
|
||
body: buildRefineRequest(segment, instruction),
|
||
},
|
||
);
|
||
if (error || !data) {
|
||
setStatus("error");
|
||
const code = errorCode(error);
|
||
toast(
|
||
code === "LLM_UNAVAILABLE"
|
||
? "未配置提供商,请先去设置页连一家。"
|
||
: "回炉失败,请稍后重试。",
|
||
"error",
|
||
);
|
||
return null;
|
||
}
|
||
const next = data as RefineResponse;
|
||
const outcome: RefineResult = {
|
||
original: next.original,
|
||
refined: next.refined,
|
||
};
|
||
setResult(outcome);
|
||
setStatus("done");
|
||
return outcome;
|
||
} catch {
|
||
setStatus("error");
|
||
toast("回炉请求异常,请检查网络。", "error");
|
||
return null;
|
||
}
|
||
},
|
||
[toast],
|
||
);
|
||
|
||
const reset = useCallback((): void => {
|
||
setStatus("idle");
|
||
setResult(null);
|
||
}, []);
|
||
|
||
return { status, result, refine, reset };
|
||
}
|