@zenith-open/zenithcms-plugin-ai-architect-ui 1.0.0-beta.8 → 1.0.0-beta.9
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/plugin-ai-architect-ui/src/AIWriterPage.js +54 -59
- package/dist/plugin-ai-architect-ui/src/SettingsAi.js +24 -29
- package/dist/plugin-ai-architect-ui/src/index.js +1 -17
- package/dist/plugin-ai-architect-ui/src/plugin.js +2 -10
- package/package.json +2 -1
- package/dist/AIWriterPage.d.ts +0 -2
- package/dist/AIWriterPage.js +0 -192
- package/dist/SettingsAi.d.ts +0 -8
- package/dist/SettingsAi.js +0 -245
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/plugin.d.ts +0 -2
- package/dist/plugin.js +0 -2
|
@@ -1,60 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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");
|
|
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 '../../admin/src/lib/api';
|
|
5
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
6
|
+
import { cn } from '../../admin/src/lib/utils';
|
|
7
|
+
import toast from 'react-hot-toast';
|
|
8
|
+
import { useTheme } from '../../admin/src/context/ThemeContext';
|
|
9
|
+
import { PageHeader } from '../../admin/src/components/ui/PageHeader';
|
|
15
10
|
// ── Tool Definitions ───────────────────────────────────────────────────────────
|
|
16
11
|
const TOOLS = [
|
|
17
|
-
{ id: 'seo', name: 'SEO Analysis', icon:
|
|
18
|
-
{ id: 'quality', name: 'Quality Audit', icon:
|
|
19
|
-
{ id: 'improve', name: 'Refine Text', icon:
|
|
20
|
-
{ id: 'meta', name: 'Meta Generator', icon:
|
|
21
|
-
{ id: 'alt', name: 'Alt Text', icon:
|
|
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' },
|
|
22
17
|
];
|
|
23
18
|
const MODES = [
|
|
24
|
-
{ id: 'writer', label: 'Writer', icon:
|
|
25
|
-
{ id: 'architect', label: 'Architect', icon:
|
|
26
|
-
{ id: 'tools', label: 'Tools', icon:
|
|
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' },
|
|
27
22
|
];
|
|
28
23
|
// ── Result Renderers ──────────────────────────────────────────────────────────
|
|
29
24
|
function SeoResultView({ data }) {
|
|
30
|
-
return ((
|
|
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)))] }))] }));
|
|
31
26
|
}
|
|
32
27
|
function QualityResultView({ data }) {
|
|
33
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' };
|
|
34
|
-
return ((
|
|
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: [
|
|
35
30
|
{ label: 'Words', val: data.wordCount },
|
|
36
31
|
{ label: 'Sentences', val: data.sentenceCount },
|
|
37
32
|
{ label: 'Avg Words/Sent', val: data.avgWordsPerSentence },
|
|
38
|
-
].map(m => ((
|
|
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))] }))] }));
|
|
39
34
|
}
|
|
40
35
|
function SchemaResultView({ data }) {
|
|
41
|
-
return ((
|
|
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)))] })] }));
|
|
42
37
|
}
|
|
43
38
|
// ── Main Component ─────────────────────────────────────────────────────────────
|
|
44
39
|
const AIWriterPage = () => {
|
|
45
|
-
const { theme } =
|
|
40
|
+
const { theme } = useTheme();
|
|
46
41
|
const dark = theme === 'dark';
|
|
47
|
-
const [mode, setMode] =
|
|
48
|
-
const [prompt, setPrompt] =
|
|
49
|
-
const [loading, setLoading] =
|
|
50
|
-
const [result, setResult] =
|
|
51
|
-
const [activeTool, setActiveTool] =
|
|
52
|
-
const [history, setHistory] =
|
|
53
|
-
const [schemaForSave, setSchemaForSave] =
|
|
54
|
-
const [savingSchema, setSavingSchema] =
|
|
55
|
-
const textareaRef =
|
|
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);
|
|
56
51
|
// Auto-resize textarea
|
|
57
|
-
|
|
52
|
+
useEffect(() => {
|
|
58
53
|
if (textareaRef.current) {
|
|
59
54
|
textareaRef.current.style.height = 'auto';
|
|
60
55
|
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 300)}px`;
|
|
@@ -70,12 +65,12 @@ const AIWriterPage = () => {
|
|
|
70
65
|
let res;
|
|
71
66
|
let resultData;
|
|
72
67
|
if (mode === 'architect') {
|
|
73
|
-
res = await
|
|
68
|
+
res = await api.post('/system/ai-architect', { prompt });
|
|
74
69
|
resultData = res.data.data.schema;
|
|
75
70
|
setSchemaForSave(resultData);
|
|
76
71
|
}
|
|
77
72
|
else if (mode === 'writer') {
|
|
78
|
-
res = await
|
|
73
|
+
res = await api.post('/content-tools/ai/generate', { prompt });
|
|
79
74
|
resultData = res.data.data.text;
|
|
80
75
|
}
|
|
81
76
|
else {
|
|
@@ -93,7 +88,7 @@ const AIWriterPage = () => {
|
|
|
93
88
|
else if (activeTool === 'alt') {
|
|
94
89
|
payload = { imageUrl: prompt };
|
|
95
90
|
}
|
|
96
|
-
res = await
|
|
91
|
+
res = await api.post(tool.endpoint, payload);
|
|
97
92
|
const d = res.data.data;
|
|
98
93
|
if (activeTool === 'seo')
|
|
99
94
|
resultData = d;
|
|
@@ -114,11 +109,11 @@ const AIWriterPage = () => {
|
|
|
114
109
|
result: resultData,
|
|
115
110
|
timestamp: new Date()
|
|
116
111
|
}, ...prev.slice(0, 19)]);
|
|
117
|
-
|
|
112
|
+
toast.success('Generated');
|
|
118
113
|
}
|
|
119
114
|
catch (err) {
|
|
120
115
|
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || 'AI request failed';
|
|
121
|
-
|
|
116
|
+
toast.error(msg);
|
|
122
117
|
}
|
|
123
118
|
finally {
|
|
124
119
|
setLoading(false);
|
|
@@ -133,12 +128,12 @@ const AIWriterPage = () => {
|
|
|
133
128
|
return;
|
|
134
129
|
setSavingSchema(true);
|
|
135
130
|
try {
|
|
136
|
-
await
|
|
137
|
-
|
|
131
|
+
await api.post('/schemas', schemaForSave);
|
|
132
|
+
toast.success(`Collection "${schemaForSave.name}" created!`);
|
|
138
133
|
setSchemaForSave(null);
|
|
139
134
|
}
|
|
140
135
|
catch (err) {
|
|
141
|
-
|
|
136
|
+
toast.error(err?.response?.data?.error?.message || 'Failed to save schema');
|
|
142
137
|
}
|
|
143
138
|
finally {
|
|
144
139
|
setSavingSchema(false);
|
|
@@ -147,7 +142,7 @@ const AIWriterPage = () => {
|
|
|
147
142
|
const copyResult = () => {
|
|
148
143
|
const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
149
144
|
navigator.clipboard.writeText(text);
|
|
150
|
-
|
|
145
|
+
toast.success('Copied to clipboard');
|
|
151
146
|
};
|
|
152
147
|
const downloadResult = () => {
|
|
153
148
|
const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
@@ -164,12 +159,12 @@ const AIWriterPage = () => {
|
|
|
164
159
|
if (!result)
|
|
165
160
|
return null;
|
|
166
161
|
if (mode === 'architect')
|
|
167
|
-
return (
|
|
162
|
+
return _jsx(SchemaResultView, { data: result });
|
|
168
163
|
if (mode === 'tools' && activeTool === 'seo')
|
|
169
|
-
return (
|
|
164
|
+
return _jsx(SeoResultView, { data: result });
|
|
170
165
|
if (mode === 'tools' && activeTool === 'quality')
|
|
171
|
-
return (
|
|
172
|
-
return ((
|
|
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) }));
|
|
173
168
|
};
|
|
174
169
|
const activePlaceholder = mode === 'architect'
|
|
175
170
|
? 'Describe a collection schema... e.g., "An e-commerce product with variants, pricing, and inventory tracking"'
|
|
@@ -180,18 +175,18 @@ const AIWriterPage = () => {
|
|
|
180
175
|
: activeTool === 'meta'
|
|
181
176
|
? 'Paste your content to generate a meta description...'
|
|
182
177
|
: 'Paste text to analyze...';
|
|
183
|
-
return ((
|
|
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
|
|
184
179
|
? (dark ? 'bg-white text-black' : 'bg-gray-900 text-white')
|
|
185
|
-
: (dark ? 'text-z-secondary hover:text-white' : 'text-z-secondary hover:text-z-primary')), children: [(
|
|
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
|
|
186
181
|
? (dark ? 'bg-white text-black border-white' : 'bg-gray-900 text-white border-gray-900')
|
|
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: [(
|
|
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
|
|
188
183
|
? 'bg-transparent text-white placeholder:text-gray-700'
|
|
189
|
-
: 'bg-transparent text-z-primary placeholder:text-z-muted') }), (
|
|
190
|
-
? (
|
|
191
|
-
: (
|
|
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: () => {
|
|
192
187
|
const text = JSON.stringify(schemaForSave, null, 2);
|
|
193
188
|
navigator.clipboard.writeText(text);
|
|
194
|
-
|
|
195
|
-
}, className:
|
|
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"] })] })] }) })) })] })] })] }));
|
|
196
191
|
};
|
|
197
|
-
|
|
192
|
+
export default AIWriterPage;
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
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"));
|
|
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 '../../admin/src/lib/utils';
|
|
5
|
+
import api from '../../admin/src/lib/api';
|
|
6
|
+
import toast from 'react-hot-toast';
|
|
12
7
|
const PROVIDERS = [
|
|
13
8
|
{
|
|
14
9
|
id: 'openrouter',
|
|
@@ -178,12 +173,12 @@ const TIER_BADGE = {
|
|
|
178
173
|
};
|
|
179
174
|
const SettingsAi = ({ settings, setSettings, theme }) => {
|
|
180
175
|
const dark = theme === 'dark';
|
|
181
|
-
const [validating, setValidating] =
|
|
182
|
-
const [testResult, setTestResult] =
|
|
183
|
-
const [showKeys, setShowKeys] =
|
|
184
|
-
const [expandedProvider, setExpandedProvider] =
|
|
185
|
-
const [dynamicModels, setDynamicModels] =
|
|
186
|
-
const [fetchingModels, setFetchingModels] =
|
|
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);
|
|
187
182
|
const activeProvider = PROVIDERS.find(p => {
|
|
188
183
|
const key = settings[p.keyField]?.trim();
|
|
189
184
|
return key && key !== '[MASKED_CREDENTIAL]';
|
|
@@ -196,18 +191,18 @@ const SettingsAi = ({ settings, setSettings, theme }) => {
|
|
|
196
191
|
const providerConfig = PROVIDERS.find(p => p.id === providerId);
|
|
197
192
|
const apiKeyField = providerConfig ? providerConfig.keyField : 'openRouterApiKey';
|
|
198
193
|
const apiKey = settings[apiKeyField];
|
|
199
|
-
const res = await
|
|
194
|
+
const res = await api.post('/system/settings/ai/validate', {
|
|
200
195
|
provider: providerId,
|
|
201
196
|
model: settings.aiModel,
|
|
202
197
|
apiKey: apiKey
|
|
203
198
|
});
|
|
204
199
|
setTestResult({ ok: true, msg: res.data.message || 'API Key is valid' });
|
|
205
|
-
|
|
200
|
+
toast.success('AI connection verified');
|
|
206
201
|
}
|
|
207
202
|
catch (err) {
|
|
208
203
|
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || 'Connection failed';
|
|
209
204
|
setTestResult({ ok: false, msg });
|
|
210
|
-
|
|
205
|
+
toast.error('AI connection failed');
|
|
211
206
|
}
|
|
212
207
|
finally {
|
|
213
208
|
setValidating(false);
|
|
@@ -217,34 +212,34 @@ const SettingsAi = ({ settings, setSettings, theme }) => {
|
|
|
217
212
|
setFetchingModels(providerId);
|
|
218
213
|
try {
|
|
219
214
|
const apiKey = settings[apiKeyField];
|
|
220
|
-
const res = await
|
|
215
|
+
const res = await api.post('/system/settings/ai/models', {
|
|
221
216
|
provider: providerId,
|
|
222
217
|
apiKey: apiKey
|
|
223
218
|
});
|
|
224
219
|
const models = res.data?.data || [];
|
|
225
220
|
setDynamicModels(prev => ({ ...prev, [providerId]: models }));
|
|
226
|
-
|
|
221
|
+
toast.success(`Fetched ${models.length} models for ${providerId}`);
|
|
227
222
|
}
|
|
228
223
|
catch (err) {
|
|
229
224
|
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || 'Failed to fetch models';
|
|
230
|
-
|
|
225
|
+
toast.error(msg);
|
|
231
226
|
}
|
|
232
227
|
finally {
|
|
233
228
|
setFetchingModels(null);
|
|
234
229
|
}
|
|
235
230
|
};
|
|
236
231
|
const toggleKey = (id) => setShowKeys(prev => ({ ...prev, [id]: !prev[id] }));
|
|
237
|
-
const inp = (dark) =>
|
|
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
|
|
238
233
|
? 'bg-black border-z-border text-white placeholder:text-gray-700 focus:border-z-accent'
|
|
239
234
|
: 'bg-z-panel border-z-border text-z-primary placeholder:text-z-muted focus:border-z-accent');
|
|
240
|
-
return ((
|
|
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 => {
|
|
241
236
|
const isExpanded = expandedProvider === provider.id;
|
|
242
237
|
const keyValue = settings[provider.keyField] || '';
|
|
243
238
|
const hasKey = keyValue.trim() && keyValue !== '[MASKED_CREDENTIAL]';
|
|
244
239
|
const isMasked = keyValue === '[MASKED_CREDENTIAL]';
|
|
245
|
-
return ((
|
|
240
|
+
return (_jsxs("div", { className: cn('border transition-all shadow-sm', isExpanded
|
|
246
241
|
? (dark ? 'border-white/15 bg-black/80 backdrop-blur-md shadow-sm' : 'border-z-border-strong bg-white')
|
|
247
|
-
: ('z-card-interactive')), children: [(
|
|
248
|
-
})] }), (
|
|
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." })] })] })] }));
|
|
249
244
|
};
|
|
250
|
-
|
|
245
|
+
export default SettingsAi;
|
|
@@ -1,17 +1 @@
|
|
|
1
|
-
|
|
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
|
+
export * from './plugin';
|
|
@@ -1,10 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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; } });
|
|
1
|
+
export { default as AIWriterPage } from './AIWriterPage';
|
|
2
|
+
export { default as SettingsAi } from './SettingsAi';
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenith-open/zenithcms-plugin-ai-architect-ui",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.9",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "AI Copilot and SEO Architect UI Plugin for Zenith CMS",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
package/dist/AIWriterPage.d.ts
DELETED
package/dist/AIWriterPage.js
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
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;
|
package/dist/SettingsAi.d.ts
DELETED
package/dist/SettingsAi.js
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
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
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './plugin';
|
package/dist/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './plugin';
|
package/dist/plugin.d.ts
DELETED
package/dist/plugin.js
DELETED