新增普通文案优化数据校验
This commit is contained in:
@@ -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: "",
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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<QaReport>;
|
||||
|
||||
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<CopyOptimizationRequest>;
|
||||
|
||||
const copyChangeNoteSchema = z.object({
|
||||
original: requiredLlmStringSchema,
|
||||
revised: requiredLlmStringSchema,
|
||||
reason: requiredLlmStringSchema,
|
||||
confidence: z.enum(["confident", "uncertain"]),
|
||||
revertible: z.boolean(),
|
||||
}) satisfies z.ZodType<CopyChangeNote>;
|
||||
|
||||
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<CopyAiTasteCheck>;
|
||||
|
||||
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<CopyOptimizationResult>;
|
||||
|
||||
Reference in New Issue
Block a user