m19921414377's picture
Upload folder using huggingface_hub
6db48b4 verified
import axios from 'axios'
const API_BASE_URL = '/api'
export interface Page {
index: number
type: 'cover' | 'content' | 'summary'
content: string
}
export interface OutlineResponse {
success: boolean
outline?: string
pages?: Page[]
error?: string
}
export interface ProgressEvent {
index: number
status: 'generating' | 'done' | 'error'
current?: number
total?: number
image_url?: string
message?: string
}
export interface FinishEvent {
success: boolean
task_id: string
images: string[]
}
// 生成大纲(支持图片上传)
export async function generateOutline(
topic: string,
images?: File[]
): Promise<OutlineResponse & { has_images?: boolean }> {
// 如果有图片,使用 FormData
if (images && images.length > 0) {
const formData = new FormData()
formData.append('topic', topic)
images.forEach((file) => {
formData.append('images', file)
})
const response = await axios.post<OutlineResponse & { has_images?: boolean }>(
`${API_BASE_URL}/outline`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
)
return response.data
}
// 无图片,使用 JSON
const response = await axios.post<OutlineResponse>(`${API_BASE_URL}/outline`, {
topic
})
return response.data
}
// 获取图片 URL(新格式:task_id/filename)
// thumbnail 参数:true=缩略图(默认),false=原图
export function getImageUrl(taskId: string, filename: string, thumbnail: boolean = true): string {
const thumbParam = thumbnail ? '?thumbnail=true' : '?thumbnail=false'
return `${API_BASE_URL}/images/${taskId}/${filename}${thumbParam}`
}
// 重新生成图片(即使成功的也可以重新生成)
export async function regenerateImage(
taskId: string,
page: Page,
useReference: boolean = true,
context?: {
fullOutline?: string
userTopic?: string
}
): Promise<{ success: boolean; index: number; image_url?: string; error?: string }> {
const response = await axios.post(`${API_BASE_URL}/regenerate`, {
task_id: taskId,
page,
use_reference: useReference,
full_outline: context?.fullOutline,
user_topic: context?.userTopic
})
return response.data
}
// 批量重试失败的图片(SSE)
export async function retryFailedImages(
taskId: string,
pages: Page[],
onProgress: (event: ProgressEvent) => void,
onComplete: (event: ProgressEvent) => void,
onError: (event: ProgressEvent) => void,
onFinish: (event: { success: boolean; total: number; completed: number; failed: number }) => void,
onStreamError: (error: Error) => void
) {
try {
const response = await fetch(`${API_BASE_URL}/retry-failed`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
task_id: taskId,
pages
})
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const reader = response.body?.getReader()
if (!reader) {
throw new Error('无法读取响应流')
}
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (!line.trim()) continue
const [eventLine, dataLine] = line.split('\n')
if (!eventLine || !dataLine) continue
const eventType = eventLine.replace('event: ', '').trim()
const eventData = dataLine.replace('data: ', '').trim()
try {
const data = JSON.parse(eventData)
switch (eventType) {
case 'retry_start':
onProgress({ index: -1, status: 'generating', message: data.message })
break
case 'complete':
onComplete(data)
break
case 'error':
onError(data)
break
case 'retry_finish':
onFinish(data)
break
}
} catch (e) {
console.error('解析 SSE 数据失败:', e)
}
}
}
} catch (error) {
onStreamError(error as Error)
}
}
// ==================== 历史记录相关 API ====================
export interface HistoryRecord {
id: string
title: string
created_at: string
updated_at: string
status: string
thumbnail: string | null
page_count: number
task_id: string | null
}
export interface HistoryDetail {
id: string
title: string
created_at: string
updated_at: string
outline: {
raw: string
pages: Page[]
}
images: {
task_id: string | null
generated: string[]
}
status: string
thumbnail: string | null
}
// 创建历史记录
export async function createHistory(
topic: string,
outline: { raw: string; pages: Page[] },
taskId?: string
): Promise<{ success: boolean; record_id?: string; error?: string }> {
const response = await axios.post(`${API_BASE_URL}/history`, {
topic,
outline,
task_id: taskId
})
return response.data
}
// 获取历史记录列表
export async function getHistoryList(
page: number = 1,
pageSize: number = 20,
status?: string
): Promise<{
success: boolean
records: HistoryRecord[]
total: number
page: number
page_size: number
total_pages: number
}> {
const params: any = { page, page_size: pageSize }
if (status) params.status = status
const response = await axios.get(`${API_BASE_URL}/history`, { params })
return response.data
}
// 获取历史记录详情
export async function getHistory(recordId: string): Promise<{
success: boolean
record?: HistoryDetail
error?: string
}> {
const response = await axios.get(`${API_BASE_URL}/history/${recordId}`)
return response.data
}
// 更新历史记录
export async function updateHistory(
recordId: string,
data: {
outline?: { raw: string; pages: Page[] }
images?: { task_id: string | null; generated: string[] }
status?: string
thumbnail?: string
}
): Promise<{ success: boolean; error?: string }> {
const response = await axios.put(`${API_BASE_URL}/history/${recordId}`, data)
return response.data
}
// 删除历史记录
export async function deleteHistory(recordId: string): Promise<{
success: boolean
error?: string
}> {
const response = await axios.delete(`${API_BASE_URL}/history/${recordId}`)
return response.data
}
// 搜索历史记录
export async function searchHistory(keyword: string): Promise<{
success: boolean
records: HistoryRecord[]
}> {
const response = await axios.get(`${API_BASE_URL}/history/search`, {
params: { keyword }
})
return response.data
}
// 获取统计信息
export async function getHistoryStats(): Promise<{
success: boolean
total: number
by_status: Record<string, number>
}> {
const response = await axios.get(`${API_BASE_URL}/history/stats`)
return response.data
}
// 使用 POST 方式生成图片(更可靠)
export async function generateImagesPost(
pages: Page[],
taskId: string | null,
fullOutline: string,
onProgress: (event: ProgressEvent) => void,
onComplete: (event: ProgressEvent) => void,
onError: (event: ProgressEvent) => void,
onFinish: (event: FinishEvent) => void,
onStreamError: (error: Error) => void,
userImages?: File[],
userTopic?: string
) {
try {
// 将用户图片转换为 base64
let userImagesBase64: string[] = []
if (userImages && userImages.length > 0) {
userImagesBase64 = await Promise.all(
userImages.map(file => {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result as string)
reader.onerror = reject
reader.readAsDataURL(file)
})
})
)
}
const response = await fetch(`${API_BASE_URL}/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pages,
task_id: taskId,
full_outline: fullOutline,
user_images: userImagesBase64.length > 0 ? userImagesBase64 : undefined,
user_topic: userTopic || ''
})
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const reader = response.body?.getReader()
if (!reader) {
throw new Error('无法读取响应流')
}
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (!line.trim()) continue
const [eventLine, dataLine] = line.split('\n')
if (!eventLine || !dataLine) continue
const eventType = eventLine.replace('event: ', '').trim()
const eventData = dataLine.replace('data: ', '').trim()
try {
const data = JSON.parse(eventData)
switch (eventType) {
case 'progress':
onProgress(data)
break
case 'complete':
onComplete(data)
break
case 'error':
onError(data)
break
case 'finish':
onFinish(data)
break
}
} catch (e) {
console.error('解析 SSE 数据失败:', e)
}
}
}
} catch (error) {
onStreamError(error as Error)
}
}
// 扫描所有任务并同步图片列表
export async function scanAllTasks(): Promise<{
success: boolean
total_tasks?: number
synced?: number
failed?: number
orphan_tasks?: string[]
results?: any[]
error?: string
}> {
const response = await axios.post(`${API_BASE_URL}/history/scan-all`)
return response.data
}
// ==================== 配置管理 API ====================
export interface Config {
text_generation: {
active_provider: string
providers: Record<string, any>
}
image_generation: {
active_provider: string
providers: Record<string, any>
}
}
// 获取配置
export async function getConfig(): Promise<{
success: boolean
config?: Config
error?: string
}> {
const response = await axios.get(`${API_BASE_URL}/config`)
return response.data
}
// 更新配置
export async function updateConfig(config: Partial<Config>): Promise<{
success: boolean
message?: string
error?: string
}> {
const response = await axios.post(`${API_BASE_URL}/config`, config)
return response.data
}
// 测试服务商连接
export async function testConnection(config: {
type: string
provider_name?: string
api_key?: string
base_url?: string
model: string
}): Promise<{
success: boolean
message?: string
error?: string
}> {
const response = await axios.post(`${API_BASE_URL}/config/test`, config)
return response.data
}