diff --git a/frontend/src/api/prompt.ts b/frontend/src/api/prompt.ts new file mode 100644 index 0000000..0d41baf --- /dev/null +++ b/frontend/src/api/prompt.ts @@ -0,0 +1,35 @@ +import { post } from './client' +import { STYLE_CATEGORIES } from '../utils/style' + +interface OptimizePromptParams { + tags: string[] + assetType: string + prompt: string + userNote?: string +} + +interface OptimizePromptResponse { + prompt: string + rawText: string +} + +/** + * 从风格键值对中提取标签的中文名称作为 tags + */ +export function extractTags(style: Record): string[] { + return STYLE_CATEGORIES.flatMap(cat => { + const value = style[cat.key] + if (!value) return [] + const option = cat.options.find(o => o.value === value) + return option ? [option.label] : [] + }) +} + +/** + * 调用后端提示词优化接口 + */ +export async function optimizePrompt( + params: OptimizePromptParams, +): Promise { + return post('/api/v1/prompt/optimize', params) +} diff --git a/frontend/src/components/GenerateForm.module.css b/frontend/src/components/GenerateForm.module.css new file mode 100644 index 0000000..11efb51 --- /dev/null +++ b/frontend/src/components/GenerateForm.module.css @@ -0,0 +1,98 @@ +.promptPanels { + display: flex; + gap: 16px; +} + +.panel { + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; +} + +.panelSingle { + composes: panel; + flex: 1; +} + +.panelLabel { + font-size: 13px; + font-weight: 600; + color: var(--text-secondary); +} + +.optimizedBadge { + font-size: 11px; + padding: 2px 8px; + border-radius: 10px; + background: var(--accent-dim); + color: var(--accent); + font-weight: 600; +} + +.textarea { + resize: vertical; + width: 100%; + min-height: 120px; +} + +.toggleRow { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 4px; +} + +.toggle { + position: relative; + width: 40px; + height: 22px; + border-radius: 11px; + border: none; + cursor: pointer; + transition: background-color 0.2s; + padding: 0; + flex-shrink: 0; +} + +.toggleOn { + background: var(--accent); +} + +.toggleOff { + background: var(--border); +} + +.toggleKnob { + position: absolute; + top: 2px; + width: 18px; + height: 18px; + border-radius: 50%; + background: #fff; + transition: left 0.2s; +} + +.toggleKnobOn { + left: 20px; +} + +.toggleKnobOff { + left: 2px; +} + +.toggleLabel { + font-size: 13px; + color: var(--text-secondary); +} + +.optimizeBtn { + padding: 6px 16px; + font-size: 13px; +} + +@media (max-width: 640px) { + .promptPanels { + flex-direction: column; + } +} diff --git a/frontend/src/components/GenerateForm.tsx b/frontend/src/components/GenerateForm.tsx index e3c2ec0..40a05ec 100644 --- a/frontend/src/components/GenerateForm.tsx +++ b/frontend/src/components/GenerateForm.tsx @@ -13,7 +13,7 @@ const ASSET_TYPES: { value: AssetType; label: string }[] = [ ] interface GenerateFormProps { - onSubmit: () => void + onSubmit: (prompt: string) => void submitting: boolean } @@ -24,13 +24,32 @@ export default function GenerateForm({ onSubmit, submitting }: GenerateFormProps assetType, taskStyle, params, + enableAI, + optimizedPrompt, + isOptimizing, setPrompt, setAssetType, toggleTaskStyle, setParams, + setEnableAI, + setOptimizedPrompt, + runOptimize, } = useTaskStore() const mergedStyle = mergeStyles(projectStyle, taskStyle) + const finalPrompt = enableAI && optimizedPrompt ? optimizedPrompt : prompt + + const handleOptimize = async () => { + try { + await runOptimize(projectStyle) + } catch { + // error handled in store + } + } + + const handleSubmit = () => { + onSubmit(finalPrompt) + } return (
@@ -62,16 +81,41 @@ export default function GenerateForm({ onSubmit, submitting }: GenerateFormProps
- {/* 提示词 */} + {/* 提示词(含 AI 优化) */}
-

- 提示词 -

+
+

+ 提示词 +

+ {enableAI && ( + + )} +
@@ -113,8 +157,8 @@ export default function GenerateForm({ onSubmit, submitting }: GenerateFormProps