From c2010fa8f479abad9fe44c93e70f811f8691d00a Mon Sep 17 00:00:00 2001 From: czj <13261895355@163.com> Date: Thu, 2 Jul 2026 14:32:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=99=AE=E9=80=9A=E6=96=87?= =?UTF-8?q?=E6=A1=88=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/domain/__tests__/validation.test.ts | 55 +++++++++++++++++++++ src/lib/domain/types.ts | 43 ++++++++++++++++ src/lib/domain/validation.ts | 52 +++++++++++++++++++ 3 files changed, 150 insertions(+) diff --git a/src/lib/domain/__tests__/validation.test.ts b/src/lib/domain/__tests__/validation.test.ts index bd796cf..9a12cb9 100644 --- a/src/lib/domain/__tests__/validation.test.ts +++ b/src/lib/domain/__tests__/validation.test.ts @@ -4,6 +4,8 @@ import { articleInputSchema, confirmedFactCardSchema, candidateFactCardSchema, + copyOptimizationRequestSchema, + copyOptimizationResultSchema, optimizationFactCardSchema, optimizedArticleSchema, qaReportSchema, @@ -51,6 +53,59 @@ describe("domain validation", () => { ).toThrow(); }); + it("accepts a trimmed copy optimization request", () => { + const parsed = copyOptimizationRequestSchema.parse({ + source_text: " 我写了一段有点卡的文案 ", + goal: "", + intensity: "light", + user_instructions: " 保留口语感 ", + }); + + expect(parsed).toEqual({ + source_text: "我写了一段有点卡的文案", + goal: "保留原意,减少 AI 味", + intensity: "light", + user_instructions: "保留口语感", + }); + }); + + it("rejects empty copy optimization source text", () => { + expect(() => + copyOptimizationRequestSchema.parse({ + source_text: " ", + intensity: "light", + }), + ).toThrow(); + }); + + it("accepts structured copy optimization results", () => { + const parsed = copyOptimizationResultSchema.parse({ + optimized_text: "我把句子顺了一下。", + change_notes: [ + { + original: "我把句子顺顺。", + revised: "我把句子顺了一下。", + reason: "修正口语里不顺的重复。", + confidence: "confident", + revertible: false, + }, + ], + ai_taste_checks: [ + { + rule_id: "promotion_tone", + status: "pass", + evidence: "没有新增宣传词。", + suggestion: "", + }, + ], + warnings: [], + }); + + expect(parsed.optimized_text).toBe("我把句子顺了一下。"); + expect(parsed.change_notes[0]?.confidence).toBe("confident"); + expect(parsed.ai_taste_checks[0]?.rule_id).toBe("promotion_tone"); + }); + it("accepts an unconfirmed optimization fact card with unresolved items", () => { const parsed = optimizationFactCardSchema.parse({ company_full_name: "", diff --git a/src/lib/domain/types.ts b/src/lib/domain/types.ts index 52778da..c56f1a1 100644 --- a/src/lib/domain/types.ts +++ b/src/lib/domain/types.ts @@ -86,3 +86,46 @@ export interface QaReport { overall_status: CheckStatus; checks: QaCheck[]; } + +export type CopyOptimizationIntensity = "light" | "medium" | "conversational"; + +export type CopyChangeConfidence = "confident" | "uncertain"; + +export type CopyAiTasteRuleId = + | "meaning_inflation" + | "promotion_tone" + | "formulaic_sentence" + | "format_trace" + | "chat_trace" + | "filler_hedging"; + +export type CopyAiTasteStatus = "pass" | "warn"; + +export interface CopyOptimizationRequest { + source_text: string; + goal: string; + intensity: CopyOptimizationIntensity; + user_instructions: string; +} + +export interface CopyChangeNote { + original: string; + revised: string; + reason: string; + confidence: CopyChangeConfidence; + revertible: boolean; +} + +export interface CopyAiTasteCheck { + rule_id: CopyAiTasteRuleId; + status: CopyAiTasteStatus; + evidence: string; + suggestion: string; +} + +export interface CopyOptimizationResult { + optimized_text: string; + change_notes: CopyChangeNote[]; + ai_taste_checks: CopyAiTasteCheck[]; + warnings: string[]; +} diff --git a/src/lib/domain/validation.ts b/src/lib/domain/validation.ts index 0b57420..3fca694 100644 --- a/src/lib/domain/validation.ts +++ b/src/lib/domain/validation.ts @@ -5,6 +5,10 @@ import type { CandidateFactCard, CheckStatus, ConfirmedFactCard, + CopyAiTasteCheck, + CopyChangeNote, + CopyOptimizationRequest, + CopyOptimizationResult, ImageInput, OptimizationFactCard, OptimizedArticle, @@ -431,3 +435,51 @@ export const qaReportSchema = z.object({ overall_status: checkStatusSchema, checks: z.array(qaCheckSchema).default([]), }) satisfies z.ZodType; + +export const copyOptimizationIntensitySchema = z.enum([ + "light", + "medium", + "conversational", +]); + +export const copyOptimizationRequestSchema = z.object({ + source_text: z.string().trim().min(1), + goal: z + .preprocess((value) => { + if (typeof value !== "string") return value; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : "保留原意,减少 AI 味"; + }, z.string().trim().min(1)) + .default("保留原意,减少 AI 味"), + intensity: copyOptimizationIntensitySchema.default("light"), + user_instructions: z.string().trim().default(""), +}) satisfies z.ZodType; + +const copyChangeNoteSchema = z.object({ + original: requiredLlmStringSchema, + revised: requiredLlmStringSchema, + reason: requiredLlmStringSchema, + confidence: z.enum(["confident", "uncertain"]), + revertible: z.boolean(), +}) satisfies z.ZodType; + +const copyAiTasteCheckSchema = z.object({ + rule_id: z.enum([ + "meaning_inflation", + "promotion_tone", + "formulaic_sentence", + "format_trace", + "chat_trace", + "filler_hedging", + ]), + status: z.enum(["pass", "warn"]), + evidence: requiredLlmStringSchema, + suggestion: optionalLlmStringSchema, +}) satisfies z.ZodType; + +export const copyOptimizationResultSchema = z.object({ + optimized_text: requiredLlmStringSchema, + change_notes: z.array(copyChangeNoteSchema).default([]), + ai_taste_checks: z.array(copyAiTasteCheckSchema).default([]), + warnings: stringListSchema, +}) satisfies z.ZodType;