feat: add admin usage statistics and LLM configuration management

This commit is contained in:
2026-03-12 16:30:24 +08:00
parent 202f2fa83b
commit 7f25dd09f6
16 changed files with 757 additions and 300 deletions

View File

@@ -11,7 +11,8 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
import { Navbar } from '@/components/Navbar'
import { AudioPlayer } from '@/components/AudioPlayer'
import { audiobookApi, type AudiobookProject, type AudiobookProjectDetail, type AudiobookCharacter, type AudiobookSegment } from '@/lib/api/audiobook'
import apiClient, { formatApiError } from '@/lib/api'
import apiClient, { formatApiError, adminApi } from '@/lib/api'
import { useAuth } from '@/contexts/AuthContext'
function LazyAudioPlayer({ audioUrl, jobId }: { audioUrl: string; jobId: number }) {
const [visible, setVisible] = useState(false)
@@ -223,7 +224,7 @@ function LLMConfigDialog({ open, onClose }: { open: boolean; onClose: () => void
const [existing, setExisting] = useState<{ base_url?: string; model?: string; has_key: boolean } | null>(null)
useEffect(() => {
if (open) audiobookApi.getLLMConfig().then(setExisting).catch(() => {})
if (open) adminApi.getLLMConfig().then(setExisting).catch(() => {})
}, [open])
const handleSave = async () => {
@@ -233,10 +234,10 @@ function LLMConfigDialog({ open, onClose }: { open: boolean; onClose: () => void
}
setLoading(true)
try {
await audiobookApi.setLLMConfig({ base_url: baseUrl, api_key: apiKey, model })
await adminApi.setLLMConfig({ base_url: baseUrl, api_key: apiKey, model })
toast.success(t('llmConfigPanel.savedSuccess'))
setApiKey('')
const updated = await audiobookApi.getLLMConfig()
const updated = await adminApi.getLLMConfig()
setExisting(updated)
onClose()
} catch (e: any) {
@@ -360,6 +361,7 @@ function ProjectListSidebar({
loading,
collapsed,
onToggle,
isSuperuser,
}: {
projects: AudiobookProject[]
selectedId: number | null
@@ -369,6 +371,7 @@ function ProjectListSidebar({
loading: boolean
collapsed: boolean
onToggle: () => void
isSuperuser?: boolean
}) {
const { t } = useTranslation('audiobook')
return (
@@ -387,9 +390,11 @@ function ProjectListSidebar({
{!collapsed && (
<>
<div className="flex items-center justify-end px-2 pb-1 gap-0.5 shrink-0">
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={onLLM} title={t('llmConfig')}>
<Settings2 className="h-4 w-4" />
</Button>
{isSuperuser && (
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={onLLM} title={t('llmConfig')}>
<Settings2 className="h-4 w-4" />
</Button>
)}
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={onNew} title={t('newProject')}>
<Plus className="h-4 w-4" />
</Button>
@@ -1066,6 +1071,7 @@ function ChaptersPanel({
export default function Audiobook() {
const { t } = useTranslation('audiobook')
const { user } = useAuth()
const [projects, setProjects] = useState<AudiobookProject[]>([])
const [selectedProjectId, setSelectedProjectId] = useState<number | null>(null)
const [detail, setDetail] = useState<AudiobookProjectDetail | null>(null)
@@ -1447,6 +1453,7 @@ export default function Audiobook() {
loading={loading}
collapsed={!sidebarOpen}
onToggle={() => setSidebarOpen(v => !v)}
isSuperuser={user?.is_superuser}
/>
<div className="flex-1 flex flex-col overflow-hidden bg-muted/30">
<Navbar />