feat: add custom instructions feature to AIPolishDialog

This commit is contained in:
hanle
2026-03-12 18:01:35 +08:00
committed by siyue
parent d6e723d0e1
commit 9d1d6c4088
4 changed files with 54 additions and 7 deletions
+39 -5
View File
@@ -16,6 +16,8 @@ import {
DialogTitle
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { useAIConfigStore } from "@/store/useAIConfigStore";
import { AI_MODEL_CONFIGS } from "@/config/ai";
import { cn } from "@/lib/utils";
@@ -49,6 +51,7 @@ export default function AIPolishDialog({
const t = useTranslations("aiPolishDialog");
const [isPolishing, setIsPolishing] = useState(false);
const [polishedContent, setPolishedContent] = useState("");
const [customInstructions, setCustomInstructions] = useState("");
const {
selectedModel,
doubaoApiKey,
@@ -106,7 +109,8 @@ export default function AIPolishDialog({
apiKey,
apiEndpoint: selectedModel === "openai" ? openaiApiEndpoint : undefined,
model: config.requiresModelId ? modelId : config.defaultModel,
modelType: selectedModel
modelType: selectedModel,
customInstructions: customInstructions.trim() || undefined
}),
signal: abortControllerRef.current.signal
});
@@ -153,14 +157,13 @@ export default function AIPolishDialog({
}, [polishedContent]);
useEffect(() => {
if (open) {
handlePolish();
} else {
if (!open) {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
abortControllerRef.current = null;
}
setPolishedContent("");
setCustomInstructions("");
}
}, [open]);
@@ -231,10 +234,39 @@ export default function AIPolishDialog({
>
{isPolishing
? t("description.polishing")
: t("description.finished")}
: polishedContent
? t("description.finished")
: t("description.ready")}
</DialogDescription>
</DialogHeader>
<div className="space-y-2">
<Label
htmlFor="custom-instructions"
className={cn(
"text-sm font-medium",
"text-neutral-600 dark:text-neutral-400"
)}
>
{t("customInstructions")}
</Label>
<Textarea
id="custom-instructions"
placeholder={t("customInstructionsPlaceholder")}
value={customInstructions}
onChange={(e) => setCustomInstructions(e.target.value)}
disabled={isPolishing}
rows={2}
className={cn(
"resize-none rounded-xl border",
"bg-neutral-50 dark:bg-neutral-800/50",
"border-neutral-200 dark:border-neutral-800",
"text-neutral-700 dark:text-neutral-300",
"placeholder:text-neutral-400 dark:placeholder:text-neutral-500"
)}
/>
</div>
<div className="grid grid-cols-2 gap-6">
<div className="space-y-2">
<div className="flex items-center gap-2 px-3">
@@ -323,6 +355,8 @@ export default function AIPolishDialog({
<Loader2 className="h-4 w-4 animate-spin" />
{t("button.generating")}
</div>
) : !polishedContent ? (
t("button.start")
) : (
t("button.regenerate")
)}
+4
View File
@@ -671,6 +671,7 @@
"aiPolishDialog": {
"title": "AI Polish",
"description": {
"ready": "Add optional instructions, then click \"Start polish\"",
"polishing": "Polishing your content...",
"finished": "Content has been optimized, please check the result"
},
@@ -683,7 +684,10 @@
"original": "Original Content",
"polished": "Polished Content"
},
"customInstructions": "Custom instructions",
"customInstructionsPlaceholder": "e.g. Use more technical terms, highlight metrics, keep tone formal… (optional)",
"button": {
"start": "Start polish",
"regenerate": "Regenerate",
"generating": "Generating...",
"apply": "Apply Content"
+4
View File
@@ -716,6 +716,7 @@
"aiPolishDialog": {
"title": "AI 润色",
"description": {
"ready": "填写可选的自定义要求后,点击「开始润色」",
"polishing": "正在为您润色内容...",
"finished": "已经为您优化了内容,请查看效果"
},
@@ -728,7 +729,10 @@
"original": "原始内容",
"polished": "润色后的内容"
},
"customInstructions": "自定义要求",
"customInstructionsPlaceholder": "例如:请用更技术化的表述、突出量化成果、语气偏正式…(选填,留空则按默认规则润色)",
"button": {
"start": "开始润色",
"regenerate": "重新生成",
"generating": "生成中...",
"apply": "应用内容"
+7 -2
View File
@@ -8,12 +8,13 @@ export const Route = createFileRoute("/api/polish")({
POST: async ({ request }) => {
try {
const body = await request.json();
const { apiKey, model, content, modelType, apiEndpoint } = body as {
const { apiKey, model, content, modelType, apiEndpoint, customInstructions } = body as {
apiKey: string;
model: string;
content: string;
modelType: AIModelType;
apiEndpoint?: string;
customInstructions?: string;
};
const modelConfig = AI_MODEL_CONFIGS[modelType as AIModelType];
@@ -21,7 +22,7 @@ export const Route = createFileRoute("/api/polish")({
throw new Error("Invalid model type");
}
const systemPrompt = `你是一个专业的简历优化助手。请帮助优化以下 Markdown 格式的文本,使其更加专业和有吸引力。
let systemPrompt = `你是一个专业的简历优化助手。请帮助优化以下 Markdown 格式的文本,使其更加专业和有吸引力。
优化原则:
1. 使用更专业的词汇和表达方式
@@ -33,6 +34,10 @@ export const Route = createFileRoute("/api/polish")({
请直接返回优化后的 Markdown 文本,不要包含任何解释或其他内容。`;
if (customInstructions?.trim()) {
systemPrompt += `\n\n用户额外要求:\n${customInstructions.trim()}`;
}
if (modelType === "gemini") {
const geminiModel = model || "gemini-flash-latest";
const modelInstance = getGeminiModelInstance({