feat(kb): AI Draft button in KB article editor
All checks were successful
build-and-release / build (push) Successful in 1m51s

- Imports Sparkles icon from lucide-solid
- Adds [drafting, setDrafting] signal
- Adds aiDraft() function: calls POST /api/admin/kb/articles/ai-draft
  with current title + resolved category name; fills content field and
  summary field (only if summary is currently empty) from the response
- AI Draft button appears inline with the Content label, styled in the
  orange/amber palette; shows 'Generating...' while in flight
- Adds monospace font to the content textarea and a helper hint line
  reminding admins to review before saving

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tracewebstudio Dev 2026-08-15 14:03:54 +02:00
parent 8c03cd0eac
commit 11041ba91c

View file

@ -1,6 +1,6 @@
import { A } from '@solidjs/router';
import { createMemo, createResource, createSignal, For, Show } from 'solid-js';
import { BookOpen, Plus } from 'lucide-solid';
import { BookOpen, Plus, Sparkles } from 'lucide-solid';
const API = '';
@ -233,6 +233,7 @@ export default function KnowledgeBasePage(props: KnowledgeBasePageProps) {
const [categoryId, setCategoryId] = createSignal('');
const [status, setStatus] = createSignal<'PUBLISHED' | 'DRAFT'>('DRAFT');
const [saving, setSaving] = createSignal(false);
const [drafting, setDrafting] = createSignal(false);
const [formError, setFormError] = createSignal('');
const [catFormId, setCatFormId] = createSignal('');
@ -306,6 +307,38 @@ export default function KnowledgeBasePage(props: KnowledgeBasePageProps) {
setTab('editor');
};
const aiDraft = async () => {
if (!title().trim()) {
setFormError('Enter a title before generating a draft.');
return;
}
setFormError('');
setDrafting(true);
try {
const catName = (categories() || []).find((c) => c.id === categoryId())?.name;
const res = await fetch(`${API}/api/admin/kb/articles/ai-draft`, {
method: 'POST',
headers: authHeaders(true),
body: JSON.stringify({
title: title(),
category: catName,
topic_hints: undefined,
}),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
setFormError(data.error || 'AI generation failed');
return;
}
if (data.content) setContent(data.content);
if (data.summary && !summary().trim()) setSummary(data.summary);
} catch {
setFormError('AI generation failed — check your connection.');
} finally {
setDrafting(false);
}
};
const handleSaveArticle = async () => {
if (!title().trim()) {
setFormError('Title is required');
@ -675,8 +708,20 @@ export default function KnowledgeBasePage(props: KnowledgeBasePageProps) {
<input value={summary()} onInput={(e) => setSummary(e.currentTarget.value)} style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px" />
</div>
<div style="grid-column:1 / -1">
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Content</label>
<textarea rows={14} value={content()} onInput={(e) => setContent(e.currentTarget.value)} style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;resize:vertical" />
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">
<label style="display:block;font-size:13px;font-weight:600">Content</label>
<button
type="button"
onClick={aiDraft}
disabled={drafting()}
style={`display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;border-radius:6px;border:1px solid #FDBA74;background:${drafting() ? '#F3F4F6' : '#FFF5F0'};font-size:11px;font-weight:700;color:${drafting() ? '#9CA3AF' : '#92400E'};cursor:${drafting() ? 'default' : 'pointer'}`}
>
<Sparkles size={12} />
{drafting() ? 'Generating…' : 'AI Draft'}
</button>
</div>
<textarea rows={14} value={content()} onInput={(e) => setContent(e.currentTarget.value)} style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;resize:vertical;font-family:monospace" />
<p style="margin:4px 0 0;font-size:11px;color:#9CA3AF">Markdown supported. AI Draft fills this from the title + category review before saving.</p>
</div>
<div style="display:flex;align-items:center;gap:8px">
<input type="checkbox" checked={status() === 'PUBLISHED'} onChange={(e) => setStatus(e.currentTarget.checked ? 'PUBLISHED' : 'DRAFT')} />