Files
mkd/components/admin/AISection.tsx
2026-02-04 00:17:04 +05:00

121 lines
4.5 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useState } from 'react';
import { backendApi } from '../../services/apiClient';
import { Bot, Loader2 } from 'lucide-react';
export const AISection: React.FC = () => {
const [enabled, setEnabled] = useState(false);
const [url, setUrl] = useState('');
const [apiKey, setApiKey] = useState('');
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
useEffect(() => {
backendApi
.getAIChatSettings()
.then((data) => {
setEnabled(data.enabled !== false);
setUrl(data.url || '');
setApiKey(data.apiKey || '');
})
.catch(() => {
setEnabled(false);
setUrl('');
setApiKey('');
})
.finally(() => setLoading(false));
}, []);
const handleSave = async () => {
setSaving(true);
try {
await backendApi.saveAIChatSettings({
enabled,
url: url.trim(),
apiKey: apiKey.trim(),
});
window.dispatchEvent(new CustomEvent('mkd-ai-status-changed'));
alert('Настройки ИИ сохранены');
} catch (e: unknown) {
const msg = e && typeof e === 'object' && 'message' in e ? String((e as { message: string }).message) : 'Ошибка сохранения';
alert(msg);
} finally {
setSaving(false);
}
};
if (loading) {
return (
<div className="flex items-center justify-center py-12 text-slate-500">
<Loader2 className="w-8 h-8 animate-spin" />
</div>
);
}
return (
<div className="space-y-6">
<div className="flex items-center gap-3 mb-6">
<div className="w-12 h-12 rounded-xl bg-primary-50 flex items-center justify-center">
<Bot className="w-6 h-6 text-primary-600" />
</div>
<div>
<h3 className="text-lg font-bold text-slate-800">ИИ-помощник</h3>
<p className="text-sm text-slate-500">Включение чата с ИИ и настройка адреса API (OpenAI-совместимый) и токена</p>
</div>
</div>
<div className="flex items-center gap-3">
<button
type="button"
role="switch"
aria-checked={enabled}
onClick={() => setEnabled((v) => !v)}
className={`relative inline-flex h-7 w-12 flex-shrink-0 rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 ${
enabled ? 'bg-primary-600' : 'bg-slate-200'
}`}
>
<span
className={`pointer-events-none inline-block h-6 w-6 transform rounded-full bg-white shadow ring-0 transition ${
enabled ? 'translate-x-5' : 'translate-x-1'
}`}
/>
</button>
<span className="text-sm font-medium text-slate-700">{enabled ? 'ИИ включён' : 'ИИ выключен'}</span>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Адрес API</label>
<input
type="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://ai.iieasy.ru/v1/chat/completions"
className="w-full rounded-xl border border-slate-200 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
<p className="text-xs text-slate-500 mt-1">URL эндпоинта Chat Completions (OpenAI-совместимый)</p>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Токен (API key)</label>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="Опционально, если API требует авторизацию"
className="w-full rounded-xl border border-slate-200 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
autoComplete="off"
/>
</div>
<button
type="button"
onClick={handleSave}
disabled={saving}
className="rounded-xl bg-primary-600 text-white px-5 py-2.5 text-sm font-medium hover:bg-primary-700 disabled:opacity-50 flex items-center gap-2"
>
{saving ? <Loader2 className="w-4 h-4 animate-spin" /> : null}
Сохранить
</button>
</div>
);
};