🎉 初始化项目

Signed-off-by: Echo <1711788888@qq.com>
This commit is contained in:
2026-02-27 21:52:00 +08:00
commit f4e166c5ee
482 changed files with 55079 additions and 0 deletions

114
web-app/src/api/aiConfig.ts Normal file
View 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
View 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)
},
}

View 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
View 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

View 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
View 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
View 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' }
})
}
}