feat: 添加 AI 索引重建功能,优化相关 API 和工作流,增强内存管理配置
Some checks failed
docker-images / resolve-build-targets (push) Successful in 6s
ui-regression / playwright-regression (push) Successful in 4m43s
docker-images / build-and-push (admin) (push) Successful in 42s
docker-images / submit-indexnow (push) Has been cancelled
docker-images / build-and-push (frontend) (push) Has been cancelled
docker-images / build-and-push (backend) (push) Has started running
Some checks failed
docker-images / resolve-build-targets (push) Successful in 6s
ui-regression / playwright-regression (push) Successful in 4m43s
docker-images / build-and-push (admin) (push) Successful in 42s
docker-images / submit-indexnow (push) Has been cancelled
docker-images / build-and-push (frontend) (push) Has been cancelled
docker-images / build-and-push (backend) (push) Has started running
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import type {
|
||||
AdminAnalyticsResponse,
|
||||
AdminAiImageProviderTestResponse,
|
||||
AdminAiReindexResponse,
|
||||
AdminAiProviderTestResponse,
|
||||
AdminImageUploadResponse,
|
||||
AdminMediaBatchDeleteResponse,
|
||||
@@ -362,7 +361,7 @@ export const adminApi = {
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
reindexAi: () =>
|
||||
request<AdminAiReindexResponse>('/api/admin/ai/reindex', {
|
||||
request<WorkerTaskActionResponse>('/api/admin/ai/reindex', {
|
||||
method: 'POST',
|
||||
}),
|
||||
testAiProvider: (provider: {
|
||||
|
||||
@@ -545,11 +545,6 @@ export interface TaxonomyPayload {
|
||||
seoDescription?: string | null
|
||||
}
|
||||
|
||||
export interface AdminAiReindexResponse {
|
||||
indexed_chunks: number
|
||||
last_indexed_at: string | null
|
||||
}
|
||||
|
||||
export interface AdminAiProviderTestResponse {
|
||||
provider: string
|
||||
endpoint: string
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Bot, Check, Plus, RefreshCcw, Save, Trash2 } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { startTransition, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { MediaUrlControls } from '@/components/media-url-controls'
|
||||
@@ -19,6 +20,7 @@ import type {
|
||||
HumanVerificationMode,
|
||||
MusicTrack,
|
||||
SiteSettingsPayload,
|
||||
WorkerJobRecord,
|
||||
} from '@/lib/types'
|
||||
|
||||
function createEmptyMusicTrack(): MusicTrack {
|
||||
@@ -237,6 +239,8 @@ export function SiteSettingsPage() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [reindexing, setReindexing] = useState(false)
|
||||
const [reindexJobId, setReindexJobId] = useState<number | null>(null)
|
||||
const [reindexJobStatus, setReindexJobStatus] = useState<WorkerJobRecord['status'] | null>(null)
|
||||
const [testingProvider, setTestingProvider] = useState(false)
|
||||
const [testingImageProvider, setTestingImageProvider] = useState(false)
|
||||
const [testingR2Storage, setTestingR2Storage] = useState(false)
|
||||
@@ -295,6 +299,74 @@ export function SiteSettingsPage() {
|
||||
})
|
||||
}, [form?.ai_active_provider_id, form?.ai_providers])
|
||||
|
||||
useEffect(() => {
|
||||
if (!reindexJobId) {
|
||||
return
|
||||
}
|
||||
|
||||
let cancelled = false
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const scheduleNextPoll = () => {
|
||||
timer = setTimeout(() => {
|
||||
void pollJob()
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
const pollJob = async () => {
|
||||
try {
|
||||
const job = await adminApi.getWorkerJob(reindexJobId)
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
setReindexJobStatus(job.status)
|
||||
|
||||
if (job.status === 'succeeded') {
|
||||
setReindexing(false)
|
||||
setReindexJobId(null)
|
||||
setReindexJobStatus(null)
|
||||
const indexedChunks = Number(job.result?.indexed_chunks ?? 0)
|
||||
toast.success(
|
||||
indexedChunks > 0
|
||||
? `AI 索引重建完成,共生成 ${indexedChunks} 个分块。`
|
||||
: 'AI 索引重建完成。',
|
||||
)
|
||||
await loadSettings(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (job.status === 'failed' || job.status === 'cancelled') {
|
||||
setReindexing(false)
|
||||
setReindexJobId(null)
|
||||
setReindexJobStatus(null)
|
||||
toast.error(job.error_text?.trim() || 'AI 重建索引失败。')
|
||||
return
|
||||
}
|
||||
|
||||
scheduleNextPoll()
|
||||
} catch (error) {
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
scheduleNextPoll()
|
||||
if (error instanceof ApiError && error.status === 401) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pollJob()
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
}, [loadSettings, reindexJobId])
|
||||
|
||||
const updateField = <K extends keyof AdminSiteSettingsResponse>(
|
||||
key: K,
|
||||
value: AdminSiteSettingsResponse[K],
|
||||
@@ -498,25 +570,38 @@ export function SiteSettingsPage() {
|
||||
<RefreshCcw className="h-4 w-4" />
|
||||
刷新
|
||||
</Button>
|
||||
{reindexJobId ? (
|
||||
<Button variant="outline" asChild data-testid="site-settings-reindex-job">
|
||||
<Link to={`/workers?job=${reindexJobId}`}>查看索引任务</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={reindexing}
|
||||
data-testid="site-settings-reindex"
|
||||
onClick={async () => {
|
||||
let queued = false
|
||||
try {
|
||||
setReindexing(true)
|
||||
setReindexJobStatus('queued')
|
||||
const result = await adminApi.reindexAi()
|
||||
toast.success(`AI 索引已重建,共生成 ${result.indexed_chunks} 个分块。`)
|
||||
await loadSettings(false)
|
||||
queued = true
|
||||
setReindexJobId(result.job.id)
|
||||
setReindexJobStatus(result.job.status)
|
||||
toast.success(`AI 重建任务已入队:#${result.job.id}`)
|
||||
} catch (error) {
|
||||
setReindexJobId(null)
|
||||
setReindexJobStatus(null)
|
||||
toast.error(error instanceof ApiError ? error.message : 'AI 重建索引失败。')
|
||||
} finally {
|
||||
setReindexing(false)
|
||||
if (!queued) {
|
||||
setReindexing(false)
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Bot className="h-4 w-4" />
|
||||
{reindexing ? '重建中...' : '重建 AI 索引'}
|
||||
{reindexing ? '任务进行中...' : '重建 AI 索引'}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={saving}
|
||||
@@ -1653,6 +1738,12 @@ export function SiteSettingsPage() {
|
||||
<p className="mt-3 text-sm leading-6 text-muted-foreground">
|
||||
{form.ai_last_indexed_at ?? '索引尚未建立。'}
|
||||
</p>
|
||||
{reindexJobId ? (
|
||||
<p className="mt-3 text-xs leading-6 text-muted-foreground">
|
||||
当前后台任务 #{reindexJobId}
|
||||
{reindexJobStatus ? `,状态:${reindexJobStatus}` : ''}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user