@zenith-open/zenithcms-plugin-ai-architect-ui 1.0.0-beta.10 → 1.0.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AIWriterPage.d.ts +2 -0
- package/dist/AIWriterPage.js +192 -0
- package/dist/SettingsAi.d.ts +8 -0
- package/dist/SettingsAi.js +245 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plugin-ai-architect-ui/src/AIWriterPage.js +59 -54
- package/dist/plugin-ai-architect-ui/src/SettingsAi.js +29 -24
- package/dist/plugin-ai-architect-ui/src/index.js +17 -1
- package/dist/plugin-ai-architect-ui/src/plugin.js +10 -2
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.js +2 -0
- package/package.json +1 -2
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import { Sparkles, Send, Loader2, Copy, Zap, Terminal, Cpu, PenTool, ShieldCheck, Search, Image as ImageIcon, CheckCircle2, AlertCircle, ChevronRight, RotateCcw, Save, Download, Code2, Wand2, Hash } from 'lucide-react';
|
|
4
|
+
import api from '../lib/api';
|
|
5
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
import toast from 'react-hot-toast';
|
|
8
|
+
import { useTheme } from '../context/ThemeContext';
|
|
9
|
+
import { PageHeader } from '../components/ui/PageHeader';
|
|
10
|
+
// ── Tool Definitions ───────────────────────────────────────────────────────────
|
|
11
|
+
const TOOLS = [
|
|
12
|
+
{ id: 'seo', name: 'SEO Analysis', icon: Search, endpoint: '/content-tools/seo-analysis', desc: 'Score title, meta, content', color: 'text-amber-400' },
|
|
13
|
+
{ id: 'quality', name: 'Quality Audit', icon: ShieldCheck, endpoint: '/content-tools/quality', desc: 'Readability + word structure', color: 'text-z-active-text' },
|
|
14
|
+
{ id: 'improve', name: 'Refine Text', icon: Wand2, endpoint: '/content-tools/ai/improve', desc: 'AI-powered rewrite', color: 'text-purple-400' },
|
|
15
|
+
{ id: 'meta', name: 'Meta Generator', icon: Hash, endpoint: '/content-tools/ai/meta-description', desc: 'Auto SEO meta description', color: 'text-z-active-text' },
|
|
16
|
+
{ id: 'alt', name: 'Alt Text', icon: ImageIcon, endpoint: '/content-tools/ai/alt-text', desc: 'Generate image alt text', color: 'text-pink-400' },
|
|
17
|
+
];
|
|
18
|
+
const MODES = [
|
|
19
|
+
{ id: 'writer', label: 'Writer', icon: PenTool, desc: 'Generate content from a prompt' },
|
|
20
|
+
{ id: 'architect', label: 'Architect', icon: Cpu, desc: 'Design a collection schema with AI' },
|
|
21
|
+
{ id: 'tools', label: 'Tools', icon: Zap, desc: 'SEO, quality, meta & more' },
|
|
22
|
+
];
|
|
23
|
+
// ── Result Renderers ──────────────────────────────────────────────────────────
|
|
24
|
+
function SeoResultView({ data }) {
|
|
25
|
+
return (_jsxs("div", { className: "space-y-5", children: [_jsxs("div", { className: "flex items-end gap-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary mb-1", children: "SEO Score" }), _jsx("span", { className: cn('text-5xl font-black tabular-nums', data.score >= 70 ? 'text-z-active-text' : data.score >= 45 ? 'text-amber-400' : 'text-red-400'), children: data.score }), _jsx("span", { className: "text-lg text-z-secondary font-black", children: "/100" })] }), _jsx("div", { className: "flex-1 h-2 bg-z-hover rounded-full overflow-hidden mb-3", children: _jsx("div", { className: cn('h-full transition-all duration-700', data.score >= 70 ? 'bg-z-accent' : data.score >= 45 ? 'bg-amber-500' : 'bg-red-500'), style: { width: `${data.score}%` } }) })] }), data.passed?.length > 0 && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-active-text/70", children: "Passing" }), data.passed.map((p, i) => (_jsxs("div", { className: "flex items-center gap-2.5 text-[10px] text-z-muted", children: [_jsx(CheckCircle2, { size: 11, className: "text-z-active-text flex-shrink-0" }), p] }, i)))] })), data.issues?.length > 0 && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-red-500/70", children: "Issues" }), data.issues.map((p, i) => (_jsxs("div", { className: "flex items-center gap-2.5 text-[10px] text-z-muted", children: [_jsx(AlertCircle, { size: 11, className: "text-red-500 flex-shrink-0" }), p] }, i)))] })), data.suggestions?.length > 0 && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-amber-500/70", children: "Suggestions" }), data.suggestions.map((p, i) => (_jsxs("div", { className: "flex items-center gap-2.5 text-[10px] text-z-muted", children: [_jsx(ChevronRight, { size: 11, className: "text-amber-500 flex-shrink-0" }), p] }, i)))] }))] }));
|
|
26
|
+
}
|
|
27
|
+
function QualityResultView({ data }) {
|
|
28
|
+
const gradeColor = { A: 'text-z-active-text', B: 'text-z-active-text', C: 'text-amber-400', D: 'text-orange-400', F: 'text-red-400' };
|
|
29
|
+
return (_jsxs("div", { className: "space-y-5", children: [_jsxs("div", { className: "flex items-start gap-6", children: [_jsxs("div", { children: [_jsx("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary mb-1", children: "Grade" }), _jsx("span", { className: cn('text-6xl font-black', gradeColor[data.grade] || 'text-white'), children: data.grade })] }), _jsx("div", { className: "grid grid-cols-3 gap-3 flex-1 pt-1", children: [
|
|
30
|
+
{ label: 'Words', val: data.wordCount },
|
|
31
|
+
{ label: 'Sentences', val: data.sentenceCount },
|
|
32
|
+
{ label: 'Avg Words/Sent', val: data.avgWordsPerSentence },
|
|
33
|
+
].map(m => (_jsxs("div", { className: "bg-z-hover border border-z-border p-3", children: [_jsx("p", { className: "text-[8px] font-black uppercase tracking-widest text-z-secondary mb-1", children: m.label }), _jsx("p", { className: "text-lg font-black text-white tabular-nums", children: m.val })] }, m.label))) })] }), data.issues?.length > 0 && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-red-500/70", children: "Issues" }), data.issues.map((p, i) => _jsxs("div", { className: "flex items-center gap-2.5 text-[10px] text-z-muted", children: [_jsx(AlertCircle, { size: 11, className: "text-red-500 flex-shrink-0" }), p] }, i))] })), data.suggestions?.length > 0 && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-active-text/70", children: "Suggestions" }), data.suggestions.map((p, i) => _jsxs("div", { className: "flex items-center gap-2.5 text-[10px] text-z-muted", children: [_jsx(CheckCircle2, { size: 11, className: "text-z-active-text flex-shrink-0" }), p] }, i))] }))] }));
|
|
34
|
+
}
|
|
35
|
+
function SchemaResultView({ data }) {
|
|
36
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-3 flex-wrap", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-black text-white tracking-tight", children: data.name }), _jsxs("p", { className: "text-[9px] text-z-secondary font-mono mt-0.5", children: ["/", data.slug] })] }), _jsxs("div", { className: "flex gap-2", children: [data.drafts && _jsx("span", { className: "px-2 py-0.5 text-[7px] font-black uppercase tracking-widest bg-z-accent/10 border border-z-accent/20 text-z-active-text", children: "Drafts" }), data.timestamps && _jsx("span", { className: "px-2 py-0.5 text-[7px] font-black uppercase tracking-widest bg-gray-500/10 border border-gray-500/20 text-z-muted", children: "Timestamps" })] })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsxs("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary", children: [data.fields?.length || 0, " Fields"] }), data.fields?.map((f, i) => (_jsxs("div", { className: "flex items-center justify-between px-3 py-2 bg-z-hover border border-z-border hover:border-z-border-strong transition-colors group", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-[9px] font-mono text-z-active-text", children: f.name }), f.required && _jsx("span", { className: "text-[6px] font-black text-red-500 uppercase tracking-wider", children: "required" })] }), _jsx("span", { className: "text-[8px] text-gray-600 uppercase tracking-widest font-black", children: f.type })] }, i)))] })] }));
|
|
37
|
+
}
|
|
38
|
+
// ── Main Component ─────────────────────────────────────────────────────────────
|
|
39
|
+
const AIWriterPage = () => {
|
|
40
|
+
const { theme } = useTheme();
|
|
41
|
+
const dark = theme === 'dark';
|
|
42
|
+
const [mode, setMode] = useState('writer');
|
|
43
|
+
const [prompt, setPrompt] = useState('');
|
|
44
|
+
const [loading, setLoading] = useState(false);
|
|
45
|
+
const [result, setResult] = useState(null);
|
|
46
|
+
const [activeTool, setActiveTool] = useState('seo');
|
|
47
|
+
const [history, setHistory] = useState([]);
|
|
48
|
+
const [schemaForSave, setSchemaForSave] = useState(null);
|
|
49
|
+
const [savingSchema, setSavingSchema] = useState(false);
|
|
50
|
+
const textareaRef = useRef(null);
|
|
51
|
+
// Auto-resize textarea
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (textareaRef.current) {
|
|
54
|
+
textareaRef.current.style.height = 'auto';
|
|
55
|
+
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 300)}px`;
|
|
56
|
+
}
|
|
57
|
+
}, [prompt]);
|
|
58
|
+
const handleExecute = async () => {
|
|
59
|
+
if (!prompt.trim() && activeTool !== 'alt')
|
|
60
|
+
return;
|
|
61
|
+
setLoading(true);
|
|
62
|
+
setResult(null);
|
|
63
|
+
setSchemaForSave(null);
|
|
64
|
+
try {
|
|
65
|
+
let res;
|
|
66
|
+
let resultData;
|
|
67
|
+
if (mode === 'architect') {
|
|
68
|
+
res = await api.post('/system/ai-architect', { prompt });
|
|
69
|
+
resultData = res.data.data.schema;
|
|
70
|
+
setSchemaForSave(resultData);
|
|
71
|
+
}
|
|
72
|
+
else if (mode === 'writer') {
|
|
73
|
+
res = await api.post('/content-tools/ai/generate', { prompt });
|
|
74
|
+
resultData = res.data.data.text;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const tool = TOOLS.find(t => t.id === activeTool);
|
|
78
|
+
let payload = { content: prompt };
|
|
79
|
+
if (activeTool === 'improve') {
|
|
80
|
+
payload = { text: prompt, instruction: 'Make it more professional, clear, and concise. Improve grammar and flow.' };
|
|
81
|
+
}
|
|
82
|
+
else if (activeTool === 'meta') {
|
|
83
|
+
payload = { title: 'Content', content: prompt };
|
|
84
|
+
}
|
|
85
|
+
else if (activeTool === 'seo') {
|
|
86
|
+
payload = { title: prompt.split('\n')[0]?.substring(0, 60), content: prompt, description: prompt.substring(0, 160) };
|
|
87
|
+
}
|
|
88
|
+
else if (activeTool === 'alt') {
|
|
89
|
+
payload = { imageUrl: prompt };
|
|
90
|
+
}
|
|
91
|
+
res = await api.post(tool.endpoint, payload);
|
|
92
|
+
const d = res.data.data;
|
|
93
|
+
if (activeTool === 'seo')
|
|
94
|
+
resultData = d;
|
|
95
|
+
else if (activeTool === 'quality')
|
|
96
|
+
resultData = d;
|
|
97
|
+
else if (activeTool === 'improve')
|
|
98
|
+
resultData = d.text;
|
|
99
|
+
else if (activeTool === 'meta')
|
|
100
|
+
resultData = d.description;
|
|
101
|
+
else if (activeTool === 'alt')
|
|
102
|
+
resultData = d.altText;
|
|
103
|
+
}
|
|
104
|
+
setResult(resultData);
|
|
105
|
+
setHistory(prev => [{
|
|
106
|
+
id: Date.now().toString(),
|
|
107
|
+
mode,
|
|
108
|
+
prompt: prompt.substring(0, 80),
|
|
109
|
+
result: resultData,
|
|
110
|
+
timestamp: new Date()
|
|
111
|
+
}, ...prev.slice(0, 19)]);
|
|
112
|
+
toast.success('Generated');
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || 'AI request failed';
|
|
116
|
+
toast.error(msg);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
setLoading(false);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const handleKeyDown = (e) => {
|
|
123
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter')
|
|
124
|
+
handleExecute();
|
|
125
|
+
};
|
|
126
|
+
const saveSchemaToDb = async () => {
|
|
127
|
+
if (!schemaForSave)
|
|
128
|
+
return;
|
|
129
|
+
setSavingSchema(true);
|
|
130
|
+
try {
|
|
131
|
+
await api.post('/schemas', schemaForSave);
|
|
132
|
+
toast.success(`Collection "${schemaForSave.name}" created!`);
|
|
133
|
+
setSchemaForSave(null);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
toast.error(err?.response?.data?.error?.message || 'Failed to save schema');
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
setSavingSchema(false);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const copyResult = () => {
|
|
143
|
+
const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
144
|
+
navigator.clipboard.writeText(text);
|
|
145
|
+
toast.success('Copied to clipboard');
|
|
146
|
+
};
|
|
147
|
+
const downloadResult = () => {
|
|
148
|
+
const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
149
|
+
const ext = mode === 'architect' ? 'json' : 'txt';
|
|
150
|
+
const blob = new Blob([text], { type: 'text/plain' });
|
|
151
|
+
const url = URL.createObjectURL(blob);
|
|
152
|
+
const a = document.createElement('a');
|
|
153
|
+
a.href = url;
|
|
154
|
+
a.download = `zenith-ai-output.${ext}`;
|
|
155
|
+
a.click();
|
|
156
|
+
URL.revokeObjectURL(url);
|
|
157
|
+
};
|
|
158
|
+
const renderResult = () => {
|
|
159
|
+
if (!result)
|
|
160
|
+
return null;
|
|
161
|
+
if (mode === 'architect')
|
|
162
|
+
return _jsx(SchemaResultView, { data: result });
|
|
163
|
+
if (mode === 'tools' && activeTool === 'seo')
|
|
164
|
+
return _jsx(SeoResultView, { data: result });
|
|
165
|
+
if (mode === 'tools' && activeTool === 'quality')
|
|
166
|
+
return _jsx(QualityResultView, { data: result });
|
|
167
|
+
return (_jsx(motion.div, { initial: { opacity: 0, y: 4 }, animate: { opacity: 1, y: 0 }, className: "whitespace-pre-wrap text-[12px] leading-relaxed text-gray-300 font-sans", children: typeof result === 'string' ? result : JSON.stringify(result, null, 2) }));
|
|
168
|
+
};
|
|
169
|
+
const activePlaceholder = mode === 'architect'
|
|
170
|
+
? 'Describe a collection schema... e.g., "An e-commerce product with variants, pricing, and inventory tracking"'
|
|
171
|
+
: mode === 'writer'
|
|
172
|
+
? 'Describe the content you want to generate... e.g., "Write a compelling blog intro about sustainable fashion"'
|
|
173
|
+
: activeTool === 'alt'
|
|
174
|
+
? 'Paste an image URL to generate alt text...'
|
|
175
|
+
: activeTool === 'meta'
|
|
176
|
+
? 'Paste your content to generate a meta description...'
|
|
177
|
+
: 'Paste text to analyze...';
|
|
178
|
+
return (_jsxs("div", { className: "flex flex-col h-[calc(100vh-64px)] overflow-hidden", children: [_jsx(PageHeader, { title: "AI Architect", description: "Generate content, design schemas, and analyze text quality", actions: _jsx("div", { className: cn('flex p-0.5 border', dark ? 'bg-black border-z-border' : 'bg-z-panel border-z-border'), children: MODES.map(m => (_jsxs("button", { onClick: () => { setMode(m.id); setResult(null); setSchemaForSave(null); }, title: m.desc, className: cn('flex items-center gap-2 px-4 py-2 text-[9px] font-black uppercase tracking-widest transition-all', mode === m.id
|
|
179
|
+
? (dark ? 'bg-white text-black' : 'bg-gray-900 text-white')
|
|
180
|
+
: (dark ? 'text-z-secondary hover:text-white' : 'text-z-secondary hover:text-z-primary')), children: [_jsx(m.icon, { size: 11 }), _jsx("span", { className: "hidden sm:inline", children: m.label })] }, m.id))) }) }), _jsxs("div", { className: "flex flex-1 overflow-hidden", children: [_jsxs("div", { className: cn('w-56 flex-shrink-0 border-r flex flex-col hidden lg:flex', dark ? 'border-z-border bg-black' : 'border-z-border bg-gray-50'), children: [_jsx("div", { className: cn('px-4 py-3 border-b flex-shrink-0', dark ? 'border-z-border' : 'border-z-border'), children: _jsx("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary", children: "History" }) }), _jsxs("div", { className: "flex-1 overflow-y-auto p-2 space-y-1", children: [history.length === 0 && (_jsx("p", { className: "text-[8px] text-gray-600 uppercase tracking-widest p-2 text-center mt-4", children: "No history yet" })), history.map(h => (_jsxs("button", { onClick: () => { setResult(h.result); setSchemaForSave(h.mode === 'architect' ? h.result : null); }, className: cn('w-full text-left p-2.5 border border-transparent transition-all', dark ? 'hover:bg-z-hover hover:border-z-border' : 'hover:bg-white hover:border-z-border'), children: [_jsx("div", { className: "flex items-center gap-1.5 mb-1", children: _jsx("span", { className: cn('text-[7px] font-black uppercase tracking-widest', h.mode === 'architect' ? 'text-purple-400' : h.mode === 'tools' ? 'text-amber-400' : 'text-z-active-text'), children: h.mode }) }), _jsx("p", { className: "text-[9px] text-z-secondary truncate", children: h.prompt })] }, h.id)))] })] }), _jsxs("div", { className: "flex-1 flex flex-col min-w-0 border-r", style: { borderColor: dark ? 'rgba(255,255,255,0.08)' : '#e5e7eb' }, children: [_jsx(AnimatePresence, { children: mode === 'tools' && (_jsx(motion.div, { initial: { height: 0, opacity: 0 }, animate: { height: 'auto', opacity: 1 }, exit: { height: 0, opacity: 0 }, className: cn('flex-shrink-0 border-b overflow-hidden', dark ? 'border-z-border' : 'border-z-border'), children: _jsx("div", { className: "p-3 flex gap-2 flex-wrap", children: TOOLS.map(tool => (_jsxs("button", { onClick: () => { setActiveTool(tool.id); setResult(null); }, title: tool.desc, className: cn('flex items-center gap-2 px-3 py-2 text-[9px] font-black uppercase tracking-widest border transition-all', activeTool === tool.id
|
|
181
|
+
? (dark ? 'bg-white text-black border-white' : 'bg-gray-900 text-white border-gray-900')
|
|
182
|
+
: (dark ? 'border-z-border text-z-secondary hover:text-white hover:border-white/20' : 'border-z-border text-z-secondary hover:border-gray-400 hover:text-z-primary')), children: [_jsx(tool.icon, { size: 11, className: activeTool === tool.id ? '' : tool.color }), tool.name] }, tool.id))) }) })) }), _jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [_jsxs("div", { className: cn('flex-shrink-0 flex items-center gap-3 px-5 py-3 border-b', dark ? 'border-z-border' : 'border-z-border'), children: [_jsx(Terminal, { size: 12, className: "text-z-secondary" }), _jsx("span", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary", children: mode === 'architect' ? 'Schema Prompt' : mode === 'writer' ? 'Content Prompt' : TOOLS.find(t => t.id === activeTool)?.name }), _jsx("span", { className: "ml-auto text-[8px] text-gray-600 uppercase tracking-widest hidden sm:block", children: "\u2318 + Enter to run" })] }), _jsx("textarea", { ref: textareaRef, value: prompt, onChange: e => setPrompt(e.target.value), onKeyDown: handleKeyDown, placeholder: activePlaceholder, className: cn('flex-1 w-full p-5 text-sm outline-none resize-none font-sans leading-relaxed', dark
|
|
183
|
+
? 'bg-transparent text-white placeholder:text-gray-700'
|
|
184
|
+
: 'bg-transparent text-z-primary placeholder:text-z-muted') }), _jsx("div", { className: cn('flex-shrink-0 p-4 border-t', dark ? 'border-z-border' : 'border-z-border'), children: _jsxs("div", { className: "flex items-center gap-3", children: [prompt.trim() && (_jsx("button", { onClick: () => { setPrompt(''); setResult(null); }, className: "p-2.5 text-gray-600 hover:text-white transition-colors border border-transparent hover:border-white/10", title: "Clear", children: _jsx(RotateCcw, { size: 14 }) })), _jsx("button", { onClick: handleExecute, disabled: loading || (!prompt.trim() && activeTool !== 'alt'), className: cn('flex-1 py-3 font-black uppercase tracking-widest text-[10px] transition-all flex items-center justify-center gap-2.5', 'bg-z-accent hover:opacity-90 text-white', 'disabled:opacity-40 disabled:cursor-not-allowed', 'shadow-sm hover:shadow-sm'), children: loading
|
|
185
|
+
? _jsxs(_Fragment, { children: [_jsx(Loader2, { size: 13, className: "animate-spin" }), " Generating\u2026"] })
|
|
186
|
+
: _jsxs(_Fragment, { children: [_jsx(Send, { size: 13 }), " ", mode === 'architect' ? 'Design Schema' : mode === 'writer' ? 'Generate Content' : 'Analyze'] }) })] }) })] })] }), _jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [_jsxs("div", { className: cn('flex-shrink-0 flex items-center justify-between px-5 py-3 border-b', dark ? 'border-z-border' : 'border-z-border'), children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx(Sparkles, { size: 12, className: result ? 'text-z-active-text' : 'text-z-secondary' }), _jsx("span", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary", children: "Output" }), result && _jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-z-accent shadow-sm" })] }), result && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { onClick: copyResult, title: "Copy", className: "p-2 text-z-secondary hover:text-white transition-colors border border-transparent hover:border-white/10", children: _jsx(Copy, { size: 13 }) }), _jsx("button", { onClick: downloadResult, title: "Download", className: "p-2 text-z-secondary hover:text-white transition-colors border border-transparent hover:border-white/10", children: _jsx(Download, { size: 13 }) }), mode === 'architect' && (_jsx("button", { onClick: () => { setResult(null); setSchemaForSave(null); }, title: "Clear", className: "p-2 text-z-secondary hover:text-white transition-colors border border-transparent hover:border-white/10", children: _jsx(RotateCcw, { size: 13 }) }))] }))] }), _jsx("div", { className: "flex-1 overflow-auto p-6", children: loading ? (_jsxs("div", { className: "h-full flex flex-col items-center justify-center gap-4", children: [_jsxs("div", { className: "relative", children: [_jsx(Loader2, { size: 32, className: "animate-spin text-z-active-text" }), _jsx("div", { className: "absolute inset-0 blur-xl bg-z-accent/20 animate-pulse" })] }), _jsx("p", { className: "text-[9px] font-black uppercase tracking-[0.4em] text-z-secondary animate-pulse", children: mode === 'architect' ? 'Designing Schema…' : mode === 'writer' ? 'Writing Content…' : 'Analyzing…' })] })) : !result ? (_jsxs("div", { className: "h-full flex flex-col items-center justify-center gap-5 opacity-30", children: [_jsx(Cpu, { size: 40, className: "text-z-secondary" }), _jsxs("div", { className: "text-center space-y-1", children: [_jsx("p", { className: "text-[9px] font-black uppercase tracking-[0.4em] text-z-secondary", children: "Awaiting Input" }), _jsx("p", { className: "text-[8px] text-gray-600 uppercase tracking-widest", children: mode === 'architect' ? 'Describe a collection to generate a schema' : mode === 'writer' ? 'Write a prompt to generate content' : 'Paste content to analyze' })] })] })) : (_jsx(AnimatePresence, { mode: "wait", children: _jsx(motion.div, { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.2 }, children: renderResult() }, JSON.stringify(result).substring(0, 50)) })) }), _jsx(AnimatePresence, { children: result && mode === 'architect' && schemaForSave && (_jsx(motion.div, { initial: { height: 0, opacity: 0 }, animate: { height: 'auto', opacity: 1 }, exit: { height: 0, opacity: 0 }, className: cn('flex-shrink-0 border-t overflow-hidden', dark ? 'border-z-border' : 'border-z-border'), children: _jsxs("div", { className: "px-5 py-3 flex items-center justify-between gap-3", children: [_jsx("p", { className: "text-[8px] text-z-secondary uppercase tracking-widest", children: "Schema looks good? Save it as a live collection." }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { onClick: () => {
|
|
187
|
+
const text = JSON.stringify(schemaForSave, null, 2);
|
|
188
|
+
navigator.clipboard.writeText(text);
|
|
189
|
+
toast.success('Schema copied as JSON');
|
|
190
|
+
}, className: cn('px-4 py-2 border text-[9px] font-black uppercase tracking-widest flex items-center gap-2 transition-all', dark ? 'border-z-border text-z-muted hover:text-white hover:border-white/20' : 'border-z-border text-z-secondary hover:border-gray-400'), children: [_jsx(Code2, { size: 11 }), " Copy JSON"] }), _jsxs("button", { onClick: saveSchemaToDb, disabled: savingSchema, className: "px-5 py-2 bg-z-accent hover:opacity-90 text-white text-[9px] font-black uppercase tracking-widest flex items-center gap-2 transition-all disabled:opacity-50 shadow-sm", children: [savingSchema ? _jsx(Loader2, { size: 11, className: "animate-spin" }) : _jsx(Save, { size: 11 }), "Save Collection"] })] })] }) })) })] })] })] }));
|
|
191
|
+
};
|
|
192
|
+
export default AIWriterPage;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Lock, Loader2, CheckCircle2, AlertCircle, ExternalLink, Eye, EyeOff, Cpu, Zap, ChevronRight, Info, TestTube2 } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
import api from '../../lib/api';
|
|
6
|
+
import toast from 'react-hot-toast';
|
|
7
|
+
const PROVIDERS = [
|
|
8
|
+
{
|
|
9
|
+
id: 'openrouter',
|
|
10
|
+
name: 'OpenRouter',
|
|
11
|
+
color: 'text-z-active-text',
|
|
12
|
+
description: 'Unified gateway to 200+ models from any provider via one API key',
|
|
13
|
+
docsUrl: 'https://openrouter.ai/keys',
|
|
14
|
+
keyPlaceholder: 'sk-or-v1-...',
|
|
15
|
+
keyField: 'openRouterApiKey',
|
|
16
|
+
badge: 'Recommended',
|
|
17
|
+
models: [
|
|
18
|
+
{ value: 'anthropic/claude-3.5-sonnet', label: 'Claude 3.5 Sonnet', tier: 'pro' },
|
|
19
|
+
{ value: 'anthropic/claude-3.5-haiku', label: 'Claude 3.5 Haiku', tier: 'pro' },
|
|
20
|
+
{ value: 'anthropic/claude-3-opus', label: 'Claude 3 Opus', tier: 'ultra' },
|
|
21
|
+
{ value: 'openai/gpt-4o', label: 'GPT-4o', tier: 'pro' },
|
|
22
|
+
{ value: 'openai/gpt-4o-mini', label: 'GPT-4o Mini', tier: 'free' },
|
|
23
|
+
{ value: 'openai/gpt-4-turbo', label: 'GPT-4 Turbo', tier: 'pro' },
|
|
24
|
+
{ value: 'google/gemini-pro-1.5', label: 'Gemini 1.5 Pro', tier: 'pro' },
|
|
25
|
+
{ value: 'google/gemini-flash-1.5', label: 'Gemini 1.5 Flash', tier: 'free' },
|
|
26
|
+
{ value: 'meta-llama/llama-3.3-70b-instruct', label: 'Llama 3.3 70B', tier: 'free' },
|
|
27
|
+
{ value: 'mistralai/mistral-large', label: 'Mistral Large', tier: 'pro' },
|
|
28
|
+
{ value: 'mistralai/mixtral-8x7b-instruct', label: 'Mixtral 8x7B', tier: 'free' },
|
|
29
|
+
{ value: 'deepseek/deepseek-r1', label: 'DeepSeek R1', tier: 'pro' },
|
|
30
|
+
{ value: 'x-ai/grok-beta', label: 'Grok Beta', tier: 'pro' },
|
|
31
|
+
{ value: 'cohere/command-r-plus', label: 'Cohere Command R+', tier: 'pro' },
|
|
32
|
+
{ value: 'perplexity/llama-3.1-sonar-large-128k-online', label: 'Perplexity Sonar Large', tier: 'pro' },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'openai',
|
|
37
|
+
name: 'OpenAI',
|
|
38
|
+
color: 'text-z-active-text',
|
|
39
|
+
description: 'Direct access to GPT-4o, o1, and all OpenAI models',
|
|
40
|
+
docsUrl: 'https://platform.openai.com/api-keys',
|
|
41
|
+
keyPlaceholder: 'sk-proj-...',
|
|
42
|
+
keyField: 'openaiApiKey',
|
|
43
|
+
models: [
|
|
44
|
+
{ value: 'gpt-4o', label: 'GPT-4o', tier: 'pro' },
|
|
45
|
+
{ value: 'gpt-4o-mini', label: 'GPT-4o Mini', tier: 'free' },
|
|
46
|
+
{ value: 'gpt-4-turbo', label: 'GPT-4 Turbo', tier: 'pro' },
|
|
47
|
+
{ value: 'o1-preview', label: 'o1 Preview', tier: 'ultra' },
|
|
48
|
+
{ value: 'o1-mini', label: 'o1 Mini', tier: 'pro' },
|
|
49
|
+
{ value: 'o3-mini', label: 'o3 Mini', tier: 'pro' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'anthropic',
|
|
54
|
+
name: 'Anthropic',
|
|
55
|
+
color: 'text-orange-400',
|
|
56
|
+
description: 'Direct access to Claude models with vision and context support',
|
|
57
|
+
docsUrl: 'https://console.anthropic.com/settings/keys',
|
|
58
|
+
keyPlaceholder: 'sk-ant-...',
|
|
59
|
+
keyField: 'anthropicApiKey',
|
|
60
|
+
models: [
|
|
61
|
+
{ value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', tier: 'pro' },
|
|
62
|
+
{ value: 'claude-3-5-haiku-20241022', label: 'Claude 3.5 Haiku', tier: 'free' },
|
|
63
|
+
{ value: 'claude-3-opus-20240229', label: 'Claude 3 Opus', tier: 'ultra' },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'google',
|
|
68
|
+
name: 'Google Gemini',
|
|
69
|
+
color: 'text-z-active-text',
|
|
70
|
+
description: 'Gemini Pro/Flash with long context and multimodal capabilities',
|
|
71
|
+
docsUrl: 'https://aistudio.google.com/app/apikey',
|
|
72
|
+
keyPlaceholder: 'AIza...',
|
|
73
|
+
keyField: 'googleApiKey',
|
|
74
|
+
models: [
|
|
75
|
+
{ value: 'gemini-1.5-pro-latest', label: 'Gemini 1.5 Pro', tier: 'pro' },
|
|
76
|
+
{ value: 'gemini-1.5-flash-latest', label: 'Gemini 1.5 Flash', tier: 'free' },
|
|
77
|
+
{ value: 'gemini-2.0-flash-exp', label: 'Gemini 2.0 Flash', tier: 'pro' },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'groq',
|
|
82
|
+
name: 'Groq',
|
|
83
|
+
color: 'text-pink-400',
|
|
84
|
+
description: 'Ultra-fast inference with LPU hardware — 800+ tokens/sec',
|
|
85
|
+
docsUrl: 'https://console.groq.com/keys',
|
|
86
|
+
keyPlaceholder: 'gsk_...',
|
|
87
|
+
keyField: 'groqApiKey',
|
|
88
|
+
badge: 'Fastest',
|
|
89
|
+
models: [
|
|
90
|
+
{ value: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70B', tier: 'free' },
|
|
91
|
+
{ value: 'llama-3.1-8b-instant', label: 'Llama 3.1 8B Instant', tier: 'free' },
|
|
92
|
+
{ value: 'mixtral-8x7b-32768', label: 'Mixtral 8x7B', tier: 'free' },
|
|
93
|
+
{ value: 'gemma2-9b-it', label: 'Gemma 2 9B', tier: 'free' },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'nvidia',
|
|
98
|
+
name: 'NVIDIA NIM',
|
|
99
|
+
color: 'text-green-400',
|
|
100
|
+
description: 'NVIDIA-hosted models with GPU-accelerated inference',
|
|
101
|
+
docsUrl: 'https://build.nvidia.com/explore/discover',
|
|
102
|
+
keyPlaceholder: 'nvapi-...',
|
|
103
|
+
keyField: 'nvidiaApiKey',
|
|
104
|
+
models: [
|
|
105
|
+
{ value: 'meta/llama-3.1-405b-instruct', label: 'Llama 3.1 405B', tier: 'ultra' },
|
|
106
|
+
{ value: 'meta/llama-3.1-70b-instruct', label: 'Llama 3.1 70B', tier: 'pro' },
|
|
107
|
+
{ value: 'meta/llama-3.1-8b-instruct', label: 'Llama 3.1 8B', tier: 'free' },
|
|
108
|
+
{ value: 'mistralai/mistral-large-2-instruct', label: 'Mistral Large 2', tier: 'pro' },
|
|
109
|
+
{ value: 'nvidia/llama-3.1-nemotron-70b-instruct', label: 'Nemotron 70B', tier: 'pro' },
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'together',
|
|
114
|
+
name: 'Together AI',
|
|
115
|
+
color: 'text-yellow-400',
|
|
116
|
+
description: 'Open-source models on fast distributed inference infrastructure',
|
|
117
|
+
docsUrl: 'https://api.together.xyz/settings/api-keys',
|
|
118
|
+
keyPlaceholder: 'together-...',
|
|
119
|
+
keyField: 'togetherApiKey',
|
|
120
|
+
models: [
|
|
121
|
+
{ value: 'meta-llama/Llama-3.3-70B-Instruct-Turbo', label: 'Llama 3.3 70B Turbo', tier: 'pro' },
|
|
122
|
+
{ value: 'mistralai/Mixtral-8x7B-Instruct-v0.1', label: 'Mixtral 8x7B', tier: 'free' },
|
|
123
|
+
{ value: 'deepseek-ai/DeepSeek-R1', label: 'DeepSeek R1', tier: 'pro' },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'mistral',
|
|
128
|
+
name: 'Mistral AI',
|
|
129
|
+
color: 'text-amber-400',
|
|
130
|
+
description: 'Direct access to Mistral, Codestral, and Pixtral models',
|
|
131
|
+
docsUrl: 'https://console.mistral.ai/api-keys',
|
|
132
|
+
keyPlaceholder: 'mistral-...',
|
|
133
|
+
keyField: 'mistralApiKey',
|
|
134
|
+
models: [
|
|
135
|
+
{ value: 'mistral-large-latest', label: 'Mistral Large', tier: 'pro' },
|
|
136
|
+
{ value: 'mistral-small-latest', label: 'Mistral Small', tier: 'free' },
|
|
137
|
+
{ value: 'codestral-latest', label: 'Codestral', tier: 'pro' },
|
|
138
|
+
{ value: 'pixtral-large-latest', label: 'Pixtral Large', tier: 'ultra' },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'cohere',
|
|
143
|
+
name: 'Cohere',
|
|
144
|
+
color: 'text-teal-400',
|
|
145
|
+
description: 'Enterprise-grade models optimized for search and RAG workflows',
|
|
146
|
+
docsUrl: 'https://dashboard.cohere.com/api-keys',
|
|
147
|
+
keyPlaceholder: 'co_...',
|
|
148
|
+
keyField: 'cohereApiKey',
|
|
149
|
+
models: [
|
|
150
|
+
{ value: 'command-r-plus', label: 'Command R+', tier: 'ultra' },
|
|
151
|
+
{ value: 'command-r', label: 'Command R', tier: 'pro' },
|
|
152
|
+
{ value: 'command-light', label: 'Command Light', tier: 'free' },
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'xai',
|
|
157
|
+
name: 'xAI / Grok',
|
|
158
|
+
color: 'text-gray-300',
|
|
159
|
+
description: "Elon Musk's xAI Grok model with real-time X/Twitter integration",
|
|
160
|
+
docsUrl: 'https://console.x.ai/',
|
|
161
|
+
keyPlaceholder: 'xai-...',
|
|
162
|
+
keyField: 'xaiApiKey',
|
|
163
|
+
models: [
|
|
164
|
+
{ value: 'grok-beta', label: 'Grok Beta', tier: 'pro' },
|
|
165
|
+
{ value: 'grok-vision-beta', label: 'Grok Vision Beta', tier: 'pro' },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
const TIER_BADGE = {
|
|
170
|
+
free: 'bg-z-active-bg text-z-active-text border-z-accent/20',
|
|
171
|
+
pro: 'bg-z-accent/10 text-z-active-text border-z-accent/20',
|
|
172
|
+
ultra: 'bg-purple-500/10 text-purple-400 border-purple-500/20',
|
|
173
|
+
};
|
|
174
|
+
const SettingsAi = ({ settings, setSettings, theme }) => {
|
|
175
|
+
const dark = theme === 'dark';
|
|
176
|
+
const [validating, setValidating] = useState(false);
|
|
177
|
+
const [testResult, setTestResult] = useState(null);
|
|
178
|
+
const [showKeys, setShowKeys] = useState({});
|
|
179
|
+
const [expandedProvider, setExpandedProvider] = useState('openrouter');
|
|
180
|
+
const [dynamicModels, setDynamicModels] = useState({});
|
|
181
|
+
const [fetchingModels, setFetchingModels] = useState(null);
|
|
182
|
+
const activeProvider = PROVIDERS.find(p => {
|
|
183
|
+
const key = settings[p.keyField]?.trim();
|
|
184
|
+
return key && key !== '[MASKED_CREDENTIAL]';
|
|
185
|
+
}) || PROVIDERS.find(p => p.id === 'openrouter');
|
|
186
|
+
const handleValidate = async () => {
|
|
187
|
+
setValidating(true);
|
|
188
|
+
setTestResult(null);
|
|
189
|
+
try {
|
|
190
|
+
const providerId = settings.aiProvider || 'openrouter';
|
|
191
|
+
const providerConfig = PROVIDERS.find(p => p.id === providerId);
|
|
192
|
+
const apiKeyField = providerConfig ? providerConfig.keyField : 'openRouterApiKey';
|
|
193
|
+
const apiKey = settings[apiKeyField];
|
|
194
|
+
const res = await api.post('/system/settings/ai/validate', {
|
|
195
|
+
provider: providerId,
|
|
196
|
+
model: settings.aiModel,
|
|
197
|
+
apiKey: apiKey
|
|
198
|
+
});
|
|
199
|
+
setTestResult({ ok: true, msg: res.data.message || 'API Key is valid' });
|
|
200
|
+
toast.success('AI connection verified');
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || 'Connection failed';
|
|
204
|
+
setTestResult({ ok: false, msg });
|
|
205
|
+
toast.error('AI connection failed');
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
setValidating(false);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const handleFetchModels = async (providerId, apiKeyField) => {
|
|
212
|
+
setFetchingModels(providerId);
|
|
213
|
+
try {
|
|
214
|
+
const apiKey = settings[apiKeyField];
|
|
215
|
+
const res = await api.post('/system/settings/ai/models', {
|
|
216
|
+
provider: providerId,
|
|
217
|
+
apiKey: apiKey
|
|
218
|
+
});
|
|
219
|
+
const models = res.data?.data || [];
|
|
220
|
+
setDynamicModels(prev => ({ ...prev, [providerId]: models }));
|
|
221
|
+
toast.success(`Fetched ${models.length} models for ${providerId}`);
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || 'Failed to fetch models';
|
|
225
|
+
toast.error(msg);
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
setFetchingModels(null);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const toggleKey = (id) => setShowKeys(prev => ({ ...prev, [id]: !prev[id] }));
|
|
232
|
+
const inp = (dark) => cn('w-full border px-3 py-2.5 text-sm font-semibold outline-none transition-colors rounded-none focus-visible:ring-2 focus-visible:ring-z-active-border focus-visible:ring-offset-1 focus-visible:ring-offset-black', dark
|
|
233
|
+
? 'bg-black border-z-border text-white placeholder:text-gray-700 focus:border-z-accent'
|
|
234
|
+
: 'bg-z-panel border-z-border text-z-primary placeholder:text-z-muted focus:border-z-accent');
|
|
235
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: cn('p-5 border space-y-4 shadow-sm', dark ? 'bg-z-panel backdrop-blur-md border-z-border' : 'bg-z-input border-z-border'), children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx(Cpu, { size: 14, className: "text-z-active-text" }), _jsx("span", { className: cn('text-sm font-semibold ', dark ? 'text-white' : 'text-z-primary'), children: "Active Model" }), _jsx("span", { className: "ml-auto text-sm text-z-secondary", children: "Used by all AI features" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-semibold text-z-secondary", children: "AI Provider" }), _jsx("select", { value: settings.aiProvider || 'openrouter', onChange: e => setSettings({ ...settings, aiProvider: e.target.value, aiModel: PROVIDERS.find(p => p.id === e.target.value)?.models[0]?.value || '' }), className: inp(dark), children: PROVIDERS.map(p => _jsx("option", { value: p.id, children: p.name }, p.id)) })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-semibold text-z-secondary", children: "Model" }), _jsx("select", { value: settings.aiModel || '', onChange: e => setSettings({ ...settings, aiModel: e.target.value }), className: inp(dark), children: (dynamicModels[settings.aiProvider || 'openrouter'] || PROVIDERS.find(p => p.id === (settings.aiProvider || 'openrouter'))?.models || []).map(m => (_jsxs("option", { value: m.value, children: [m.label, " ", m.tier ? `(${m.tier})` : ''] }, m.value))) })] })] }), _jsxs("div", { className: "flex items-center gap-3 pt-1", children: [_jsxs("button", { onClick: handleValidate, disabled: validating, className: "px-4 py-2 bg-z-accent hover:opacity-90 shadow-sm text-white text-sm font-semibold flex items-center gap-2 disabled:opacity-50 transition-all", children: [validating ? _jsx(Loader2, { size: 11, className: "animate-spin" }) : _jsx(TestTube2, { size: 11 }), "Test Connection"] }), testResult && (_jsxs("div", { className: cn('flex items-center gap-2 text-sm font-semibold ', testResult.ok ? 'text-z-active-text' : 'text-red-400'), children: [testResult.ok ? _jsx(CheckCircle2, { size: 11 }) : _jsx(AlertCircle, { size: 11 }), testResult.msg] }))] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(Lock, { size: 12, className: "text-z-secondary" }), _jsx("span", { className: "text-sm font-semibold text-z-secondary", children: "Provider API Keys" }), _jsx("span", { className: "ml-auto text-sm text-gray-600", children: "All keys encrypted at rest" })] }), PROVIDERS.map(provider => {
|
|
236
|
+
const isExpanded = expandedProvider === provider.id;
|
|
237
|
+
const keyValue = settings[provider.keyField] || '';
|
|
238
|
+
const hasKey = keyValue.trim() && keyValue !== '[MASKED_CREDENTIAL]';
|
|
239
|
+
const isMasked = keyValue === '[MASKED_CREDENTIAL]';
|
|
240
|
+
return (_jsxs("div", { className: cn('border transition-all shadow-sm', isExpanded
|
|
241
|
+
? (dark ? 'border-white/15 bg-black/80 backdrop-blur-md shadow-sm' : 'border-z-border-strong bg-white')
|
|
242
|
+
: ('z-card-interactive')), children: [_jsxs("button", { onClick: () => setExpandedProvider(isExpanded ? '' : provider.id), className: "w-full flex items-center gap-3 px-4 py-3 text-left", children: [_jsxs("div", { className: "flex items-center gap-3 flex-1 min-w-0", children: [_jsx("div", { className: cn('w-2 h-2 rounded-full flex-shrink-0', hasKey || isMasked ? 'bg-z-accent shadow-sm' : 'bg-gray-700') }), _jsx("div", { className: cn('text-sm font-semibold', provider.color), children: provider.name }), provider.badge && (_jsx("span", { className: "text-sm font-semibold px-1.5 py-0.5 bg-z-active-bg border border-z-active-border text-z-active-text", children: provider.badge })), _jsx("span", { className: "text-sm text-gray-600 truncate hidden sm:block", children: provider.description })] }), _jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [isMasked && _jsx("span", { className: "text-sm text-z-active-text font-semibold", children: "Configured" }), hasKey && !isMasked && _jsx("span", { className: "text-sm text-z-active-text font-semibold", children: "Active" }), _jsx(ChevronRight, { size: 12, className: cn('text-z-secondary transition-transform', isExpanded && 'rotate-90') })] })] }), isExpanded && (_jsxs("div", { className: "px-4 pb-4 space-y-3 border-t", style: { borderColor: dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)' }, children: [_jsx("p", { className: "text-sm text-z-secondary pt-3", children: provider.description }), _jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("label", { className: "text-sm font-semibold text-z-secondary", children: "API Key" }), _jsxs("a", { href: provider.docsUrl, target: "_blank", rel: "noopener noreferrer", className: "text-sm text-z-active-text hover:text-z-active-text flex items-center gap-1 transition-colors", children: ["Get Key ", _jsx(ExternalLink, { size: 9 })] })] }), _jsxs("div", { className: "relative", children: [_jsx("input", { type: showKeys[provider.id] ? 'text' : 'password', value: keyValue, onChange: e => setSettings({ ...settings, [provider.keyField]: e.target.value }), placeholder: isMasked ? '••••••••••••••••' : provider.keyPlaceholder, className: cn(inp(dark), 'pr-10 font-mono') }), _jsx("button", { type: "button", onClick: () => toggleKey(provider.id), className: "absolute right-3 top-1/2 -translate-y-1/2 text-z-secondary hover:text-white transition-colors", children: showKeys[provider.id] ? _jsx(EyeOff, { size: 13 }) : _jsx(Eye, { size: 13 }) })] }), isMasked && (_jsxs("p", { className: "text-sm text-amber-500/70 flex items-center gap-1", children: [_jsx(Lock, { size: 9 }), " Key is stored \u2014 enter a new value to replace it"] }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("label", { className: "text-sm font-semibold text-z-secondary", children: ["Available Models ", dynamicModels[provider.id] ? `(${dynamicModels[provider.id].length})` : ''] }), _jsxs("button", { type: "button", onClick: () => handleFetchModels(provider.id, provider.keyField), disabled: fetchingModels === provider.id || (!hasKey && !isMasked), className: "text-sm text-z-active-text hover:text-z-active-text flex items-center gap-1 disabled:opacity-50 transition-colors", children: [fetchingModels === provider.id ? _jsx(Loader2, { size: 9, className: "animate-spin" }) : _jsx(Zap, { size: 9 }), "Fetch Models"] })] }), _jsx("div", { className: "flex flex-wrap gap-1.5 max-h-[120px] overflow-y-auto pr-2 custom-scrollbar", children: (dynamicModels[provider.id] || provider.models).map(m => (_jsx("span", { title: m.value, className: cn('text-sm font-semibold px-2 py-1 border', m.tier ? TIER_BADGE[m.tier] : 'bg-z-hover border-white/10 text-z-muted'), children: m.label }, m.value))) })] })] }))] }, provider.id));
|
|
243
|
+
})] }), _jsxs("div", { className: cn('flex gap-3 p-4 border', dark ? 'bg-z-accent/5 border-z-accent/15' : 'bg-z-active-bg border-z-active-border'), children: [_jsx(Info, { size: 12, className: "text-z-active-text flex-shrink-0 mt-0.5" }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-semibold text-z-active-text", children: "Provider Priority" }), _jsx("p", { className: "text-sm text-z-secondary leading-relaxed", children: "The AI engine auto-selects providers in this order: OpenRouter \u2192 xAI \u2192 NVIDIA NIM \u2192 Groq \u2192 Together AI \u2192 Mistral \u2192 Cohere \u2192 OpenAI \u2192 Anthropic \u2192 Google Gemini. Set the \"Active Model\" above to override. Keys are never sent to the client." })] })] })] }));
|
|
244
|
+
};
|
|
245
|
+
export default SettingsAi;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './plugin';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './plugin';
|
|
@@ -1,55 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const lucide_react_1 = require("lucide-react");
|
|
9
|
+
const api_1 = __importDefault(require("../../admin/src/lib/api"));
|
|
10
|
+
const framer_motion_1 = require("framer-motion");
|
|
11
|
+
const utils_1 = require("../../admin/src/lib/utils");
|
|
12
|
+
const react_hot_toast_1 = __importDefault(require("react-hot-toast"));
|
|
13
|
+
const ThemeContext_1 = require("../../admin/src/context/ThemeContext");
|
|
14
|
+
const PageHeader_1 = require("../../admin/src/components/ui/PageHeader");
|
|
10
15
|
// ── Tool Definitions ───────────────────────────────────────────────────────────
|
|
11
16
|
const TOOLS = [
|
|
12
|
-
{ id: 'seo', name: 'SEO Analysis', icon: Search, endpoint: '/content-tools/seo-analysis', desc: 'Score title, meta, content', color: 'text-amber-400' },
|
|
13
|
-
{ id: 'quality', name: 'Quality Audit', icon: ShieldCheck, endpoint: '/content-tools/quality', desc: 'Readability + word structure', color: 'text-z-active-text' },
|
|
14
|
-
{ id: 'improve', name: 'Refine Text', icon: Wand2, endpoint: '/content-tools/ai/improve', desc: 'AI-powered rewrite', color: 'text-purple-400' },
|
|
15
|
-
{ id: 'meta', name: 'Meta Generator', icon: Hash, endpoint: '/content-tools/ai/meta-description', desc: 'Auto SEO meta description', color: 'text-z-active-text' },
|
|
16
|
-
{ id: 'alt', name: 'Alt Text', icon:
|
|
17
|
+
{ id: 'seo', name: 'SEO Analysis', icon: lucide_react_1.Search, endpoint: '/content-tools/seo-analysis', desc: 'Score title, meta, content', color: 'text-amber-400' },
|
|
18
|
+
{ id: 'quality', name: 'Quality Audit', icon: lucide_react_1.ShieldCheck, endpoint: '/content-tools/quality', desc: 'Readability + word structure', color: 'text-z-active-text' },
|
|
19
|
+
{ id: 'improve', name: 'Refine Text', icon: lucide_react_1.Wand2, endpoint: '/content-tools/ai/improve', desc: 'AI-powered rewrite', color: 'text-purple-400' },
|
|
20
|
+
{ id: 'meta', name: 'Meta Generator', icon: lucide_react_1.Hash, endpoint: '/content-tools/ai/meta-description', desc: 'Auto SEO meta description', color: 'text-z-active-text' },
|
|
21
|
+
{ id: 'alt', name: 'Alt Text', icon: lucide_react_1.Image, endpoint: '/content-tools/ai/alt-text', desc: 'Generate image alt text', color: 'text-pink-400' },
|
|
17
22
|
];
|
|
18
23
|
const MODES = [
|
|
19
|
-
{ id: 'writer', label: 'Writer', icon: PenTool, desc: 'Generate content from a prompt' },
|
|
20
|
-
{ id: 'architect', label: 'Architect', icon: Cpu, desc: 'Design a collection schema with AI' },
|
|
21
|
-
{ id: 'tools', label: 'Tools', icon: Zap, desc: 'SEO, quality, meta & more' },
|
|
24
|
+
{ id: 'writer', label: 'Writer', icon: lucide_react_1.PenTool, desc: 'Generate content from a prompt' },
|
|
25
|
+
{ id: 'architect', label: 'Architect', icon: lucide_react_1.Cpu, desc: 'Design a collection schema with AI' },
|
|
26
|
+
{ id: 'tools', label: 'Tools', icon: lucide_react_1.Zap, desc: 'SEO, quality, meta & more' },
|
|
22
27
|
];
|
|
23
28
|
// ── Result Renderers ──────────────────────────────────────────────────────────
|
|
24
29
|
function SeoResultView({ data }) {
|
|
25
|
-
return (
|
|
30
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-end gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary mb-1", children: "SEO Score" }), (0, jsx_runtime_1.jsx)("span", { className: (0, utils_1.cn)('text-5xl font-black tabular-nums', data.score >= 70 ? 'text-z-active-text' : data.score >= 45 ? 'text-amber-400' : 'text-red-400'), children: data.score }), (0, jsx_runtime_1.jsx)("span", { className: "text-lg text-z-secondary font-black", children: "/100" })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 h-2 bg-z-hover rounded-full overflow-hidden mb-3", children: (0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)('h-full transition-all duration-700', data.score >= 70 ? 'bg-z-accent' : data.score >= 45 ? 'bg-amber-500' : 'bg-red-500'), style: { width: `${data.score}%` } }) })] }), data.passed?.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-1.5", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-active-text/70", children: "Passing" }), data.passed.map((p, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 text-[10px] text-z-muted", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CheckCircle2, { size: 11, className: "text-z-active-text flex-shrink-0" }), p] }, i)))] })), data.issues?.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-1.5", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-red-500/70", children: "Issues" }), data.issues.map((p, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 text-[10px] text-z-muted", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.AlertCircle, { size: 11, className: "text-red-500 flex-shrink-0" }), p] }, i)))] })), data.suggestions?.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-1.5", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-amber-500/70", children: "Suggestions" }), data.suggestions.map((p, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 text-[10px] text-z-muted", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { size: 11, className: "text-amber-500 flex-shrink-0" }), p] }, i)))] }))] }));
|
|
26
31
|
}
|
|
27
32
|
function QualityResultView({ data }) {
|
|
28
33
|
const gradeColor = { A: 'text-z-active-text', B: 'text-z-active-text', C: 'text-amber-400', D: 'text-orange-400', F: 'text-red-400' };
|
|
29
|
-
return (
|
|
34
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start gap-6", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary mb-1", children: "Grade" }), (0, jsx_runtime_1.jsx)("span", { className: (0, utils_1.cn)('text-6xl font-black', gradeColor[data.grade] || 'text-white'), children: data.grade })] }), (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-3 gap-3 flex-1 pt-1", children: [
|
|
30
35
|
{ label: 'Words', val: data.wordCount },
|
|
31
36
|
{ label: 'Sentences', val: data.sentenceCount },
|
|
32
37
|
{ label: 'Avg Words/Sent', val: data.avgWordsPerSentence },
|
|
33
|
-
].map(m => (
|
|
38
|
+
].map(m => ((0, jsx_runtime_1.jsxs)("div", { className: "bg-z-hover border border-z-border p-3", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[8px] font-black uppercase tracking-widest text-z-secondary mb-1", children: m.label }), (0, jsx_runtime_1.jsx)("p", { className: "text-lg font-black text-white tabular-nums", children: m.val })] }, m.label))) })] }), data.issues?.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-1.5", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-red-500/70", children: "Issues" }), data.issues.map((p, i) => (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 text-[10px] text-z-muted", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.AlertCircle, { size: 11, className: "text-red-500 flex-shrink-0" }), p] }, i))] })), data.suggestions?.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-1.5", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-active-text/70", children: "Suggestions" }), data.suggestions.map((p, i) => (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 text-[10px] text-z-muted", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CheckCircle2, { size: 11, className: "text-z-active-text flex-shrink-0" }), p] }, i))] }))] }));
|
|
34
39
|
}
|
|
35
40
|
function SchemaResultView({ data }) {
|
|
36
|
-
return (
|
|
41
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start justify-between gap-3 flex-wrap", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-black text-white tracking-tight", children: data.name }), (0, jsx_runtime_1.jsxs)("p", { className: "text-[9px] text-z-secondary font-mono mt-0.5", children: ["/", data.slug] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [data.drafts && (0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 text-[7px] font-black uppercase tracking-widest bg-z-accent/10 border border-z-accent/20 text-z-active-text", children: "Drafts" }), data.timestamps && (0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 text-[7px] font-black uppercase tracking-widest bg-gray-500/10 border border-gray-500/20 text-z-muted", children: "Timestamps" })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-1.5", children: [(0, jsx_runtime_1.jsxs)("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary", children: [data.fields?.length || 0, " Fields"] }), data.fields?.map((f, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between px-3 py-2 bg-z-hover border border-z-border hover:border-z-border-strong transition-colors group", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-[9px] font-mono text-z-active-text", children: f.name }), f.required && (0, jsx_runtime_1.jsx)("span", { className: "text-[6px] font-black text-red-500 uppercase tracking-wider", children: "required" })] }), (0, jsx_runtime_1.jsx)("span", { className: "text-[8px] text-gray-600 uppercase tracking-widest font-black", children: f.type })] }, i)))] })] }));
|
|
37
42
|
}
|
|
38
43
|
// ── Main Component ─────────────────────────────────────────────────────────────
|
|
39
44
|
const AIWriterPage = () => {
|
|
40
|
-
const { theme } = useTheme();
|
|
45
|
+
const { theme } = (0, ThemeContext_1.useTheme)();
|
|
41
46
|
const dark = theme === 'dark';
|
|
42
|
-
const [mode, setMode] = useState('writer');
|
|
43
|
-
const [prompt, setPrompt] = useState('');
|
|
44
|
-
const [loading, setLoading] = useState(false);
|
|
45
|
-
const [result, setResult] = useState(null);
|
|
46
|
-
const [activeTool, setActiveTool] = useState('seo');
|
|
47
|
-
const [history, setHistory] = useState([]);
|
|
48
|
-
const [schemaForSave, setSchemaForSave] = useState(null);
|
|
49
|
-
const [savingSchema, setSavingSchema] = useState(false);
|
|
50
|
-
const textareaRef = useRef(null);
|
|
47
|
+
const [mode, setMode] = (0, react_1.useState)('writer');
|
|
48
|
+
const [prompt, setPrompt] = (0, react_1.useState)('');
|
|
49
|
+
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
50
|
+
const [result, setResult] = (0, react_1.useState)(null);
|
|
51
|
+
const [activeTool, setActiveTool] = (0, react_1.useState)('seo');
|
|
52
|
+
const [history, setHistory] = (0, react_1.useState)([]);
|
|
53
|
+
const [schemaForSave, setSchemaForSave] = (0, react_1.useState)(null);
|
|
54
|
+
const [savingSchema, setSavingSchema] = (0, react_1.useState)(false);
|
|
55
|
+
const textareaRef = (0, react_1.useRef)(null);
|
|
51
56
|
// Auto-resize textarea
|
|
52
|
-
useEffect(() => {
|
|
57
|
+
(0, react_1.useEffect)(() => {
|
|
53
58
|
if (textareaRef.current) {
|
|
54
59
|
textareaRef.current.style.height = 'auto';
|
|
55
60
|
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 300)}px`;
|
|
@@ -65,12 +70,12 @@ const AIWriterPage = () => {
|
|
|
65
70
|
let res;
|
|
66
71
|
let resultData;
|
|
67
72
|
if (mode === 'architect') {
|
|
68
|
-
res = await
|
|
73
|
+
res = await api_1.default.post('/system/ai-architect', { prompt });
|
|
69
74
|
resultData = res.data.data.schema;
|
|
70
75
|
setSchemaForSave(resultData);
|
|
71
76
|
}
|
|
72
77
|
else if (mode === 'writer') {
|
|
73
|
-
res = await
|
|
78
|
+
res = await api_1.default.post('/content-tools/ai/generate', { prompt });
|
|
74
79
|
resultData = res.data.data.text;
|
|
75
80
|
}
|
|
76
81
|
else {
|
|
@@ -88,7 +93,7 @@ const AIWriterPage = () => {
|
|
|
88
93
|
else if (activeTool === 'alt') {
|
|
89
94
|
payload = { imageUrl: prompt };
|
|
90
95
|
}
|
|
91
|
-
res = await
|
|
96
|
+
res = await api_1.default.post(tool.endpoint, payload);
|
|
92
97
|
const d = res.data.data;
|
|
93
98
|
if (activeTool === 'seo')
|
|
94
99
|
resultData = d;
|
|
@@ -109,11 +114,11 @@ const AIWriterPage = () => {
|
|
|
109
114
|
result: resultData,
|
|
110
115
|
timestamp: new Date()
|
|
111
116
|
}, ...prev.slice(0, 19)]);
|
|
112
|
-
|
|
117
|
+
react_hot_toast_1.default.success('Generated');
|
|
113
118
|
}
|
|
114
119
|
catch (err) {
|
|
115
120
|
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || 'AI request failed';
|
|
116
|
-
|
|
121
|
+
react_hot_toast_1.default.error(msg);
|
|
117
122
|
}
|
|
118
123
|
finally {
|
|
119
124
|
setLoading(false);
|
|
@@ -128,12 +133,12 @@ const AIWriterPage = () => {
|
|
|
128
133
|
return;
|
|
129
134
|
setSavingSchema(true);
|
|
130
135
|
try {
|
|
131
|
-
await
|
|
132
|
-
|
|
136
|
+
await api_1.default.post('/schemas', schemaForSave);
|
|
137
|
+
react_hot_toast_1.default.success(`Collection "${schemaForSave.name}" created!`);
|
|
133
138
|
setSchemaForSave(null);
|
|
134
139
|
}
|
|
135
140
|
catch (err) {
|
|
136
|
-
|
|
141
|
+
react_hot_toast_1.default.error(err?.response?.data?.error?.message || 'Failed to save schema');
|
|
137
142
|
}
|
|
138
143
|
finally {
|
|
139
144
|
setSavingSchema(false);
|
|
@@ -142,7 +147,7 @@ const AIWriterPage = () => {
|
|
|
142
147
|
const copyResult = () => {
|
|
143
148
|
const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
144
149
|
navigator.clipboard.writeText(text);
|
|
145
|
-
|
|
150
|
+
react_hot_toast_1.default.success('Copied to clipboard');
|
|
146
151
|
};
|
|
147
152
|
const downloadResult = () => {
|
|
148
153
|
const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
@@ -159,12 +164,12 @@ const AIWriterPage = () => {
|
|
|
159
164
|
if (!result)
|
|
160
165
|
return null;
|
|
161
166
|
if (mode === 'architect')
|
|
162
|
-
return
|
|
167
|
+
return (0, jsx_runtime_1.jsx)(SchemaResultView, { data: result });
|
|
163
168
|
if (mode === 'tools' && activeTool === 'seo')
|
|
164
|
-
return
|
|
169
|
+
return (0, jsx_runtime_1.jsx)(SeoResultView, { data: result });
|
|
165
170
|
if (mode === 'tools' && activeTool === 'quality')
|
|
166
|
-
return
|
|
167
|
-
return (
|
|
171
|
+
return (0, jsx_runtime_1.jsx)(QualityResultView, { data: result });
|
|
172
|
+
return ((0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { initial: { opacity: 0, y: 4 }, animate: { opacity: 1, y: 0 }, className: "whitespace-pre-wrap text-[12px] leading-relaxed text-gray-300 font-sans", children: typeof result === 'string' ? result : JSON.stringify(result, null, 2) }));
|
|
168
173
|
};
|
|
169
174
|
const activePlaceholder = mode === 'architect'
|
|
170
175
|
? 'Describe a collection schema... e.g., "An e-commerce product with variants, pricing, and inventory tracking"'
|
|
@@ -175,18 +180,18 @@ const AIWriterPage = () => {
|
|
|
175
180
|
: activeTool === 'meta'
|
|
176
181
|
? 'Paste your content to generate a meta description...'
|
|
177
182
|
: 'Paste text to analyze...';
|
|
178
|
-
return (
|
|
183
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col h-[calc(100vh-64px)] overflow-hidden", children: [(0, jsx_runtime_1.jsx)(PageHeader_1.PageHeader, { title: "AI Architect", description: "Generate content, design schemas, and analyze text quality", actions: (0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)('flex p-0.5 border', dark ? 'bg-black border-z-border' : 'bg-z-panel border-z-border'), children: MODES.map(m => ((0, jsx_runtime_1.jsxs)("button", { onClick: () => { setMode(m.id); setResult(null); setSchemaForSave(null); }, title: m.desc, className: (0, utils_1.cn)('flex items-center gap-2 px-4 py-2 text-[9px] font-black uppercase tracking-widest transition-all', mode === m.id
|
|
179
184
|
? (dark ? 'bg-white text-black' : 'bg-gray-900 text-white')
|
|
180
|
-
: (dark ? 'text-z-secondary hover:text-white' : 'text-z-secondary hover:text-z-primary')), children: [
|
|
185
|
+
: (dark ? 'text-z-secondary hover:text-white' : 'text-z-secondary hover:text-z-primary')), children: [(0, jsx_runtime_1.jsx)(m.icon, { size: 11 }), (0, jsx_runtime_1.jsx)("span", { className: "hidden sm:inline", children: m.label })] }, m.id))) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-1 overflow-hidden", children: [(0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)('w-56 flex-shrink-0 border-r flex flex-col hidden lg:flex', dark ? 'border-z-border bg-black' : 'border-z-border bg-gray-50'), children: [(0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)('px-4 py-3 border-b flex-shrink-0', dark ? 'border-z-border' : 'border-z-border'), children: (0, jsx_runtime_1.jsx)("p", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary", children: "History" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 overflow-y-auto p-2 space-y-1", children: [history.length === 0 && ((0, jsx_runtime_1.jsx)("p", { className: "text-[8px] text-gray-600 uppercase tracking-widest p-2 text-center mt-4", children: "No history yet" })), history.map(h => ((0, jsx_runtime_1.jsxs)("button", { onClick: () => { setResult(h.result); setSchemaForSave(h.mode === 'architect' ? h.result : null); }, className: (0, utils_1.cn)('w-full text-left p-2.5 border border-transparent transition-all', dark ? 'hover:bg-z-hover hover:border-z-border' : 'hover:bg-white hover:border-z-border'), children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-1.5 mb-1", children: (0, jsx_runtime_1.jsx)("span", { className: (0, utils_1.cn)('text-[7px] font-black uppercase tracking-widest', h.mode === 'architect' ? 'text-purple-400' : h.mode === 'tools' ? 'text-amber-400' : 'text-z-active-text'), children: h.mode }) }), (0, jsx_runtime_1.jsx)("p", { className: "text-[9px] text-z-secondary truncate", children: h.prompt })] }, h.id)))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 flex flex-col min-w-0 border-r", style: { borderColor: dark ? 'rgba(255,255,255,0.08)' : '#e5e7eb' }, children: [(0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: mode === 'tools' && ((0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { initial: { height: 0, opacity: 0 }, animate: { height: 'auto', opacity: 1 }, exit: { height: 0, opacity: 0 }, className: (0, utils_1.cn)('flex-shrink-0 border-b overflow-hidden', dark ? 'border-z-border' : 'border-z-border'), children: (0, jsx_runtime_1.jsx)("div", { className: "p-3 flex gap-2 flex-wrap", children: TOOLS.map(tool => ((0, jsx_runtime_1.jsxs)("button", { onClick: () => { setActiveTool(tool.id); setResult(null); }, title: tool.desc, className: (0, utils_1.cn)('flex items-center gap-2 px-3 py-2 text-[9px] font-black uppercase tracking-widest border transition-all', activeTool === tool.id
|
|
181
186
|
? (dark ? 'bg-white text-black border-white' : 'bg-gray-900 text-white border-gray-900')
|
|
182
|
-
: (dark ? 'border-z-border text-z-secondary hover:text-white hover:border-white/20' : 'border-z-border text-z-secondary hover:border-gray-400 hover:text-z-primary')), children: [
|
|
187
|
+
: (dark ? 'border-z-border text-z-secondary hover:text-white hover:border-white/20' : 'border-z-border text-z-secondary hover:border-gray-400 hover:text-z-primary')), children: [(0, jsx_runtime_1.jsx)(tool.icon, { size: 11, className: activeTool === tool.id ? '' : tool.color }), tool.name] }, tool.id))) }) })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 flex flex-col overflow-hidden", children: [(0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)('flex-shrink-0 flex items-center gap-3 px-5 py-3 border-b', dark ? 'border-z-border' : 'border-z-border'), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Terminal, { size: 12, className: "text-z-secondary" }), (0, jsx_runtime_1.jsx)("span", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary", children: mode === 'architect' ? 'Schema Prompt' : mode === 'writer' ? 'Content Prompt' : TOOLS.find(t => t.id === activeTool)?.name }), (0, jsx_runtime_1.jsx)("span", { className: "ml-auto text-[8px] text-gray-600 uppercase tracking-widest hidden sm:block", children: "\u2318 + Enter to run" })] }), (0, jsx_runtime_1.jsx)("textarea", { ref: textareaRef, value: prompt, onChange: e => setPrompt(e.target.value), onKeyDown: handleKeyDown, placeholder: activePlaceholder, className: (0, utils_1.cn)('flex-1 w-full p-5 text-sm outline-none resize-none font-sans leading-relaxed', dark
|
|
183
188
|
? 'bg-transparent text-white placeholder:text-gray-700'
|
|
184
|
-
: 'bg-transparent text-z-primary placeholder:text-z-muted') }),
|
|
185
|
-
?
|
|
186
|
-
:
|
|
189
|
+
: 'bg-transparent text-z-primary placeholder:text-z-muted') }), (0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)('flex-shrink-0 p-4 border-t', dark ? 'border-z-border' : 'border-z-border'), children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [prompt.trim() && ((0, jsx_runtime_1.jsx)("button", { onClick: () => { setPrompt(''); setResult(null); }, className: "p-2.5 text-gray-600 hover:text-white transition-colors border border-transparent hover:border-white/10", title: "Clear", children: (0, jsx_runtime_1.jsx)(lucide_react_1.RotateCcw, { size: 14 }) })), (0, jsx_runtime_1.jsx)("button", { onClick: handleExecute, disabled: loading || (!prompt.trim() && activeTool !== 'alt'), className: (0, utils_1.cn)('flex-1 py-3 font-black uppercase tracking-widest text-[10px] transition-all flex items-center justify-center gap-2.5', 'bg-z-accent hover:opacity-90 text-white', 'disabled:opacity-40 disabled:cursor-not-allowed', 'shadow-sm hover:shadow-sm'), children: loading
|
|
190
|
+
? (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { size: 13, className: "animate-spin" }), " Generating\u2026"] })
|
|
191
|
+
: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Send, { size: 13 }), " ", mode === 'architect' ? 'Design Schema' : mode === 'writer' ? 'Generate Content' : 'Analyze'] }) })] }) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 flex flex-col min-w-0", children: [(0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)('flex-shrink-0 flex items-center justify-between px-5 py-3 border-b', dark ? 'border-z-border' : 'border-z-border'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 12, className: result ? 'text-z-active-text' : 'text-z-secondary' }), (0, jsx_runtime_1.jsx)("span", { className: "text-[8px] font-black uppercase tracking-[0.3em] text-z-secondary", children: "Output" }), result && (0, jsx_runtime_1.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-z-accent shadow-sm" })] }), result && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("button", { onClick: copyResult, title: "Copy", className: "p-2 text-z-secondary hover:text-white transition-colors border border-transparent hover:border-white/10", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 13 }) }), (0, jsx_runtime_1.jsx)("button", { onClick: downloadResult, title: "Download", className: "p-2 text-z-secondary hover:text-white transition-colors border border-transparent hover:border-white/10", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Download, { size: 13 }) }), mode === 'architect' && ((0, jsx_runtime_1.jsx)("button", { onClick: () => { setResult(null); setSchemaForSave(null); }, title: "Clear", className: "p-2 text-z-secondary hover:text-white transition-colors border border-transparent hover:border-white/10", children: (0, jsx_runtime_1.jsx)(lucide_react_1.RotateCcw, { size: 13 }) }))] }))] }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 overflow-auto p-6", children: loading ? ((0, jsx_runtime_1.jsxs)("div", { className: "h-full flex flex-col items-center justify-center gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { size: 32, className: "animate-spin text-z-active-text" }), (0, jsx_runtime_1.jsx)("div", { className: "absolute inset-0 blur-xl bg-z-accent/20 animate-pulse" })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-[9px] font-black uppercase tracking-[0.4em] text-z-secondary animate-pulse", children: mode === 'architect' ? 'Designing Schema…' : mode === 'writer' ? 'Writing Content…' : 'Analyzing…' })] })) : !result ? ((0, jsx_runtime_1.jsxs)("div", { className: "h-full flex flex-col items-center justify-center gap-5 opacity-30", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Cpu, { size: 40, className: "text-z-secondary" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center space-y-1", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[9px] font-black uppercase tracking-[0.4em] text-z-secondary", children: "Awaiting Input" }), (0, jsx_runtime_1.jsx)("p", { className: "text-[8px] text-gray-600 uppercase tracking-widest", children: mode === 'architect' ? 'Describe a collection to generate a schema' : mode === 'writer' ? 'Write a prompt to generate content' : 'Paste content to analyze' })] })] })) : ((0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { mode: "wait", children: (0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.2 }, children: renderResult() }, JSON.stringify(result).substring(0, 50)) })) }), (0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: result && mode === 'architect' && schemaForSave && ((0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { initial: { height: 0, opacity: 0 }, animate: { height: 'auto', opacity: 1 }, exit: { height: 0, opacity: 0 }, className: (0, utils_1.cn)('flex-shrink-0 border-t overflow-hidden', dark ? 'border-z-border' : 'border-z-border'), children: (0, jsx_runtime_1.jsxs)("div", { className: "px-5 py-3 flex items-center justify-between gap-3", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[8px] text-z-secondary uppercase tracking-widest", children: "Schema looks good? Save it as a live collection." }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsxs)("button", { onClick: () => {
|
|
187
192
|
const text = JSON.stringify(schemaForSave, null, 2);
|
|
188
193
|
navigator.clipboard.writeText(text);
|
|
189
|
-
|
|
190
|
-
}, className: cn('px-4 py-2 border text-[9px] font-black uppercase tracking-widest flex items-center gap-2 transition-all', dark ? 'border-z-border text-z-muted hover:text-white hover:border-white/20' : 'border-z-border text-z-secondary hover:border-gray-400'), children: [
|
|
194
|
+
react_hot_toast_1.default.success('Schema copied as JSON');
|
|
195
|
+
}, className: (0, utils_1.cn)('px-4 py-2 border text-[9px] font-black uppercase tracking-widest flex items-center gap-2 transition-all', dark ? 'border-z-border text-z-muted hover:text-white hover:border-white/20' : 'border-z-border text-z-secondary hover:border-gray-400'), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Code2, { size: 11 }), " Copy JSON"] }), (0, jsx_runtime_1.jsxs)("button", { onClick: saveSchemaToDb, disabled: savingSchema, className: "px-5 py-2 bg-z-accent hover:opacity-90 text-white text-[9px] font-black uppercase tracking-widest flex items-center gap-2 transition-all disabled:opacity-50 shadow-sm", children: [savingSchema ? (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { size: 11, className: "animate-spin" }) : (0, jsx_runtime_1.jsx)(lucide_react_1.Save, { size: 11 }), "Save Collection"] })] })] }) })) })] })] })] }));
|
|
191
196
|
};
|
|
192
|
-
|
|
197
|
+
exports.default = AIWriterPage;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const lucide_react_1 = require("lucide-react");
|
|
9
|
+
const utils_1 = require("../../admin/src/lib/utils");
|
|
10
|
+
const api_1 = __importDefault(require("../../admin/src/lib/api"));
|
|
11
|
+
const react_hot_toast_1 = __importDefault(require("react-hot-toast"));
|
|
7
12
|
const PROVIDERS = [
|
|
8
13
|
{
|
|
9
14
|
id: 'openrouter',
|
|
@@ -173,12 +178,12 @@ const TIER_BADGE = {
|
|
|
173
178
|
};
|
|
174
179
|
const SettingsAi = ({ settings, setSettings, theme }) => {
|
|
175
180
|
const dark = theme === 'dark';
|
|
176
|
-
const [validating, setValidating] = useState(false);
|
|
177
|
-
const [testResult, setTestResult] = useState(null);
|
|
178
|
-
const [showKeys, setShowKeys] = useState({});
|
|
179
|
-
const [expandedProvider, setExpandedProvider] = useState('openrouter');
|
|
180
|
-
const [dynamicModels, setDynamicModels] = useState({});
|
|
181
|
-
const [fetchingModels, setFetchingModels] = useState(null);
|
|
181
|
+
const [validating, setValidating] = (0, react_1.useState)(false);
|
|
182
|
+
const [testResult, setTestResult] = (0, react_1.useState)(null);
|
|
183
|
+
const [showKeys, setShowKeys] = (0, react_1.useState)({});
|
|
184
|
+
const [expandedProvider, setExpandedProvider] = (0, react_1.useState)('openrouter');
|
|
185
|
+
const [dynamicModels, setDynamicModels] = (0, react_1.useState)({});
|
|
186
|
+
const [fetchingModels, setFetchingModels] = (0, react_1.useState)(null);
|
|
182
187
|
const activeProvider = PROVIDERS.find(p => {
|
|
183
188
|
const key = settings[p.keyField]?.trim();
|
|
184
189
|
return key && key !== '[MASKED_CREDENTIAL]';
|
|
@@ -191,18 +196,18 @@ const SettingsAi = ({ settings, setSettings, theme }) => {
|
|
|
191
196
|
const providerConfig = PROVIDERS.find(p => p.id === providerId);
|
|
192
197
|
const apiKeyField = providerConfig ? providerConfig.keyField : 'openRouterApiKey';
|
|
193
198
|
const apiKey = settings[apiKeyField];
|
|
194
|
-
const res = await
|
|
199
|
+
const res = await api_1.default.post('/system/settings/ai/validate', {
|
|
195
200
|
provider: providerId,
|
|
196
201
|
model: settings.aiModel,
|
|
197
202
|
apiKey: apiKey
|
|
198
203
|
});
|
|
199
204
|
setTestResult({ ok: true, msg: res.data.message || 'API Key is valid' });
|
|
200
|
-
|
|
205
|
+
react_hot_toast_1.default.success('AI connection verified');
|
|
201
206
|
}
|
|
202
207
|
catch (err) {
|
|
203
208
|
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || 'Connection failed';
|
|
204
209
|
setTestResult({ ok: false, msg });
|
|
205
|
-
|
|
210
|
+
react_hot_toast_1.default.error('AI connection failed');
|
|
206
211
|
}
|
|
207
212
|
finally {
|
|
208
213
|
setValidating(false);
|
|
@@ -212,34 +217,34 @@ const SettingsAi = ({ settings, setSettings, theme }) => {
|
|
|
212
217
|
setFetchingModels(providerId);
|
|
213
218
|
try {
|
|
214
219
|
const apiKey = settings[apiKeyField];
|
|
215
|
-
const res = await
|
|
220
|
+
const res = await api_1.default.post('/system/settings/ai/models', {
|
|
216
221
|
provider: providerId,
|
|
217
222
|
apiKey: apiKey
|
|
218
223
|
});
|
|
219
224
|
const models = res.data?.data || [];
|
|
220
225
|
setDynamicModels(prev => ({ ...prev, [providerId]: models }));
|
|
221
|
-
|
|
226
|
+
react_hot_toast_1.default.success(`Fetched ${models.length} models for ${providerId}`);
|
|
222
227
|
}
|
|
223
228
|
catch (err) {
|
|
224
229
|
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || 'Failed to fetch models';
|
|
225
|
-
|
|
230
|
+
react_hot_toast_1.default.error(msg);
|
|
226
231
|
}
|
|
227
232
|
finally {
|
|
228
233
|
setFetchingModels(null);
|
|
229
234
|
}
|
|
230
235
|
};
|
|
231
236
|
const toggleKey = (id) => setShowKeys(prev => ({ ...prev, [id]: !prev[id] }));
|
|
232
|
-
const inp = (dark) => cn('w-full border px-3 py-2.5 text-sm font-semibold outline-none transition-colors rounded-none focus-visible:ring-2 focus-visible:ring-z-active-border focus-visible:ring-offset-1 focus-visible:ring-offset-black', dark
|
|
237
|
+
const inp = (dark) => (0, utils_1.cn)('w-full border px-3 py-2.5 text-sm font-semibold outline-none transition-colors rounded-none focus-visible:ring-2 focus-visible:ring-z-active-border focus-visible:ring-offset-1 focus-visible:ring-offset-black', dark
|
|
233
238
|
? 'bg-black border-z-border text-white placeholder:text-gray-700 focus:border-z-accent'
|
|
234
239
|
: 'bg-z-panel border-z-border text-z-primary placeholder:text-z-muted focus:border-z-accent');
|
|
235
|
-
return (
|
|
240
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)('p-5 border space-y-4 shadow-sm', dark ? 'bg-z-panel backdrop-blur-md border-z-border' : 'bg-z-input border-z-border'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Cpu, { size: 14, className: "text-z-active-text" }), (0, jsx_runtime_1.jsx)("span", { className: (0, utils_1.cn)('text-sm font-semibold ', dark ? 'text-white' : 'text-z-primary'), children: "Active Model" }), (0, jsx_runtime_1.jsx)("span", { className: "ml-auto text-sm text-z-secondary", children: "Used by all AI features" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-1.5", children: [(0, jsx_runtime_1.jsx)("label", { className: "text-sm font-semibold text-z-secondary", children: "AI Provider" }), (0, jsx_runtime_1.jsx)("select", { value: settings.aiProvider || 'openrouter', onChange: e => setSettings({ ...settings, aiProvider: e.target.value, aiModel: PROVIDERS.find(p => p.id === e.target.value)?.models[0]?.value || '' }), className: inp(dark), children: PROVIDERS.map(p => (0, jsx_runtime_1.jsx)("option", { value: p.id, children: p.name }, p.id)) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-1.5", children: [(0, jsx_runtime_1.jsx)("label", { className: "text-sm font-semibold text-z-secondary", children: "Model" }), (0, jsx_runtime_1.jsx)("select", { value: settings.aiModel || '', onChange: e => setSettings({ ...settings, aiModel: e.target.value }), className: inp(dark), children: (dynamicModels[settings.aiProvider || 'openrouter'] || PROVIDERS.find(p => p.id === (settings.aiProvider || 'openrouter'))?.models || []).map(m => ((0, jsx_runtime_1.jsxs)("option", { value: m.value, children: [m.label, " ", m.tier ? `(${m.tier})` : ''] }, m.value))) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 pt-1", children: [(0, jsx_runtime_1.jsxs)("button", { onClick: handleValidate, disabled: validating, className: "px-4 py-2 bg-z-accent hover:opacity-90 shadow-sm text-white text-sm font-semibold flex items-center gap-2 disabled:opacity-50 transition-all", children: [validating ? (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { size: 11, className: "animate-spin" }) : (0, jsx_runtime_1.jsx)(lucide_react_1.TestTube2, { size: 11 }), "Test Connection"] }), testResult && ((0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)('flex items-center gap-2 text-sm font-semibold ', testResult.ok ? 'text-z-active-text' : 'text-red-400'), children: [testResult.ok ? (0, jsx_runtime_1.jsx)(lucide_react_1.CheckCircle2, { size: 11 }) : (0, jsx_runtime_1.jsx)(lucide_react_1.AlertCircle, { size: 11 }), testResult.msg] }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 mb-3", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Lock, { size: 12, className: "text-z-secondary" }), (0, jsx_runtime_1.jsx)("span", { className: "text-sm font-semibold text-z-secondary", children: "Provider API Keys" }), (0, jsx_runtime_1.jsx)("span", { className: "ml-auto text-sm text-gray-600", children: "All keys encrypted at rest" })] }), PROVIDERS.map(provider => {
|
|
236
241
|
const isExpanded = expandedProvider === provider.id;
|
|
237
242
|
const keyValue = settings[provider.keyField] || '';
|
|
238
243
|
const hasKey = keyValue.trim() && keyValue !== '[MASKED_CREDENTIAL]';
|
|
239
244
|
const isMasked = keyValue === '[MASKED_CREDENTIAL]';
|
|
240
|
-
return (
|
|
245
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)('border transition-all shadow-sm', isExpanded
|
|
241
246
|
? (dark ? 'border-white/15 bg-black/80 backdrop-blur-md shadow-sm' : 'border-z-border-strong bg-white')
|
|
242
|
-
: ('z-card-interactive')), children: [
|
|
243
|
-
})] }),
|
|
247
|
+
: ('z-card-interactive')), children: [(0, jsx_runtime_1.jsxs)("button", { onClick: () => setExpandedProvider(isExpanded ? '' : provider.id), className: "w-full flex items-center gap-3 px-4 py-3 text-left", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 flex-1 min-w-0", children: [(0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)('w-2 h-2 rounded-full flex-shrink-0', hasKey || isMasked ? 'bg-z-accent shadow-sm' : 'bg-gray-700') }), (0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)('text-sm font-semibold', provider.color), children: provider.name }), provider.badge && ((0, jsx_runtime_1.jsx)("span", { className: "text-sm font-semibold px-1.5 py-0.5 bg-z-active-bg border border-z-active-border text-z-active-text", children: provider.badge })), (0, jsx_runtime_1.jsx)("span", { className: "text-sm text-gray-600 truncate hidden sm:block", children: provider.description })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 flex-shrink-0", children: [isMasked && (0, jsx_runtime_1.jsx)("span", { className: "text-sm text-z-active-text font-semibold", children: "Configured" }), hasKey && !isMasked && (0, jsx_runtime_1.jsx)("span", { className: "text-sm text-z-active-text font-semibold", children: "Active" }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { size: 12, className: (0, utils_1.cn)('text-z-secondary transition-transform', isExpanded && 'rotate-90') })] })] }), isExpanded && ((0, jsx_runtime_1.jsxs)("div", { className: "px-4 pb-4 space-y-3 border-t", style: { borderColor: dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)' }, children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm text-z-secondary pt-3", children: provider.description }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-1.5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("label", { className: "text-sm font-semibold text-z-secondary", children: "API Key" }), (0, jsx_runtime_1.jsxs)("a", { href: provider.docsUrl, target: "_blank", rel: "noopener noreferrer", className: "text-sm text-z-active-text hover:text-z-active-text flex items-center gap-1 transition-colors", children: ["Get Key ", (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { size: 9 })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)("input", { type: showKeys[provider.id] ? 'text' : 'password', value: keyValue, onChange: e => setSettings({ ...settings, [provider.keyField]: e.target.value }), placeholder: isMasked ? '••••••••••••••••' : provider.keyPlaceholder, className: (0, utils_1.cn)(inp(dark), 'pr-10 font-mono') }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => toggleKey(provider.id), className: "absolute right-3 top-1/2 -translate-y-1/2 text-z-secondary hover:text-white transition-colors", children: showKeys[provider.id] ? (0, jsx_runtime_1.jsx)(lucide_react_1.EyeOff, { size: 13 }) : (0, jsx_runtime_1.jsx)(lucide_react_1.Eye, { size: 13 }) })] }), isMasked && ((0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-amber-500/70 flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Lock, { size: 9 }), " Key is stored \u2014 enter a new value to replace it"] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-1.5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("label", { className: "text-sm font-semibold text-z-secondary", children: ["Available Models ", dynamicModels[provider.id] ? `(${dynamicModels[provider.id].length})` : ''] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => handleFetchModels(provider.id, provider.keyField), disabled: fetchingModels === provider.id || (!hasKey && !isMasked), className: "text-sm text-z-active-text hover:text-z-active-text flex items-center gap-1 disabled:opacity-50 transition-colors", children: [fetchingModels === provider.id ? (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { size: 9, className: "animate-spin" }) : (0, jsx_runtime_1.jsx)(lucide_react_1.Zap, { size: 9 }), "Fetch Models"] })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-wrap gap-1.5 max-h-[120px] overflow-y-auto pr-2 custom-scrollbar", children: (dynamicModels[provider.id] || provider.models).map(m => ((0, jsx_runtime_1.jsx)("span", { title: m.value, className: (0, utils_1.cn)('text-sm font-semibold px-2 py-1 border', m.tier ? TIER_BADGE[m.tier] : 'bg-z-hover border-white/10 text-z-muted'), children: m.label }, m.value))) })] })] }))] }, provider.id));
|
|
248
|
+
})] }), (0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)('flex gap-3 p-4 border', dark ? 'bg-z-accent/5 border-z-accent/15' : 'bg-z-active-bg border-z-active-border'), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Info, { size: 12, className: "text-z-active-text flex-shrink-0 mt-0.5" }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-1", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-z-active-text", children: "Provider Priority" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-z-secondary leading-relaxed", children: "The AI engine auto-selects providers in this order: OpenRouter \u2192 xAI \u2192 NVIDIA NIM \u2192 Groq \u2192 Together AI \u2192 Mistral \u2192 Cohere \u2192 OpenAI \u2192 Anthropic \u2192 Google Gemini. Set the \"Active Model\" above to override. Keys are never sent to the client." })] })] })] }));
|
|
244
249
|
};
|
|
245
|
-
|
|
250
|
+
exports.default = SettingsAi;
|
|
@@ -1 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./plugin"), exports);
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SettingsAi = exports.AIWriterPage = void 0;
|
|
7
|
+
var AIWriterPage_1 = require("./AIWriterPage");
|
|
8
|
+
Object.defineProperty(exports, "AIWriterPage", { enumerable: true, get: function () { return __importDefault(AIWriterPage_1).default; } });
|
|
9
|
+
var SettingsAi_1 = require("./SettingsAi");
|
|
10
|
+
Object.defineProperty(exports, "SettingsAi", { enumerable: true, get: function () { return __importDefault(SettingsAi_1).default; } });
|
package/dist/plugin.d.ts
ADDED
package/dist/plugin.js
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenith-open/zenithcms-plugin-ai-architect-ui",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "1.0.0-beta.8",
|
|
5
4
|
"description": "AI Copilot and SEO Architect UI Plugin for Zenith CMS",
|
|
6
5
|
"main": "dist/index.js",
|
|
7
6
|
"types": "dist/index.d.ts",
|