Files
qa_test_app/frontend/src/pages/Settings/index.tsx
T
Aleksey Razorvin 9a0b3ba92c Спринт 4: AI-помощник на базе DeepSeek
- Страница /settings: ввод и проверка API ключа DeepSeek
- POST /api/llm/generate — генерация вопросов по названию теста
- POST /api/llm/improve — улучшение формулировки вопроса + ответов (модал с галочками)
- POST /api/llm/distractors — генерация дистракторов
- POST /api/llm/review — рецензия теста + кнопка «Предложить вариант»
- POST /api/llm/improve_all — улучшение всего теста с постатейным сравнением
- Миграция 004: таблица settings (key-value)
- Шапка приложения с навигацией на /settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 15:11:49 +05:00

125 lines
3.7 KiB
TypeScript
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 { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Alert, Button, Card, Form, Input, Space, Spin, Typography } from 'antd'
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import llmApi from '../../api/llm'
import settingsApi from '../../api/settings'
const { Title, Text } = Typography
const API_KEY = 'deepseek_api_key'
export default function Settings() {
const navigate = useNavigate()
const queryClient = useQueryClient()
const [checkResult, setCheckResult] = useState<{ ok: boolean; message: string } | null>(null)
const { data: setting, isLoading } = useQuery({
queryKey: ['settings', API_KEY],
queryFn: () => settingsApi.get(API_KEY),
})
const saveMutation = useMutation({
mutationFn: (value: string) => settingsApi.update(API_KEY, value || null),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['settings', API_KEY] })
setCheckResult(null)
},
})
const checkMutation = useMutation({
mutationFn: () => llmApi.check(),
onSuccess: (data) => setCheckResult(data),
})
const handleSave = (values: { api_key: string }) => {
saveMutation.mutate(values.api_key)
}
if (isLoading) {
return (
<div style={{ display: 'flex', justifyContent: 'center', padding: 80 }}>
<Spin size="large" />
</div>
)
}
return (
<div style={{ maxWidth: 600, margin: '40px auto', padding: '0 24px' }}>
<Title level={2}>Настройки</Title>
<Card title="AI-помощник (DeepSeek)">
<Text type="secondary" style={{ display: 'block', marginBottom: 16 }}>
Введите API ключ DeepSeek для активации AI-функций при создании и редактировании
тестов. Ключ хранится только на сервере.
</Text>
<Form layout="vertical" onFinish={handleSave} initialValues={{ api_key: setting?.value ?? '' }}>
<Form.Item
name="api_key"
label="API ключ DeepSeek"
>
<Input.Password
placeholder="sk-..."
visibilityToggle
style={{ fontFamily: 'monospace' }}
/>
</Form.Item>
<Form.Item style={{ marginBottom: 0 }}>
<Space wrap>
<Button
type="primary"
htmlType="submit"
loading={saveMutation.isPending}
>
Сохранить
</Button>
<Button
onClick={() => checkMutation.mutate()}
loading={checkMutation.isPending}
disabled={!setting?.value}
>
Проверить подключение
</Button>
<Button onClick={() => navigate('/')}>
На главную
</Button>
</Space>
</Form.Item>
</Form>
{saveMutation.isSuccess && (
<Alert
type="success"
message="Ключ сохранён"
showIcon
style={{ marginTop: 16 }}
closable
/>
)}
{checkResult && (
<Alert
type={checkResult.ok ? 'success' : 'error'}
message={checkResult.message}
icon={
checkResult.ok ? (
<CheckCircleOutlined />
) : (
<CloseCircleOutlined />
)
}
showIcon
style={{ marginTop: 16 }}
closable
onClose={() => setCheckResult(null)}
/>
)}
</Card>
</div>
)
}