haixunMaster/components/settings/persona-form.tsx

118 lines
3.8 KiB
TypeScript
Raw Normal View History

2026-06-21 12:50:31 +00:00
"use client";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
emptyPersonaFields,
parsePersona,
serializePersona,
type PersonaFields,
} from "@/lib/ai/persona";
const PERSONA_FIELDS: Array<{
key: keyof PersonaFields;
label: string;
hint: string;
placeholder: string;
rows: number;
}> = [
{
key: "identity",
label: "我是誰",
hint: "你的身份、專業、生活脈絡,讓 AI 知道「這是誰在說話」。",
placeholder: "例35 歲科技創業者,常分享產品與職場觀察,不裝專家但敢講真話。",
rows: 2,
},
{
key: "tone",
label: "語氣特質",
hint: "形容你的說話方式:直率、溫暖、毒舌、理性…",
placeholder: "例:直率不雞湯、愛用數據與具體例子,偶爾自嘲,不說教。",
rows: 2,
},
{
key: "audience",
label: "對誰說",
hint: "你的讀者是誰?他們在煩什麼?",
placeholder: "例:想轉職或剛創業的工程師,怕踩雷、想聽實戰經驗。",
rows: 2,
},
{
key: "hooks",
label: "開場習慣",
hint: "你平常怎麼開頭?反問、反差、故事、數字衝擊…",
placeholder: "例:常用反問句開頭;或先丟一個違反直覺的結論,再解釋。",
rows: 2,
},
{
key: "examples",
label: "代表句範例",
hint: "貼 23 句你平常會講的話。這欄對文筆影響最大AI 會模仿你的語感。",
placeholder:
"例:\n很多人以為努力就夠了其實方向錯了越努力越遠。\n我花了三年才學會先問「這件事值不值得做」再問怎麼做。",
rows: 4,
},
{
key: "avoid",
label: "避免",
hint: "不要出現的詞、語氣或套路。",
placeholder: "例:不要用「陪你」「一起變好」;不要過度敬語;不要像業配。",
rows: 2,
},
];
interface PersonaFormProps {
value?: string | null;
onChange: (serialized: string) => void;
}
export function PersonaForm({ value, onChange }: PersonaFormProps) {
const fields = parsePersona(value);
function updateField(key: keyof PersonaFields, next: string) {
const merged = { ...fields, [key]: next };
onChange(serializePersona(merged));
}
return (
<div className="space-y-4">
<div className="rounded-lg border border-border bg-muted/60 px-3.5 py-3">
<p className="text-sm font-medium"></p>
<p className="mt-1 text-xs leading-relaxed text-muted-foreground">
稿AI
<strong className="font-medium text-foreground"></strong>
</p>
</div>
{PERSONA_FIELDS.map((field) => (
<div key={field.key} className="space-y-2">
<div>
<Label>{field.label}</Label>
<p className="mt-0.5 text-xs text-muted-foreground">{field.hint}</p>
</div>
<Textarea
value={fields[field.key]}
onChange={(e) => updateField(field.key, e.target.value)}
placeholder={field.placeholder}
rows={field.rows}
/>
</div>
))}
{!serializePersona(fields) && (
<p className="text-xs text-muted-foreground">
</p>
)}
</div>
);
}
export function getDefaultPersonaPreview() {
return serializePersona({
...emptyPersonaFields(),
tone: "口語、有觀點、像朋友在脆上分享",
examples: "其實很多人搞錯重點了。\n我以前也以為…後來才發現…",
});
}