haixunMaster/lib/ai/coerce-reply-drafts.ts

157 lines
4.3 KiB
TypeScript

function asString(value: unknown): string {
if (typeof value === "string") return value.trim();
if (typeof value === "number" || typeof value === "boolean") return String(value);
return "";
}
function asNumber(value: unknown, fallback: number): number {
if (typeof value === "number" && Number.isFinite(value)) return value;
const raw = asString(value);
if (!raw) return fallback;
const parsed = Number(raw);
return Number.isFinite(parsed) ? parsed : fallback;
}
function pickDraftText(obj: Record<string, unknown>): string {
return asString(
obj.text ??
obj.comment ??
obj.reply ??
obj.content ??
obj.message ??
obj.body ??
obj.draft ??
obj. ??
obj.
);
}
function coerceOutreachDraftItem(value: unknown): {
text: string;
angle: string;
rationale: string;
} | null {
if (typeof value === "string") {
const text = value.trim();
if (!text) return null;
return { text, angle: "置入留言", rationale: "AI 產生的留言草稿" };
}
if (!value || typeof value !== "object") return null;
const obj = value as Record<string, unknown>;
const text = pickDraftText(obj);
if (!text) return null;
return {
text,
angle:
asString(obj.angle ?? obj.strategy ?? obj.type ?? obj.approach ?? obj.) ||
"置入留言",
rationale:
asString(obj.rationale ?? obj.reason ?? obj.explanation ?? obj. ?? obj.note) ||
"符合貼文情境的自然留言",
};
}
function coerceInboundDraftItem(value: unknown): { text: string; rationale: string } | null {
if (typeof value === "string") {
const text = value.trim();
if (!text) return null;
return { text, rationale: "AI 產生的回覆草稿" };
}
if (!value || typeof value !== "object") return null;
const obj = value as Record<string, unknown>;
const text = pickDraftText(obj);
if (!text) return null;
return {
text,
rationale:
asString(obj.rationale ?? obj.reason ?? obj.explanation ?? obj.) ||
"回覆這則留言",
};
}
function coerceDraftsArray<T>(
value: unknown,
coerceItem: (item: unknown) => T | null,
count: number
): T[] {
const items: T[] = [];
if (Array.isArray(value)) {
for (const item of value) {
const coerced = coerceItem(item);
if (coerced) items.push(coerced);
if (items.length >= count) return items;
}
}
if (value && typeof value === "object" && !Array.isArray(value)) {
for (const item of Object.values(value as Record<string, unknown>)) {
const coerced = coerceItem(item);
if (coerced) items.push(coerced);
if (items.length >= count) return items;
}
}
return items;
}
export function coerceOutreachDraftsRaw(parsed: unknown, count = 2) {
const root =
parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : {};
const nested =
root.result && typeof root.result === "object"
? (root.result as Record<string, unknown>)
: root;
const relevance = Math.min(
1,
Math.max(0, asNumber(nested.relevance ?? nested.score ?? nested.fit, 0.65))
);
const reason =
asString(nested.reason ?? nested.summary ?? nested.why ?? nested.) ||
"符合置入情境";
const drafts = coerceDraftsArray(
nested.drafts ?? nested.comments ?? nested.replies ?? nested.options ?? nested.稿,
coerceOutreachDraftItem,
count
);
return { relevance, reason, drafts };
}
export function coerceInboundReplyDraftsRaw(parsed: unknown, count = 2) {
const root =
parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : {};
const nested =
root.result && typeof root.result === "object"
? (root.result as Record<string, unknown>)
: root;
const sentimentRaw = asString(nested.sentiment ?? nested.tone ?? nested.).toLowerCase();
const sentiment =
sentimentRaw === "positive" ||
sentimentRaw === "neutral" ||
sentimentRaw === "negative" ||
sentimentRaw === "question" ||
sentimentRaw === "lead"
? sentimentRaw
: "neutral";
const intent =
asString(nested.intent ?? nested.intention ?? nested.) || "一般互動";
const drafts = coerceDraftsArray(
nested.drafts ?? nested.replies ?? nested.responses ?? nested.options ?? nested.稿,
coerceInboundDraftItem,
count
);
return { sentiment, intent, drafts };
}