118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
"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: "貼 2~3 句你平常會講的話。這欄對文筆影響最大,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我以前也以為…後來才發現…",
|
||
});
|
||
} |