2026-05-07 02:34:04 +08:00
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
|
import { useAccounts } from '@/hooks/useAccounts';
|
|
|
|
|
import { usePrompts } from '@/hooks/usePrompts';
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
|
|
|
|
|
const PROMPT_TYPES = [
|
|
|
|
|
{ type: 'storyboard', label: '分镜' },
|
|
|
|
|
{ type: 'image', label: '图片提示词' },
|
|
|
|
|
{ type: 'video', label: '视频提示词' },
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
export function PromptEditor() {
|
|
|
|
|
const { accounts } = useAccounts();
|
|
|
|
|
const [selectedAccount, setSelectedAccount] = useState<string>('');
|
|
|
|
|
const [selectedType, setSelectedType] = useState<string>('storyboard');
|
|
|
|
|
const { content, path, loading, load, save, setContent } = usePrompts();
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (accounts.length > 0 && !selectedAccount) {
|
|
|
|
|
setSelectedAccount(accounts[0].id);
|
|
|
|
|
}
|
|
|
|
|
}, [accounts]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (selectedAccount) {
|
|
|
|
|
load(selectedAccount, selectedType);
|
|
|
|
|
}
|
|
|
|
|
}, [selectedAccount, selectedType]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex h-full">
|
|
|
|
|
<div className="w-48 border-r border-zinc-800 p-3 space-y-3">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-zinc-500">账户</label>
|
|
|
|
|
<select
|
|
|
|
|
value={selectedAccount}
|
|
|
|
|
onChange={(e) => setSelectedAccount(e.target.value)}
|
2026-05-07 03:48:14 +08:00
|
|
|
className="mt-1 w-full h-9 rounded-md border border-zinc-200 bg-zinc-50 px-2 text-sm"
|
2026-05-07 02:34:04 +08:00
|
|
|
>
|
|
|
|
|
{accounts.map((a) => (
|
|
|
|
|
<option key={a.id} value={a.id}>{a.name}</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-zinc-500">模板</label>
|
|
|
|
|
<div className="mt-1 space-y-0.5">
|
|
|
|
|
{PROMPT_TYPES.map(({ type, label }) => (
|
|
|
|
|
<button
|
|
|
|
|
key={type}
|
|
|
|
|
onClick={() => setSelectedType(type)}
|
|
|
|
|
className={`w-full text-left px-2 py-1.5 rounded text-sm ${
|
2026-05-07 03:48:14 +08:00
|
|
|
selectedType === type ? 'bg-indigo-50 text-indigo-700 font-medium' : 'text-zinc-500 hover:text-zinc-700 hover:bg-zinc-50'
|
2026-05-07 02:34:04 +08:00
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex-1 flex flex-col">
|
|
|
|
|
<div className="flex items-center justify-between px-4 py-2 border-b border-zinc-800">
|
|
|
|
|
<span className="text-xs text-zinc-500 font-mono">{path}</span>
|
|
|
|
|
<Button
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() => save(selectedAccount, selectedType, content)}
|
|
|
|
|
disabled={!selectedAccount || loading}
|
|
|
|
|
>
|
|
|
|
|
保存
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
<textarea
|
|
|
|
|
value={content}
|
|
|
|
|
onChange={(e) => setContent(e.target.value)}
|
2026-05-07 03:48:14 +08:00
|
|
|
className="flex-1 w-full bg-white text-zinc-700 font-mono text-sm p-4 resize-none outline-none"
|
2026-05-07 02:34:04 +08:00
|
|
|
placeholder="加载中..."
|
|
|
|
|
spellCheck={false}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|