Skip to content

Commit f4426ec

Browse files
authored
Feature/customize prompt frontend (#1446)
* feat: update * feat: setup page * feat: update frontend * feat: update frontend
1 parent 91ba84d commit f4426ec

File tree

7 files changed

+311
-5
lines changed

7 files changed

+311
-5
lines changed

web/src/app/workspace/menu-footer.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
ChevronRight,
2424
Key,
2525
Logs,
26+
MessageSquareText,
2627
Package,
2728
Settings,
2829
} from 'lucide-react';
@@ -79,6 +80,12 @@ export const MenuFooter = () => {
7980
<BatteryMedium /> {sidebar_workspace('quotas')}
8081
</Link>
8182
</DropdownMenuItem>
83+
84+
<DropdownMenuItem asChild>
85+
<Link href="/workspace/prompts">
86+
<MessageSquareText /> {sidebar_workspace('prompts')}
87+
</Link>
88+
</DropdownMenuItem>
8289
</DropdownMenuGroup>
8390
</DropdownMenuContent>
8491
</DropdownMenu>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {
2+
PageContainer,
3+
PageContent,
4+
PageDescription,
5+
PageHeader,
6+
PageTitle,
7+
} from '@/components/page-container';
8+
import { getServerApi } from '@/lib/api/server';
9+
import { toJson } from '@/lib/utils';
10+
import { getTranslations } from 'next-intl/server';
11+
import { PromptSettings } from './prompt-settings';
12+
13+
export default async function Page() {
14+
const serverApi = await getServerApi();
15+
const res = await serverApi.defaultApi.promptsUserGet();
16+
const data = res.data;
17+
const page_prompts = await getTranslations('page_prompts');
18+
19+
return (
20+
<PageContainer>
21+
<PageHeader breadcrumbs={[{ title: page_prompts('metadata.title') }]} />
22+
<PageContent>
23+
<PageTitle>{page_prompts('metadata.title')}</PageTitle>
24+
<PageDescription>{page_prompts('metadata.description')}</PageDescription>
25+
<PromptSettings data={toJson(data)} />
26+
</PageContent>
27+
</PageContainer>
28+
);
29+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
'use client';
2+
3+
import { PromptDetail, UserPromptsResponse } from '@/api';
4+
import { Badge } from '@/components/ui/badge';
5+
import { Button } from '@/components/ui/button';
6+
import {
7+
Card,
8+
CardContent,
9+
CardDescription,
10+
CardFooter,
11+
CardHeader,
12+
CardTitle,
13+
} from '@/components/ui/card';
14+
import {
15+
Dialog,
16+
DialogClose,
17+
DialogContent,
18+
DialogDescription,
19+
DialogFooter,
20+
DialogHeader,
21+
DialogTitle,
22+
DialogTrigger,
23+
} from '@/components/ui/dialog';
24+
import { Textarea } from '@/components/ui/textarea';
25+
import { apiClient } from '@/lib/api/client';
26+
import { useTranslations } from 'next-intl';
27+
import { useRouter } from 'next/navigation';
28+
import { useCallback, useEffect, useState } from 'react';
29+
import { toast } from 'sonner';
30+
31+
type PromptType = 'agent_system' | 'agent_query';
32+
33+
const PROMPT_TYPES: PromptType[] = ['agent_system', 'agent_query'];
34+
35+
interface PromptCardProps {
36+
promptType: PromptType;
37+
detail: PromptDetail | undefined;
38+
onSaved: () => void;
39+
}
40+
41+
const PromptCard = ({ promptType, detail, onSaved }: PromptCardProps) => {
42+
const page_prompts = useTranslations('page_prompts');
43+
const common_action = useTranslations('common.action');
44+
const [content, setContent] = useState(detail?.content ?? '');
45+
const [saving, setSaving] = useState(false);
46+
const [resetOpen, setResetOpen] = useState(false);
47+
48+
useEffect(() => {
49+
setContent(detail?.content ?? '');
50+
}, [detail?.content]);
51+
52+
const handleSave = useCallback(async () => {
53+
setSaving(true);
54+
try {
55+
await apiClient.defaultApi.promptsUserPut({
56+
updateUserPromptsRequest: {
57+
prompts: { [promptType]: content },
58+
},
59+
});
60+
toast.success(page_prompts('toast.save_success'));
61+
onSaved();
62+
} finally {
63+
setSaving(false);
64+
}
65+
}, [content, promptType, page_prompts, onSaved]);
66+
67+
const handleReset = useCallback(async () => {
68+
await apiClient.defaultApi.promptsUserPromptTypeDelete({
69+
promptType: promptType as never,
70+
});
71+
toast.success(page_prompts('toast.reset_success'));
72+
setResetOpen(false);
73+
onSaved();
74+
}, [promptType, page_prompts, onSaved]);
75+
76+
const isCustomized = detail?.customized === true;
77+
78+
return (
79+
<Card>
80+
<CardHeader>
81+
<div className="flex items-center justify-between gap-2">
82+
<CardTitle>{page_prompts(`${promptType}.title` as never)}</CardTitle>
83+
<Badge variant={isCustomized ? 'default' : 'secondary'}>
84+
{isCustomized
85+
? page_prompts('status.customized')
86+
: page_prompts('status.default')}
87+
</Badge>
88+
</div>
89+
<CardDescription>
90+
{page_prompts(`${promptType}.description` as never)}
91+
</CardDescription>
92+
</CardHeader>
93+
94+
<CardContent>
95+
<Textarea
96+
className="min-h-[160px] max-h-[360px] font-mono text-sm resize-y"
97+
value={content}
98+
onChange={(e) => setContent(e.target.value)}
99+
placeholder={detail?.content ?? ''}
100+
/>
101+
</CardContent>
102+
103+
<CardFooter className="justify-end gap-2">
104+
{isCustomized && (
105+
<Dialog open={resetOpen} onOpenChange={setResetOpen}>
106+
<DialogTrigger asChild>
107+
<Button variant="outline">
108+
{page_prompts('action.reset')}
109+
</Button>
110+
</DialogTrigger>
111+
<DialogContent>
112+
<DialogHeader>
113+
<DialogTitle>{page_prompts('action.reset_confirm')}</DialogTitle>
114+
<DialogDescription>
115+
{page_prompts('action.reset_confirm_description')}
116+
</DialogDescription>
117+
</DialogHeader>
118+
<DialogFooter>
119+
<DialogClose asChild>
120+
<Button variant="outline">{common_action('cancel')}</Button>
121+
</DialogClose>
122+
<Button variant="destructive" onClick={handleReset}>
123+
{page_prompts('action.reset')}
124+
</Button>
125+
</DialogFooter>
126+
</DialogContent>
127+
</Dialog>
128+
)}
129+
<Button onClick={handleSave} disabled={saving}>
130+
{page_prompts('action.save')}
131+
</Button>
132+
</CardFooter>
133+
</Card>
134+
);
135+
};
136+
137+
export const PromptSettings = ({ data }: { data: UserPromptsResponse }) => {
138+
const router = useRouter();
139+
140+
const handleSaved = useCallback(() => {
141+
setTimeout(router.refresh, 300);
142+
}, [router]);
143+
144+
return (
145+
<div className="flex flex-col gap-6">
146+
{PROMPT_TYPES.map((promptType) => (
147+
<PromptCard
148+
key={promptType}
149+
promptType={promptType}
150+
detail={data[promptType]}
151+
onSaved={handleSaved}
152+
/>
153+
))}
154+
</div>
155+
);
156+
};

web/src/i18n/en-US.json

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,34 @@
464464
"prompt_template_document_content": "The content of the document from the collection.",
465465
"prompt_template_number_of_question": "The number of questions to be generated."
466466
},
467+
"page_prompts": {
468+
"metadata": {
469+
"title": "Prompt Management",
470+
"description": "Customize system prompts to adjust the AI assistant's behavior and conversation style. Changes override system defaults and can be reset at any time."
471+
},
472+
"agent_system": {
473+
"title": "System Prompt",
474+
"description": "Defines the AI assistant's persona, role, and behavioral guidelines. Affects the base behavior across all conversations."
475+
},
476+
"agent_query": {
477+
"title": "Query Prompt",
478+
"description": "The query template for each conversation. Controls how user questions and knowledge base context are combined before being sent to the AI."
479+
},
480+
"status": {
481+
"customized": "Customized",
482+
"default": "Using Default"
483+
},
484+
"action": {
485+
"save": "Save",
486+
"reset": "Reset to Default",
487+
"reset_confirm": "Reset this prompt to system default?",
488+
"reset_confirm_description": "This will delete your customization and restore the built-in system default prompt."
489+
},
490+
"toast": {
491+
"save_success": "Prompt saved successfully",
492+
"reset_success": "Prompt reset to default"
493+
}
494+
},
467495
"page_quota": {
468496
"metadata": {
469497
"title": "Quotas",
@@ -522,6 +550,7 @@
522550
"models": "Models",
523551
"api_keys": "API Keys",
524552
"audit_logs": "Audit Logs",
525-
"quotas": "Quotas"
553+
"quotas": "Quotas",
554+
"prompts": "Prompt Management"
526555
}
527556
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"metadata": {
3+
"title": "Prompt Management",
4+
"description": "Customize system prompts to adjust the AI assistant's behavior and conversation style. Changes override system defaults and can be reset at any time."
5+
},
6+
"agent_system": {
7+
"title": "System Prompt",
8+
"description": "Defines the AI assistant's persona, role, and behavioral guidelines. Affects the base behavior across all conversations."
9+
},
10+
"agent_query": {
11+
"title": "Query Prompt",
12+
"description": "The query template for each conversation. Controls how user questions and knowledge base context are combined before being sent to the AI."
13+
},
14+
"status": {
15+
"customized": "Customized",
16+
"default": "Using Default"
17+
},
18+
"action": {
19+
"save": "Save",
20+
"reset": "Reset to Default",
21+
"reset_confirm": "Reset this prompt to system default?",
22+
"reset_confirm_description": "This will delete your customization and restore the built-in system default prompt."
23+
},
24+
"toast": {
25+
"save_success": "Prompt saved successfully",
26+
"reset_success": "Prompt reset to default"
27+
}
28+
}

web/src/i18n/zh-CN.json

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,34 @@
468468
"prompt_template_document_content": "从知识库中提取的文档内容",
469469
"prompt_template_number_of_question": "需要生成的问题数量"
470470
},
471+
"page_prompts": {
472+
"metadata": {
473+
"title": "提示词管理",
474+
"description": "自定义系统默认提示词,调整 AI 助手的行为和对话风格。修改后将覆盖系统默认配置,可随时重置恢复默认。"
475+
},
476+
"agent_system": {
477+
"title": "系统提示词",
478+
"description": "定义 AI 助手的人格、角色和行为准则,影响所有对话的基础行为。"
479+
},
480+
"agent_query": {
481+
"title": "查询提示词",
482+
"description": "每次对话的查询模板,控制如何将用户问题与知识库上下文组合后发送给 AI。"
483+
},
484+
"status": {
485+
"customized": "已自定义",
486+
"default": "使用默认"
487+
},
488+
"action": {
489+
"save": "保存",
490+
"reset": "重置为默认",
491+
"reset_confirm": "确认将此提示词重置为系统默认?",
492+
"reset_confirm_description": "重置后,当前自定义内容将被删除,恢复为系统内置默认提示词。"
493+
},
494+
"toast": {
495+
"save_success": "提示词已保存",
496+
"reset_success": "已重置为默认提示词"
497+
}
498+
},
471499
"page_quota": {
472500
"metadata": {
473501
"title": "配额管理",
@@ -523,9 +551,10 @@
523551
"display_empty_title": "新会话",
524552
"more": "更多",
525553
"settings": "设置",
526-
"models": "模型",
527-
"api_keys": "API密钥",
528-
"audit_logs": "审计日志",
529-
"quotas": "配额"
554+
"models": "模型",
555+
"api_keys": "API密钥",
556+
"audit_logs": "审计日志",
557+
"quotas": "配额",
558+
"prompts": "提示词管理"
530559
}
531560
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"metadata": {
3+
"title": "提示词管理",
4+
"description": "自定义系统默认提示词,调整 AI 助手的行为和对话风格。修改后将覆盖系统默认配置,可随时重置恢复默认。"
5+
},
6+
"agent_system": {
7+
"title": "系统提示词",
8+
"description": "定义 AI 助手的人格、角色和行为准则,影响所有对话的基础行为。"
9+
},
10+
"agent_query": {
11+
"title": "查询提示词",
12+
"description": "每次对话的查询模板,控制如何将用户问题与知识库上下文组合后发送给 AI。"
13+
},
14+
"status": {
15+
"customized": "已自定义",
16+
"default": "使用默认"
17+
},
18+
"action": {
19+
"save": "保存",
20+
"reset": "重置为默认",
21+
"reset_confirm": "确认将此提示词重置为系统默认?",
22+
"reset_confirm_description": "重置后,当前自定义内容将被删除,恢复为系统内置默认提示词。"
23+
},
24+
"toast": {
25+
"save_success": "提示词已保存",
26+
"reset_success": "已重置为默认提示词"
27+
}
28+
}

0 commit comments

Comments
 (0)