114
web-app/src/api/aiConfig.ts
Normal file
114
web-app/src/api/aiConfig.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import apiClient from './client'
|
||||
|
||||
export interface AIConfig {
|
||||
id: number
|
||||
name: string
|
||||
provider: 'openai' | 'anthropic' | 'custom'
|
||||
baseUrl: string // 注意:后端返回的是 baseUrl 而不是 baseURL
|
||||
apiKey: string
|
||||
models?: string[]
|
||||
defaultModel: string
|
||||
settings?: Record<string, any>
|
||||
isActive: boolean
|
||||
isDefault: boolean
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface CreateAIConfigRequest {
|
||||
name: string
|
||||
provider: 'openai' | 'anthropic' | 'custom'
|
||||
baseUrl: string // 注意:后端使用 baseUrl 而不是 baseURL
|
||||
apiKey: string
|
||||
defaultModel?: string
|
||||
settings?: Record<string, any>
|
||||
isActive?: boolean
|
||||
isDefault?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateAIConfigRequest {
|
||||
name?: string
|
||||
provider?: 'openai' | 'anthropic' | 'custom'
|
||||
baseUrl?: string // 注意:后端使用 baseUrl 而不是 baseURL
|
||||
apiKey?: string
|
||||
defaultModel?: string
|
||||
settings?: Record<string, any>
|
||||
isActive?: boolean
|
||||
isDefault?: boolean
|
||||
}
|
||||
|
||||
export interface ModelInfo {
|
||||
id: string
|
||||
name: string
|
||||
ownedBy: string
|
||||
}
|
||||
|
||||
export interface GetModelsRequest {
|
||||
baseUrl: string // 注意:后端使用 baseUrl 而不是 baseURL
|
||||
apiKey: string
|
||||
provider: 'openai' | 'anthropic' | 'custom'
|
||||
}
|
||||
|
||||
export interface GetModelsResponse {
|
||||
models: ModelInfo[]
|
||||
}
|
||||
|
||||
export interface TestAIConfigRequest {
|
||||
baseUrl: string // 注意:后端使用 baseUrl 而不是 baseURL
|
||||
apiKey: string
|
||||
provider: 'openai' | 'anthropic' | 'custom'
|
||||
model: string
|
||||
}
|
||||
|
||||
export interface TestAIConfigResponse {
|
||||
success: boolean
|
||||
message: string
|
||||
latency: number
|
||||
}
|
||||
|
||||
export interface AIConfigListResponse {
|
||||
list: AIConfig[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export const aiConfigApi = {
|
||||
// 创建AI配置
|
||||
createAIConfig: (data: CreateAIConfigRequest) => {
|
||||
return apiClient.post<AIConfig>('/app/ai-config', data)
|
||||
},
|
||||
|
||||
// 获取AI配置列表
|
||||
getAIConfigList: () => {
|
||||
return apiClient.get<AIConfigListResponse>('/app/ai-config')
|
||||
},
|
||||
|
||||
// 更新AI配置
|
||||
updateAIConfig: (id: number, data: UpdateAIConfigRequest) => {
|
||||
return apiClient.put<AIConfig>(`/app/ai-config/${id}`, data)
|
||||
},
|
||||
|
||||
// 删除AI配置
|
||||
deleteAIConfig: (id: number) => {
|
||||
return apiClient.delete<null>(`/app/ai-config/${id}`)
|
||||
},
|
||||
|
||||
// 获取模型列表
|
||||
getModels: (data: GetModelsRequest) => {
|
||||
return apiClient.post<GetModelsResponse>('/app/ai-config/models', data)
|
||||
},
|
||||
|
||||
// 测试AI配置(用于新建时,需要传递完整信息)
|
||||
testAIConfig: (data: TestAIConfigRequest) => {
|
||||
return apiClient.post<TestAIConfigResponse>('/app/ai-config/test', data)
|
||||
},
|
||||
|
||||
// 通过ID测试AI配置(用于已保存的配置,后端会从数据库获取完整API Key)
|
||||
testAIConfigById: (id: number) => {
|
||||
return apiClient.post<TestAIConfigResponse>(`/app/ai-config/${id}/test`)
|
||||
},
|
||||
|
||||
// 通过ID获取模型列表(用于已保存的配置,后端会从数据库获取完整API Key)
|
||||
getModelsByConfigId: (id: number) => {
|
||||
return apiClient.get<GetModelsResponse>(`/app/ai-config/${id}/models`)
|
||||
},
|
||||
}
|
||||
92
web-app/src/api/auth.ts
Normal file
92
web-app/src/api/auth.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// 类型定义
|
||||
export interface RegisterRequest {
|
||||
username: string
|
||||
password: string
|
||||
nickName?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface UpdateProfileRequest {
|
||||
nickName?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
avatar?: string
|
||||
preferences?: string
|
||||
aiSettings?: string
|
||||
}
|
||||
|
||||
export interface ChangePasswordRequest {
|
||||
oldPassword: string
|
||||
newPassword: string
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
uuid: string
|
||||
username: string
|
||||
nickName: string
|
||||
email: string
|
||||
phone: string
|
||||
avatar: string
|
||||
status: string
|
||||
enable: boolean
|
||||
isAdmin: boolean
|
||||
lastLoginAt: string | null
|
||||
lastLoginIp: string
|
||||
chatCount: number
|
||||
messageCount: number
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
user: User
|
||||
token: string
|
||||
refreshToken: string
|
||||
expiresAt: number
|
||||
}
|
||||
|
||||
// API 方法
|
||||
export const authApi = {
|
||||
// 用户注册
|
||||
register: (data: RegisterRequest) => {
|
||||
return apiClient.post('/app/auth/register', data)
|
||||
},
|
||||
|
||||
// 用户登录
|
||||
login: (data: LoginRequest): Promise<{ data: LoginResponse }> => {
|
||||
return apiClient.post('/app/auth/login', data)
|
||||
},
|
||||
|
||||
// 刷新 Token
|
||||
refreshToken: (refreshToken: string): Promise<{ data: LoginResponse }> => {
|
||||
return apiClient.post('/app/auth/refresh', { refreshToken })
|
||||
},
|
||||
|
||||
// 用户登出
|
||||
logout: () => {
|
||||
return apiClient.post('/app/auth/logout')
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo: (): Promise<{ data: User }> => {
|
||||
return apiClient.get('/app/auth/userinfo')
|
||||
},
|
||||
|
||||
// 更新用户资料
|
||||
updateProfile: (data: UpdateProfileRequest) => {
|
||||
return apiClient.put('/app/user/profile', data)
|
||||
},
|
||||
|
||||
// 修改密码
|
||||
changePassword: (data: ChangePasswordRequest) => {
|
||||
return apiClient.post('/app/user/change-password', data)
|
||||
},
|
||||
}
|
||||
130
web-app/src/api/character.ts
Normal file
130
web-app/src/api/character.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// 类型定义
|
||||
export interface Character {
|
||||
id: number
|
||||
name: string
|
||||
avatar: string
|
||||
creator: string
|
||||
version: string
|
||||
description: string
|
||||
personality: string
|
||||
scenario: string
|
||||
firstMes: string
|
||||
mesExample: string
|
||||
creatorNotes: string
|
||||
systemPrompt: string
|
||||
postHistoryInstructions: string
|
||||
tags: string[]
|
||||
alternateGreetings: string[]
|
||||
characterBook: Record<string, any>
|
||||
extensions: Record<string, any>
|
||||
spec: string
|
||||
specVersion: string
|
||||
isPublic: boolean
|
||||
useCount: number
|
||||
favoriteCount: number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface CreateCharacterRequest {
|
||||
name: string
|
||||
avatar?: string
|
||||
creator?: string
|
||||
version?: string
|
||||
description?: string
|
||||
personality?: string
|
||||
scenario?: string
|
||||
firstMes?: string
|
||||
mesExample?: string
|
||||
creatorNotes?: string
|
||||
systemPrompt?: string
|
||||
postHistoryInstructions?: string
|
||||
tags?: string[]
|
||||
alternateGreetings?: string[]
|
||||
characterBook?: Record<string, any>
|
||||
extensions?: Record<string, any>
|
||||
isPublic?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateCharacterRequest {
|
||||
name?: string
|
||||
avatar?: string
|
||||
creator?: string
|
||||
version?: string
|
||||
description?: string
|
||||
personality?: string
|
||||
scenario?: string
|
||||
firstMes?: string
|
||||
mesExample?: string
|
||||
creatorNotes?: string
|
||||
systemPrompt?: string
|
||||
postHistoryInstructions?: string
|
||||
tags?: string[]
|
||||
alternateGreetings?: string[]
|
||||
characterBook?: Record<string, any>
|
||||
extensions?: Record<string, any>
|
||||
isPublic?: boolean
|
||||
}
|
||||
|
||||
export interface GetCharacterListRequest {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
keyword?: string
|
||||
tag?: string
|
||||
isPublic?: boolean
|
||||
}
|
||||
|
||||
export interface CharacterListResponse {
|
||||
list: Character[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
// API 方法
|
||||
export const characterApi = {
|
||||
// 创建角色卡
|
||||
createCharacter: (data: CreateCharacterRequest): Promise<{ data: Character }> => {
|
||||
return apiClient.post('/app/character', data)
|
||||
},
|
||||
|
||||
// 获取角色卡列表
|
||||
getCharacterList: (params?: GetCharacterListRequest): Promise<{ data: CharacterListResponse }> => {
|
||||
return apiClient.get('/app/character', { params })
|
||||
},
|
||||
|
||||
// 获取角色卡详情
|
||||
getCharacterById: (id: number): Promise<{ data: Character }> => {
|
||||
return apiClient.get(`/app/character/${id}`)
|
||||
},
|
||||
|
||||
// 更新角色卡
|
||||
updateCharacter: (id: number, data: UpdateCharacterRequest) => {
|
||||
return apiClient.put(`/app/character/${id}`, data)
|
||||
},
|
||||
|
||||
// 删除角色卡
|
||||
deleteCharacter: (id: number) => {
|
||||
return apiClient.delete(`/app/character/${id}`)
|
||||
},
|
||||
|
||||
// 上传角色卡文件(PNG/JSON)
|
||||
uploadCharacter: (file: File): Promise<{ data: Character }> => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return apiClient.post('/app/character/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
// 导出角色卡为 JSON
|
||||
exportCharacter: (id: number) => {
|
||||
return apiClient.get(`/app/character/${id}/export`, {
|
||||
responseType: 'blob',
|
||||
})
|
||||
},
|
||||
}
|
||||
76
web-app/src/api/client.ts
Normal file
76
web-app/src/api/client.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import axios, {AxiosError, AxiosInstance} from 'axios'
|
||||
|
||||
// API 基础配置
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8888'
|
||||
|
||||
// 创建 axios 实例
|
||||
const apiClient: AxiosInstance = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
// 请求拦截器 - 添加 Token
|
||||
apiClient.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器 - 统一错误处理
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => {
|
||||
return response.data
|
||||
},
|
||||
async (error: AxiosError<any>) => {
|
||||
if (error.response) {
|
||||
const { status, data } = error.response
|
||||
|
||||
// Token 过期,尝试刷新
|
||||
if (status === 401 && data?.data?.reload) {
|
||||
const refreshToken = localStorage.getItem('refreshToken')
|
||||
if (refreshToken) {
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE_URL}/app/auth/refresh`, {
|
||||
refreshToken,
|
||||
})
|
||||
const { token, refreshToken: newRefreshToken } = response.data.data
|
||||
localStorage.setItem('token', token)
|
||||
localStorage.setItem('refreshToken', newRefreshToken)
|
||||
|
||||
// 重试原请求
|
||||
if (error.config) {
|
||||
error.config.headers.Authorization = `Bearer ${token}`
|
||||
return apiClient.request(error.config)
|
||||
}
|
||||
} catch (refreshError) {
|
||||
// 刷新失败,清除 Token 并跳转登录
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('refreshToken')
|
||||
window.location.href = '/login'
|
||||
return Promise.reject(refreshError)
|
||||
}
|
||||
} else {
|
||||
// 没有 refreshToken,跳转登录
|
||||
window.location.href = '/login'
|
||||
}
|
||||
}
|
||||
|
||||
// 返回错误信息
|
||||
return Promise.reject(data?.msg || '请求失败')
|
||||
}
|
||||
|
||||
return Promise.reject(error.message || '网络错误')
|
||||
}
|
||||
)
|
||||
|
||||
export default apiClient
|
||||
109
web-app/src/api/conversation.ts
Normal file
109
web-app/src/api/conversation.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// 简化的角色信息(用于列表)
|
||||
export interface CharacterSimple {
|
||||
id: number
|
||||
name: string
|
||||
avatar: string
|
||||
description: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// 对话列表项(轻量级)
|
||||
export interface ConversationListItem {
|
||||
id: number
|
||||
characterId: number
|
||||
title: string
|
||||
messageCount: number
|
||||
tokenCount: number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
character?: CharacterSimple
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
id: number
|
||||
userId?: number
|
||||
characterId: number
|
||||
title: string
|
||||
presetId?: number
|
||||
aiProvider: string
|
||||
model: string
|
||||
settings?: Record<string, any>
|
||||
messageCount: number
|
||||
tokenCount: number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
id: number
|
||||
conversationId: number
|
||||
role: 'user' | 'assistant' | 'system'
|
||||
content: string
|
||||
tokenCount: number
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface CreateConversationRequest {
|
||||
characterId: number
|
||||
title?: string
|
||||
presetId?: number
|
||||
aiProvider?: string
|
||||
model?: string
|
||||
settings?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface SendMessageRequest {
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface ConversationListResponse {
|
||||
list: ConversationListItem[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export interface MessageListResponse {
|
||||
list: Message[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export const conversationApi = {
|
||||
// 创建对话
|
||||
createConversation: (data: CreateConversationRequest) => {
|
||||
return apiClient.post<Conversation>('/app/conversation', data)
|
||||
},
|
||||
|
||||
// 获取对话列表
|
||||
getConversationList: (params: { page?: number; pageSize?: number }) => {
|
||||
return apiClient.get<ConversationListResponse>('/app/conversation', { params })
|
||||
},
|
||||
|
||||
// 获取对话详情
|
||||
getConversationById: (id: number) => {
|
||||
return apiClient.get<Conversation>(`/app/conversation/${id}`)
|
||||
},
|
||||
|
||||
// 删除对话
|
||||
deleteConversation: (id: number) => {
|
||||
return apiClient.delete(`/app/conversation/${id}`)
|
||||
},
|
||||
|
||||
// 获取消息列表
|
||||
getMessageList: (conversationId: number, params: { page?: number; pageSize?: number }) => {
|
||||
return apiClient.get<MessageListResponse>(`/app/conversation/${conversationId}/messages`, { params })
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
sendMessage: (conversationId: number, data: SendMessageRequest) => {
|
||||
return apiClient.post<Message>(`/app/conversation/${conversationId}/message`, data)
|
||||
},
|
||||
|
||||
// 更新对话设置
|
||||
updateConversationSettings: (conversationId: number, settings: Record<string, any>) => {
|
||||
return apiClient.put(`/app/conversation/${conversationId}/settings`, { settings })
|
||||
},
|
||||
}
|
||||
102
web-app/src/api/preset.ts
Normal file
102
web-app/src/api/preset.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// 预设接口定义
|
||||
export interface Preset {
|
||||
id: number
|
||||
userId: number
|
||||
name: string
|
||||
description: string
|
||||
isPublic: boolean
|
||||
isDefault: boolean
|
||||
temperature: number
|
||||
topP: number
|
||||
topK: number
|
||||
frequencyPenalty: number
|
||||
presencePenalty: number
|
||||
maxTokens: number
|
||||
repetitionPenalty: number
|
||||
minP: number
|
||||
topA: number
|
||||
systemPrompt: string
|
||||
stopSequences: string[]
|
||||
extensions: Record<string, any>
|
||||
useCount: number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// 创建预设请求
|
||||
export interface CreatePresetRequest {
|
||||
name: string
|
||||
description?: string
|
||||
isPublic?: boolean
|
||||
temperature?: number
|
||||
topP?: number
|
||||
topK?: number
|
||||
frequencyPenalty?: number
|
||||
presencePenalty?: number
|
||||
maxTokens?: number
|
||||
repetitionPenalty?: number
|
||||
minP?: number
|
||||
topA?: number
|
||||
systemPrompt?: string
|
||||
stopSequences?: string[]
|
||||
extensions?: Record<string, any>
|
||||
}
|
||||
|
||||
// 预设列表响应
|
||||
export interface PresetListResponse {
|
||||
list: Preset[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
// 预设 API
|
||||
export const presetApi = {
|
||||
// 创建预设
|
||||
createPreset: (data: CreatePresetRequest) => {
|
||||
return apiClient.post<Preset>('/app/preset', data)
|
||||
},
|
||||
|
||||
// 获取预设列表
|
||||
getPresetList: (params: { page?: number; pageSize?: number; keyword?: string; isPublic?: boolean }) => {
|
||||
return apiClient.get<PresetListResponse>('/app/preset', { params })
|
||||
},
|
||||
|
||||
// 根据ID获取预设
|
||||
getPresetById: (id: number) => {
|
||||
return apiClient.get<Preset>(`/app/preset/${id}`)
|
||||
},
|
||||
|
||||
// 更新预设
|
||||
updatePreset: (id: number, data: Partial<CreatePresetRequest>) => {
|
||||
return apiClient.put<Preset>(`/app/preset/${id}`, data)
|
||||
},
|
||||
|
||||
// 删除预设
|
||||
deletePreset: (id: number) => {
|
||||
return apiClient.delete(`/app/preset/${id}`)
|
||||
},
|
||||
|
||||
// 设置默认预设
|
||||
setDefaultPreset: (id: number) => {
|
||||
return apiClient.post(`/app/preset/${id}/default`)
|
||||
},
|
||||
|
||||
// 导入预设
|
||||
importPreset: (file: File) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return apiClient.post<Preset>('/app/preset/import', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
|
||||
// 导出预设
|
||||
exportPreset: (id: number) => {
|
||||
return apiClient.get(`/app/preset/${id}/export`, {
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
}
|
||||
18
web-app/src/api/upload.ts
Normal file
18
web-app/src/api/upload.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// 上传图片接口
|
||||
export interface UploadImageResponse {
|
||||
url: string
|
||||
}
|
||||
|
||||
// 上传 API
|
||||
export const uploadApi = {
|
||||
// 上传图片到 OSS
|
||||
uploadImage: (file: File) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return apiClient.post<UploadImageResponse>('/app/upload/image', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user