82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { inboxApi } from '../../api.js';
|
|
|
|
export function VisionSection(): React.ReactElement {
|
|
const [models, setModels] = useState<string[]>([]);
|
|
const [at, setAt] = useState<string | null>(null);
|
|
const [selected, setSelected] = useState<string | null>(null);
|
|
const [busy, setBusy] = useState<'select' | 'refresh' | null>(null);
|
|
const [feedback, setFeedback] = useState<string | null>(null);
|
|
|
|
async function load() {
|
|
const r = await inboxApi.getVisionModels();
|
|
setModels(r.models);
|
|
setAt(r.at);
|
|
setSelected(r.selected);
|
|
}
|
|
|
|
useEffect(() => {
|
|
void load();
|
|
}, []);
|
|
|
|
async function onSelect(value: string) {
|
|
const next = value === '' ? null : value;
|
|
setBusy('select');
|
|
setFeedback(null);
|
|
await inboxApi.setVisionModel(next);
|
|
setSelected(next);
|
|
setBusy(null);
|
|
}
|
|
|
|
async function onRefresh() {
|
|
setBusy('refresh');
|
|
setFeedback(null);
|
|
const r = await inboxApi.refreshVisionCache();
|
|
setBusy(null);
|
|
if (r.ok) {
|
|
await load();
|
|
setFeedback(`감지 완료 (${r.models.length}개)`);
|
|
} else {
|
|
setFeedback(`감지 실패: ${r.reason}`);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<section style={{ marginTop: 16 }}>
|
|
<h4 style={{ fontSize: 13, marginBottom: 6 }}>이미지 분석 모델 (선택사항)</h4>
|
|
<div style={{ display: 'flex', gap: 6, alignItems: 'center', marginBottom: 6 }}>
|
|
<select
|
|
aria-label="이미지 분석 모델"
|
|
value={selected ?? ''}
|
|
onChange={(e) => { void onSelect(e.target.value); }}
|
|
disabled={busy !== null}
|
|
style={{ flex: 1, fontSize: 12, padding: '4px 8px', border: '1px solid #ccc', borderRadius: 4 }}
|
|
>
|
|
<option value="">(비활성)</option>
|
|
{models.map((m) => <option key={m} value={m}>{m}</option>)}
|
|
</select>
|
|
<button
|
|
onClick={() => { void onRefresh(); }}
|
|
disabled={busy !== null}
|
|
style={{ background: '#0a4b80', color: '#fff', border: 'none', cursor: 'pointer', fontSize: 12, padding: '4px 10px', borderRadius: 4 }}
|
|
>
|
|
{busy === 'refresh' ? '감지 중…' : '다시 감지'}
|
|
</button>
|
|
</div>
|
|
{at !== null && (
|
|
<div style={{ fontSize: 11, color: '#888' }}>
|
|
마지막 감지: {new Date(at).toLocaleString('ko-KR')}
|
|
</div>
|
|
)}
|
|
{feedback !== null && (
|
|
<div style={{ fontSize: 11, color: '#444', marginTop: 4 }}>{feedback}</div>
|
|
)}
|
|
{models.length === 0 && (
|
|
<div style={{ fontSize: 11, color: '#aaa', marginTop: 4 }}>
|
|
감지된 모델 없음. Ollama 에 vision 모델을 설치하고 "다시 감지" 클릭.
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|