refactor: standardize dialog component styling and layout for improved consistency and responsiveness.

This commit is contained in:
lintsinghua 2025-12-15 16:13:47 +08:00
parent 7d29fe0f2a
commit e531c8808d
6 changed files with 143 additions and 98 deletions

View File

@ -65,9 +65,7 @@ services:
context: ./frontend
restart: unless-stopped
ports:
- "3000:3000"
environment:
- VITE_API_BASE_URL=http://localhost:8000/api/v1
- "3000:80" # Nginx 监听 80 端口
depends_on:
- backend
networks:

View File

@ -1,3 +1,8 @@
# =============================================
# DeepAudit Frontend Docker 构建
# =============================================
# 使用 Nginx 提供静态文件和反向代理 (支持 SSE 流式传输)
FROM node:20-alpine AS builder
WORKDIR /app
@ -20,38 +25,23 @@ RUN unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY all_proxy ALL_PROXY && \
# 复制源代码
COPY . .
# 构建时使用占位符,运行时替换
ENV VITE_API_BASE_URL=__API_BASE_URL__
# 🔥 构建时使用相对路径 /api - Nginx 会处理代理
ENV VITE_API_BASE_URL=/api/v1
# 构建生产版本
RUN pnpm build
# 生产镜像
FROM node:20-alpine
WORKDIR /app
# 清除代理设置
ENV http_proxy=
ENV https_proxy=
ENV HTTP_PROXY=
ENV HTTPS_PROXY=
# 安装 serve确保无代理
RUN unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY all_proxy ALL_PROXY && \
npm install -g serve
# =============================================
# 生产镜像 - 使用 Nginx (支持 SSE 反向代理)
# =============================================
FROM nginx:alpine
# 复制构建产物
COPY --from=builder /app/dist ./dist
# 复制启动脚本
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
EXPOSE 3000
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["serve", "-s", "dist", "-l", "3000"]
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制 Nginx 配置 (包含 SSE 反向代理配置)
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -16,6 +16,26 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# ========== SSE 流式传输必需配置 ==========
# 禁用代理缓冲,确保事件实时推送
proxy_buffering off;
proxy_cache off;
# 明确告知 Nginx 不要缓冲 (对上游 FastAPI X-Accel-Buffering 头也有效)
proxy_set_header X-Accel-Buffering no;
# 支持 chunked 编码
chunked_transfer_encoding on;
# SSE 长连接超时配置
proxy_read_timeout 300s; # 5 分钟读取超时 (与后端 max_idle 一致)
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
# 保持连接
proxy_http_version 1.1;
proxy_set_header Connection '';
}
# 缓存静态资源

View File

@ -11,7 +11,7 @@ import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { ScrollArea } from '@/components/ui/scroll-area';
import { toast } from 'sonner';
import {
@ -435,12 +435,16 @@ export default function AuditRules() {
{/* Create Rule Set Dialog */}
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
<DialogContent className="max-w-lg cyber-card !fixed p-0 bg-[#0c0c12]">
<DialogHeader className="cyber-card-header">
<Terminal className="w-5 h-5 text-primary" />
<DialogTitle className="text-lg font-bold uppercase tracking-wider text-white"></DialogTitle>
<DialogContent className="!w-[min(90vw,500px)] !max-w-none max-h-[85vh] flex flex-col p-0 gap-0 bg-[#0c0c12] border border-gray-800 rounded-lg">
<DialogHeader className="px-6 py-4 border-b border-gray-800 flex-shrink-0 bg-gray-900/50">
<DialogTitle className="flex items-center gap-3 font-mono text-white">
<div className="p-2 bg-primary/20 rounded border border-primary/30">
<Terminal className="w-5 h-5 text-primary" />
</div>
<span className="text-base font-bold uppercase tracking-wider"></span>
</DialogTitle>
</DialogHeader>
<div className="p-6 space-y-4">
<div className="flex-1 overflow-y-auto p-6 space-y-4">
<div className="space-y-2">
<Label className="text-xs font-bold text-gray-500 uppercase"> *</Label>
<Input value={ruleSetForm.name} onChange={e => setRuleSetForm({ ...ruleSetForm, name: e.target.value })} placeholder="规则集名称" className="cyber-input" />
@ -470,7 +474,7 @@ export default function AuditRules() {
</div>
</div>
</div>
<DialogFooter className="p-4 border-t border-gray-800">
<DialogFooter className="flex-shrink-0 flex justify-end gap-3 px-6 py-4 bg-gray-900/50 border-t border-gray-800">
<Button variant="outline" onClick={() => setShowCreateDialog(false)} className="cyber-btn-outline"></Button>
<Button onClick={handleCreateRuleSet} className="cyber-btn-primary"></Button>
</DialogFooter>
@ -479,12 +483,16 @@ export default function AuditRules() {
{/* Edit Rule Set Dialog */}
<Dialog open={showEditDialog} onOpenChange={setShowEditDialog}>
<DialogContent className="max-w-lg cyber-card !fixed p-0 bg-[#0c0c12]">
<DialogHeader className="cyber-card-header">
<Edit className="w-5 h-5 text-primary" />
<DialogTitle className="text-lg font-bold uppercase tracking-wider text-white"></DialogTitle>
<DialogContent className="!w-[min(90vw,500px)] !max-w-none max-h-[85vh] flex flex-col p-0 gap-0 bg-[#0c0c12] border border-gray-800 rounded-lg">
<DialogHeader className="px-6 py-4 border-b border-gray-800 flex-shrink-0 bg-gray-900/50">
<DialogTitle className="flex items-center gap-3 font-mono text-white">
<div className="p-2 bg-primary/20 rounded border border-primary/30">
<Edit className="w-5 h-5 text-primary" />
</div>
<span className="text-base font-bold uppercase tracking-wider"></span>
</DialogTitle>
</DialogHeader>
<div className="p-6 space-y-4">
<div className="flex-1 overflow-y-auto p-6 space-y-4">
<div className="space-y-2">
<Label className="text-xs font-bold text-gray-500 uppercase"></Label>
<Input value={ruleSetForm.name} onChange={e => setRuleSetForm({ ...ruleSetForm, name: e.target.value })} className="cyber-input" />
@ -510,7 +518,7 @@ export default function AuditRules() {
</div>
</div>
</div>
<DialogFooter className="p-4 border-t border-gray-800">
<DialogFooter className="flex-shrink-0 flex justify-end gap-3 px-6 py-4 bg-gray-900/50 border-t border-gray-800">
<Button variant="outline" onClick={() => setShowEditDialog(false)} className="cyber-btn-outline"></Button>
<Button onClick={handleUpdateRuleSet} className="cyber-btn-primary"></Button>
</DialogFooter>
@ -519,12 +527,16 @@ export default function AuditRules() {
{/* Rule Edit Dialog */}
<Dialog open={showRuleDialog} onOpenChange={setShowRuleDialog}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto cyber-card !fixed p-0 bg-[#0c0c12]">
<DialogHeader className="cyber-card-header">
<Code className="w-5 h-5 text-primary" />
<DialogTitle className="text-lg font-bold uppercase tracking-wider text-white">{selectedRule ? '编辑规则' : '添加规则'}</DialogTitle>
<DialogContent className="!w-[min(90vw,700px)] !max-w-none max-h-[85vh] flex flex-col p-0 gap-0 bg-[#0c0c12] border border-gray-800 rounded-lg">
<DialogHeader className="px-6 py-4 border-b border-gray-800 flex-shrink-0 bg-gray-900/50">
<DialogTitle className="flex items-center gap-3 font-mono text-white">
<div className="p-2 bg-primary/20 rounded border border-primary/30">
<Code className="w-5 h-5 text-primary" />
</div>
<span className="text-base font-bold uppercase tracking-wider">{selectedRule ? '编辑规则' : '添加规则'}</span>
</DialogTitle>
</DialogHeader>
<div className="p-6 space-y-4">
<div className="flex-1 overflow-y-auto p-6 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-xs font-bold text-gray-500 uppercase"> *</Label>
@ -568,7 +580,7 @@ export default function AuditRules() {
<Input value={ruleForm.reference_url} onChange={e => setRuleForm({ ...ruleForm, reference_url: e.target.value })} placeholder="如 https://owasp.org/..." className="cyber-input" />
</div>
</div>
<DialogFooter className="p-4 border-t border-gray-800">
<DialogFooter className="flex-shrink-0 flex justify-end gap-3 px-6 py-4 bg-gray-900/50 border-t border-gray-800">
<Button variant="outline" onClick={() => setShowRuleDialog(false)} className="cyber-btn-outline"></Button>
<Button onClick={selectedRule ? handleUpdateRule : handleAddRule} className="cyber-btn-primary">{selectedRule ? '保存' : '添加'}</Button>
</DialogFooter>
@ -577,16 +589,22 @@ export default function AuditRules() {
{/* Import Dialog */}
<Dialog open={showImportDialog} onOpenChange={setShowImportDialog}>
<DialogContent className="max-w-2xl cyber-card !fixed p-0 bg-[#0c0c12]">
<DialogHeader className="cyber-card-header">
<Upload className="w-5 h-5 text-primary" />
<DialogTitle className="text-lg font-bold uppercase tracking-wider text-white"></DialogTitle>
<DialogContent className="!w-[min(90vw,700px)] !max-w-none max-h-[85vh] flex flex-col p-0 gap-0 bg-[#0c0c12] border border-gray-800 rounded-lg">
<DialogHeader className="px-6 py-4 border-b border-gray-800 flex-shrink-0 bg-gray-900/50">
<DialogTitle className="flex items-center gap-3 font-mono text-white">
<div className="p-2 bg-primary/20 rounded border border-primary/30">
<Upload className="w-5 h-5 text-primary" />
</div>
<div>
<span className="text-base font-bold uppercase tracking-wider"></span>
<p className="text-xs text-gray-500 font-normal mt-0.5"> JSON </p>
</div>
</DialogTitle>
</DialogHeader>
<DialogDescription className="px-6 pt-4 text-gray-400"> JSON </DialogDescription>
<div className="p-6">
<div className="flex-1 overflow-y-auto p-6">
<Textarea value={importJson} onChange={e => setImportJson(e.target.value)} placeholder='{"name": "...", "rules": [...]}' rows={15} className="cyber-input font-mono text-sm text-emerald-400" />
</div>
<DialogFooter className="p-4 border-t border-gray-800">
<DialogFooter className="flex-shrink-0 flex justify-end gap-3 px-6 py-4 bg-gray-900/50 border-t border-gray-800">
<Button variant="outline" onClick={() => setShowImportDialog(false)} className="cyber-btn-outline"></Button>
<Button onClick={handleImport} className="cyber-btn-primary"></Button>
</DialogFooter>

View File

@ -423,9 +423,9 @@ export default function Projects() {
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[85vh] !overflow-y-auto cyber-card border-gray-700 bg-[#0c0c12] p-0 !fixed">
<DialogContent className="!w-[min(90vw,700px)] !max-w-none max-h-[85vh] flex flex-col p-0 gap-0 bg-[#0c0c12] border border-gray-800 rounded-lg">
{/* Terminal Header */}
<div className="flex items-center gap-2 px-4 py-3 bg-[#0a0a0f] border-b border-gray-800/50">
<div className="flex items-center gap-2 px-4 py-3 bg-[#0a0a0f] border-b border-gray-800/50 flex-shrink-0">
<div className="flex items-center gap-1.5">
<div className="w-3 h-3 rounded-full bg-red-500/80" />
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
@ -436,14 +436,14 @@ export default function Projects() {
</span>
</div>
<DialogHeader className="px-6 pt-4">
<DialogHeader className="px-6 pt-4 flex-shrink-0">
<DialogTitle className="font-mono text-lg uppercase tracking-wider flex items-center gap-2 text-white">
<Terminal className="w-5 h-5 text-primary" />
</DialogTitle>
</DialogHeader>
<div className="p-6">
<div className="flex-1 overflow-y-auto p-6">
<Tabs defaultValue="repository" className="w-full">
<TabsList className="flex w-full bg-gray-900/50 border border-gray-800 p-1 h-auto gap-1 rounded">
<TabsTrigger
@ -934,9 +934,9 @@ export default function Projects() {
{/* Edit Dialog */}
<Dialog open={showEditDialog} onOpenChange={setShowEditDialog}>
<DialogContent className="max-w-2xl cyber-card border-gray-700 bg-[#0c0c12] p-0 !fixed">
<DialogContent className="!w-[min(90vw,700px)] !max-w-none max-h-[85vh] flex flex-col p-0 gap-0 bg-[#0c0c12] border border-gray-800 rounded-lg">
{/* Terminal Header */}
<div className="flex items-center gap-2 px-4 py-3 bg-[#0a0a0f] border-b border-gray-800/50">
<div className="flex items-center gap-2 px-4 py-3 bg-[#0a0a0f] border-b border-gray-800/50 flex-shrink-0">
<div className="flex items-center gap-1.5">
<div className="w-3 h-3 rounded-full bg-red-500/80" />
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
@ -947,7 +947,7 @@ export default function Projects() {
</span>
</div>
<DialogHeader className="px-6 pt-4">
<DialogHeader className="px-6 pt-4 flex-shrink-0">
<DialogTitle className="font-mono text-lg uppercase tracking-wider flex items-center gap-2 text-white">
<Edit className="w-5 h-5 text-primary" />
@ -959,7 +959,7 @@ export default function Projects() {
</DialogTitle>
</DialogHeader>
<div className="p-6 flex flex-col gap-6 max-h-[70vh] overflow-y-auto custom-scrollbar">
<div className="flex-1 overflow-y-auto p-6 space-y-6">
{/* 基本信息 */}
<div className="space-y-4">
<h3 className="font-mono font-bold uppercase text-sm text-gray-400 border-b border-gray-800 pb-2"></h3>
@ -1172,7 +1172,7 @@ export default function Projects() {
</div>
</div>
<div className="flex justify-end space-x-3 p-4 border-t border-gray-800 bg-gray-900/30">
<div className="flex-shrink-0 flex justify-end gap-3 px-6 py-4 bg-gray-900/50 border-t border-gray-800">
<Button variant="outline" onClick={() => setShowEditDialog(false)} className="cyber-btn-outline">
</Button>

View File

@ -11,7 +11,7 @@ import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { ScrollArea } from '@/components/ui/scroll-area';
import { toast } from 'sonner';
@ -28,7 +28,7 @@ import {
Terminal,
MessageSquare,
Shield,
Zap,
Code,
AlertTriangle,
Activity,
@ -344,14 +344,23 @@ export default function PromptManager() {
{/* Create/Edit Dialog */}
<Dialog open={showCreateDialog || showEditDialog} onOpenChange={(open) => { if (!open) { setShowCreateDialog(false); setShowEditDialog(false); } }}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto cyber-card p-0 bg-[#0c0c12]">
<DialogHeader className="cyber-card-header">
<Terminal className="w-5 h-5 text-primary" />
<DialogTitle className="text-lg font-bold uppercase tracking-wider text-white">
{showEditDialog ? '编辑模板' : '新建模板'}
<DialogContent className="!w-[min(90vw,700px)] !max-w-none max-h-[85vh] flex flex-col p-0 gap-0 bg-[#0c0c12] border border-gray-800 rounded-lg">
<DialogHeader className="px-6 py-4 border-b border-gray-800 flex-shrink-0 bg-gray-900/50">
<DialogTitle className="flex items-center gap-3 font-mono text-white">
<div className="p-2 bg-primary/20 rounded border border-primary/30">
<Terminal className="w-5 h-5 text-primary" />
</div>
<div>
<span className="text-base font-bold uppercase tracking-wider">
{showEditDialog ? '编辑模板' : '新建模板'}
</span>
<p className="text-xs text-gray-500 font-normal mt-0.5">
{showEditDialog ? 'Edit Template' : 'Create Template'}
</p>
</div>
</DialogTitle>
</DialogHeader>
<div className="p-6 space-y-4">
<div className="flex-1 overflow-y-auto p-6 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-xs font-bold text-gray-500 uppercase"> *</Label>
@ -381,10 +390,10 @@ export default function PromptManager() {
</TabsTrigger>
</TabsList>
<TabsContent value="zh" className="mt-4">
<Textarea value={form.content_zh} onChange={e => setForm({ ...form, content_zh: e.target.value })} placeholder="输入中文提示词内容..." rows={15} className="cyber-input font-mono text-sm text-emerald-400" />
<Textarea value={form.content_zh} onChange={e => setForm({ ...form, content_zh: e.target.value })} placeholder="输入中文提示词内容..." rows={12} className="cyber-input font-mono text-sm text-emerald-400" />
</TabsContent>
<TabsContent value="en" className="mt-4">
<Textarea value={form.content_en} onChange={e => setForm({ ...form, content_en: e.target.value })} placeholder="Enter English prompt content..." rows={15} className="cyber-input font-mono text-sm text-emerald-400" />
<Textarea value={form.content_en} onChange={e => setForm({ ...form, content_en: e.target.value })} placeholder="Enter English prompt content..." rows={12} className="cyber-input font-mono text-sm text-emerald-400" />
</TabsContent>
</Tabs>
<div className="flex items-center gap-2">
@ -392,7 +401,7 @@ export default function PromptManager() {
<Label className="text-xs font-bold text-gray-400 uppercase"></Label>
</div>
</div>
<DialogFooter className="p-4 border-t border-gray-800">
<DialogFooter className="flex-shrink-0 flex justify-end gap-3 px-6 py-4 bg-gray-900/50 border-t border-gray-800">
<Button variant="outline" onClick={() => { setShowCreateDialog(false); setShowEditDialog(false); }} className="cyber-btn-outline"></Button>
<Button onClick={showEditDialog ? handleUpdate : handleCreate} className="cyber-btn-primary">{showEditDialog ? '保存' : '创建'}</Button>
</DialogFooter>
@ -401,15 +410,21 @@ export default function PromptManager() {
{/* Test Dialog */}
<Dialog open={showTestDialog} onOpenChange={setShowTestDialog}>
<DialogContent className="!max-w-6xl w-[90vw] max-h-[90vh] !overflow-y-auto cyber-card p-0 bg-[#0c0c12] !fixed">
<DialogHeader className="cyber-card-header">
<Sparkles className="w-5 h-5 text-violet-400" />
<DialogTitle className="text-lg font-bold uppercase tracking-wider text-white">
: {selectedTemplate?.name}
<DialogContent className="!w-[min(95vw,1200px)] !max-w-none max-h-[85vh] flex flex-col p-0 gap-0 bg-[#0c0c12] border border-gray-800 rounded-lg">
<DialogHeader className="px-6 py-4 border-b border-gray-800 flex-shrink-0 bg-gray-900/50">
<DialogTitle className="flex items-center gap-3 font-mono text-white">
<div className="p-2 bg-violet-500/20 rounded border border-violet-500/30">
<Sparkles className="w-5 h-5 text-violet-400" />
</div>
<div>
<span className="text-base font-bold uppercase tracking-wider">
: {selectedTemplate?.name}
</span>
<p className="text-xs text-gray-500 font-normal mt-0.5">使</p>
</div>
</DialogTitle>
</DialogHeader>
<DialogDescription className="px-6 pt-4 text-gray-400">使</DialogDescription>
<div className="p-6 grid grid-cols-2 gap-6">
<div className="flex-1 overflow-y-auto p-6 grid grid-cols-2 gap-6">
{/* Left: Input */}
<div className="space-y-4">
<div className="grid grid-cols-2 gap-3">
@ -470,10 +485,9 @@ export default function PromptManager() {
<div className="p-3 bg-gray-900/50 border-b border-gray-800 flex items-center justify-between">
<span className="text-xs font-bold uppercase text-gray-500"></span>
<div className="flex items-center gap-2">
<div className={`text-2xl font-bold ${
testResult.result.quality_score >= 80 ? 'text-emerald-400' :
<div className={`text-2xl font-bold ${testResult.result.quality_score >= 80 ? 'text-emerald-400' :
testResult.result.quality_score >= 60 ? 'text-amber-400' : 'text-rose-400'
}`}>
}`}>
{testResult.result.quality_score}
</div>
<span className="text-xs text-gray-500">/ 100</span>
@ -493,11 +507,10 @@ export default function PromptManager() {
</div>
{testResult.result.issues.map((issue: any, idx: number) => (
<div key={idx} className="cyber-card p-0 overflow-hidden">
<div className={`px-3 py-2 border-b border-gray-800 flex items-center justify-between ${
issue.severity === 'critical' ? 'bg-rose-500/20 text-rose-400' :
<div className={`px-3 py-2 border-b border-gray-800 flex items-center justify-between ${issue.severity === 'critical' ? 'bg-rose-500/20 text-rose-400' :
issue.severity === 'high' ? 'bg-orange-500/20 text-orange-400' :
issue.severity === 'medium' ? 'bg-amber-500/20 text-amber-400' : 'bg-sky-500/20 text-sky-400'
}`}>
issue.severity === 'medium' ? 'bg-amber-500/20 text-amber-400' : 'bg-sky-500/20 text-sky-400'
}`}>
<span className="font-bold text-xs uppercase">{issue.severity}</span>
{issue.line && <span className="text-xs opacity-80"> {issue.line}</span>}
</div>
@ -565,7 +578,7 @@ export default function PromptManager() {
</div>
</div>
</div>
<DialogFooter className="p-4 border-t border-gray-800">
<DialogFooter className="flex-shrink-0 flex justify-end gap-3 px-6 py-4 bg-gray-900/50 border-t border-gray-800">
<Button variant="outline" onClick={() => setShowTestDialog(false)} className="cyber-btn-outline"></Button>
</DialogFooter>
</DialogContent>
@ -573,15 +586,21 @@ export default function PromptManager() {
{/* View Dialog */}
<Dialog open={showViewDialog} onOpenChange={setShowViewDialog}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto cyber-card p-0 bg-[#0c0c12]">
<DialogHeader className="cyber-card-header">
<FileText className="w-5 h-5 text-primary" />
<DialogTitle className="text-lg font-bold uppercase tracking-wider text-white">
{viewTemplate?.name}
<DialogContent className="!w-[min(90vw,800px)] !max-w-none max-h-[85vh] flex flex-col p-0 gap-0 bg-[#0c0c12] border border-gray-800 rounded-lg">
<DialogHeader className="px-6 py-4 border-b border-gray-800 flex-shrink-0 bg-gray-900/50">
<DialogTitle className="flex items-center gap-3 font-mono text-white">
<div className="p-2 bg-primary/20 rounded border border-primary/30">
<FileText className="w-5 h-5 text-primary" />
</div>
<div>
<span className="text-base font-bold uppercase tracking-wider">
{viewTemplate?.name}
</span>
<p className="text-xs text-gray-500 font-normal mt-0.5">{viewTemplate?.description || 'View Template'}</p>
</div>
</DialogTitle>
</DialogHeader>
<DialogDescription className="px-6 pt-4 text-gray-400">{viewTemplate?.description}</DialogDescription>
<div className="p-6 space-y-4">
<div className="flex-1 overflow-y-auto p-6 space-y-4">
<div className="flex flex-wrap gap-2 mb-4">
{viewTemplate?.is_system && <Badge className="cyber-badge-info"></Badge>}
{viewTemplate?.is_default && <Badge className="cyber-badge-success"></Badge>}
@ -614,7 +633,7 @@ export default function PromptManager() {
</TabsContent>
</Tabs>
</div>
<DialogFooter className="p-4 border-t border-gray-800 flex gap-2">
<DialogFooter className="flex-shrink-0 flex justify-end gap-3 px-6 py-4 bg-gray-900/50 border-t border-gray-800">
<Button variant="outline" onClick={() => copyToClipboard(viewTemplate?.content_zh || viewTemplate?.content_en || '')} className="cyber-btn-outline">
<Copy className="w-4 h-4 mr-2" />