新增普通文案优化数据校验
This commit is contained in:
@@ -4,6 +4,8 @@ import {
|
|||||||
articleInputSchema,
|
articleInputSchema,
|
||||||
confirmedFactCardSchema,
|
confirmedFactCardSchema,
|
||||||
candidateFactCardSchema,
|
candidateFactCardSchema,
|
||||||
|
copyOptimizationRequestSchema,
|
||||||
|
copyOptimizationResultSchema,
|
||||||
optimizationFactCardSchema,
|
optimizationFactCardSchema,
|
||||||
optimizedArticleSchema,
|
optimizedArticleSchema,
|
||||||
qaReportSchema,
|
qaReportSchema,
|
||||||
@@ -51,6 +53,59 @@ describe("domain validation", () => {
|
|||||||
).toThrow();
|
).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", () => {
|
it("accepts an unconfirmed optimization fact card with unresolved items", () => {
|
||||||
const parsed = optimizationFactCardSchema.parse({
|
const parsed = optimizationFactCardSchema.parse({
|
||||||
company_full_name: "",
|
company_full_name: "",
|
||||||
|
|||||||
@@ -86,3 +86,46 @@ export interface QaReport {
|
|||||||
overall_status: CheckStatus;
|
overall_status: CheckStatus;
|
||||||
checks: QaCheck[];
|
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,
|
CandidateFactCard,
|
||||||
CheckStatus,
|
CheckStatus,
|
||||||
ConfirmedFactCard,
|
ConfirmedFactCard,
|
||||||
|
CopyAiTasteCheck,
|
||||||
|
CopyChangeNote,
|
||||||
|
CopyOptimizationRequest,
|
||||||
|
CopyOptimizationResult,
|
||||||
ImageInput,
|
ImageInput,
|
||||||
OptimizationFactCard,
|
OptimizationFactCard,
|
||||||
OptimizedArticle,
|
OptimizedArticle,
|
||||||
@@ -431,3 +435,51 @@ export const qaReportSchema = z.object({
|
|||||||
overall_status: checkStatusSchema,
|
overall_status: checkStatusSchema,
|
||||||
checks: z.array(qaCheckSchema).default([]),
|
checks: z.array(qaCheckSchema).default([]),
|
||||||
}) satisfies z.ZodType<QaReport>;
|
}) 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