Add Vue frontend application

This commit is contained in:
2026-03-21 10:13:35 +08:00
parent 6ffa07adde
commit b40a6ebd3a
56 changed files with 10884 additions and 0 deletions

34
frontend/src/api/agent.ts Normal file
View File

@@ -0,0 +1,34 @@
import api from './index'
export interface AgentStats {
agent_id: string
call_count: number
current_task: string | null
status: 'active' | 'idle' | 'disabled'
}
export interface AgentConfig {
id: string
name: string
role: string
description: string
system_prompt: string
enabled: boolean
}
export const agentApi = {
async getStats(): Promise<AgentStats[]> {
const res = await api.get('/api/agents/stats')
return res.data
},
async getConfig(id: string): Promise<AgentConfig> {
const res = await api.get(`/api/agents/config/${id}`)
return res.data
},
async updateConfig(id: string, data: Partial<AgentConfig>): Promise<AgentConfig> {
const res = await api.put(`/api/agents/config/${id}`, data)
return res.data
},
}

View File

@@ -0,0 +1,44 @@
import api from './index'
export interface Message {
id: string
role: 'user' | 'assistant'
content: string
model?: string
tokens_used?: number
created_at: string
}
export interface Conversation {
id: string
title?: string
message_count: number
created_at: string
updated_at: string
}
export const conversationApi = {
list() {
return api.get<Conversation[]>('/api/conversations')
},
create(title?: string) {
return api.post<Conversation>('/api/conversations', { title })
},
getMessages(conversationId: string) {
return api.get<Message[]>(`/api/conversations/${conversationId}`)
},
delete(conversationId: string) {
return api.delete(`/api/conversations/${conversationId}`)
},
chat(message: string, conversationId?: string, fileIds: string[] = []) {
return api.post('/api/conversations/chat', {
message,
conversation_id: conversationId,
file_ids: fileIds,
})
},
}

View File

@@ -0,0 +1,73 @@
import api from './index'
export interface Document {
id: string
title: string
filename: string
file_type: string
file_size: number
summary?: string
chunk_count: number
is_indexed: boolean
folder_id?: string | null
created_at: string
}
export interface SearchResult {
chunk_id: string
document_id: string
document_title: string
content: string
score: number
metadata_?: string
prev_chunk?: string
next_chunk?: string
}
export interface UploadResponse {
id: string
title: string
chunk_count: number
status: string
}
export const documentApi = {
list(folderId?: string | null) {
return api.get<Document[]>('/api/documents', {
params: folderId ? { folder_id: folderId } : undefined,
})
},
upload(file: File, folderId?: string | null) {
const formData = new FormData()
formData.append('file', file)
if (folderId) {
formData.append('folder_id', folderId)
}
return api.post<UploadResponse>('/api/documents/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
},
get(id: string) {
return api.get<Document>(`/api/documents/${id}`)
},
getChunks(id: string) {
return api.get<any[]>(`/api/documents/${id}/chunks`)
},
delete(id: string) {
return api.delete(`/api/documents/${id}`)
},
search(query: string, top_k = 5, mode = 'hybrid') {
return api.get<SearchResult[]>('/api/documents/search', {
params: { query, top_k, mode },
})
},
getContent(id: string) {
return api.get<string>(`/api/documents/${id}/content`)
},
}

View File

@@ -0,0 +1,47 @@
import api from './index'
export interface FolderCreate {
name: string
parent_id?: string | null
}
export interface FolderUpdate {
name: string
}
export interface FolderItem {
id: string
name: string
parent_id: string | null
created_at: string
updated_at: string
}
export interface FolderTree {
id: string
name: string
parent_id: string | null
children: FolderTree[]
}
export const folderApi = {
// 获取文件夹树
getTree() {
return api.get<FolderTree[]>('/api/folders')
},
// 创建文件夹
create(data: FolderCreate) {
return api.post('/api/folders', data)
},
// 重命名文件夹
rename(id: string, data: FolderUpdate) {
return api.put(`/api/folders/${id}`, data)
},
// 删除文件夹
delete(id: string) {
return api.delete(`/api/folders/${id}`)
},
}

49
frontend/src/api/forum.ts Normal file
View File

@@ -0,0 +1,49 @@
import api from './index'
export interface ForumPost {
id: string
user_id: string
title: string
content: string
category?: string
is_executed: boolean
execution_result?: string
reply_count: number
created_at: string
}
export interface ForumReply {
id: string
post_id: string
user_id?: string
agent_id?: string
content: string
is_ai_reply: boolean
created_at: string
}
export const forumApi = {
listPosts() {
return api.get<ForumPost[]>('/api/forum/posts')
},
createPost(data: { title: string; content: string; category?: string }) {
return api.post<ForumPost>('/api/forum/posts', data)
},
getPost(id: string) {
return api.get<ForumPost>(`/api/forum/posts/${id}`)
},
deletePost(id: string) {
return api.delete(`/api/forum/posts/${id}`)
},
listReplies(postId: string) {
return api.get<ForumReply[]>(`/api/forum/posts/${postId}/replies`)
},
createReply(postId: string, content: string) {
return api.post<ForumReply>(`/api/forum/posts/${postId}/replies`, { content })
},
}

56
frontend/src/api/graph.ts Normal file
View File

@@ -0,0 +1,56 @@
import api from './index'
export interface KGNode {
id: string
name: string
type: string
description?: string
importance: number
created_at?: string
}
export interface KGEdge {
id: string
source: string
target: string
relation: string
weight?: number
}
export interface GraphData {
nodes: KGNode[]
edges: KGEdge[]
stats: {
node_count: number
edge_count: number
}
}
export const graphApi = {
get() {
return api.get<GraphData>('/api/graph')
},
build() {
return api.post('/api/graph/build')
},
getEntityContext(entity: string) {
return api.get<{ context: string }>(`/api/graph/entity/${encodeURIComponent(entity)}`)
},
getSummary() {
return api.get<{ summary: string }>('/api/graph/summary')
},
getNeighbors(nodeId: string, depth = 1) {
return api.get<{ nodes: KGNode[]; edges: KGEdge[] }>(
`/api/graph/neighbors/${nodeId}`,
{ params: { depth } }
)
},
deleteNode(nodeId: string) {
return api.delete(`/api/graph/nodes/${nodeId}`)
},
}

29
frontend/src/api/index.ts Normal file
View File

@@ -0,0 +1,29 @@
import axios from 'axios'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000',
timeout: 30000,
})
// 请求拦截器:添加 Token
api.interceptors.request.use((config) => {
const token = localStorage.getItem('access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器:处理错误
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
localStorage.removeItem('access_token')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
export default api

View File

@@ -0,0 +1,71 @@
import api from './index'
export type LLMProvider = 'openai' | 'claude' | 'ollama' | 'deepseek' | 'custom'
export type LLMType = 'chat' | 'vlm' | 'embedding' | 'rerank'
export interface LLMModelConfig {
name: string // 模型名称/别名
provider: LLMProvider
model: string
base_url: string
api_key: string
enabled: boolean // 是否启用
}
export interface LLMConfig {
chat?: LLMModelConfig[]
vlm?: LLMModelConfig[]
embedding?: LLMModelConfig[]
rerank?: LLMModelConfig[]
}
export interface SchedulerConfig {
daily_plan_time?: string
forum_scan_interval_minutes?: number
todo_ai_generate_time?: string
enabled?: boolean
}
export interface ProfileUpdate {
full_name?: string
password?: string
current_password?: string
}
export interface SettingsResponse {
profile: {
id: string
email: string
full_name: string
created_at: string
}
llm_config: LLMConfig
scheduler_config: SchedulerConfig
}
export const settingsApi = {
// 获取设置
get() {
return api.get<SettingsResponse>('/api/settings')
},
// 更新资料
updateProfile(data: ProfileUpdate) {
return api.put('/api/settings/profile', data)
},
// 更新 LLM 配置
updateLLM(config: Partial<LLMConfig>) {
return api.put('/api/settings/llm', config)
},
// 测试 LLM 连接
testLLM(data: { type: LLMType } & Omit<LLMModelConfig, 'name' | 'enabled'>) {
return api.post('/api/settings/llm/test', data)
},
// 更新定时任务配置
updateScheduler(config: Partial<SchedulerConfig>) {
return api.put('/api/settings/scheduler', config)
},
}

17
frontend/src/api/stats.ts Normal file
View File

@@ -0,0 +1,17 @@
import axios from '@/api'
export const getSystemHealth = () => axios.get('/stats/system')
export const getConversationStats = (days = 30) =>
axios.get('/stats/conversations', { params: { days } })
export const getKnowledgeStats = (days = 30) =>
axios.get('/stats/knowledge', { params: { days } })
export const getKanbanStats = (days = 30) =>
axios.get('/stats/kanban', { params: { days } })
export const getCommunityStats = (days = 30) =>
axios.get('/stats/community', { params: { days } })
export const getPersonalInsights = () => axios.get('/stats/insights')

35
frontend/src/api/task.ts Normal file
View File

@@ -0,0 +1,35 @@
import api from './index'
export type TaskStatus = 'todo' | 'in_progress' | 'done' | 'cancelled'
export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'
export interface Task {
id: string
title: string
description?: string
status: TaskStatus
priority: TaskPriority
due_date?: string
completed_at?: string
tags?: string
created_at: string
updated_at: string
}
export const taskApi = {
list(status?: TaskStatus) {
return api.get<Task[]>('/api/tasks', { params: status ? { status } : {} })
},
create(data: { title: string; description?: string; priority?: TaskPriority; due_date?: string }) {
return api.post<Task>('/api/tasks', data)
},
update(id: string, data: Partial<Task>) {
return api.patch<Task>(`/api/tasks/${id}`, data)
},
delete(id: string) {
return api.delete(`/api/tasks/${id}`)
},
}

59
frontend/src/api/todo.ts Normal file
View File

@@ -0,0 +1,59 @@
import api from './index'
export type TodoSource = 'ai_kanban' | 'ai_chat' | 'manual'
export interface Todo {
id: string
title: string
is_completed: boolean
source: TodoSource
source_detail: string | null
todo_date: string
completed_at: string | null
created_at: string
updated_at: string
}
export interface TodoListResponse {
items: Todo[]
total: number
page: number
page_size: number
}
export interface TodoSummary {
date: string
total: number
completed: number
pending: number
}
export const todoApi = {
list(date?: string, page = 1, pageSize = 50) {
return api.get<TodoListResponse>('/api/todos', {
params: date ? { date_str: date, page, page_size: pageSize } : { page, page_size: pageSize },
})
},
create(title: string) {
return api.post<Todo>('/api/todos', { title })
},
update(id: string, data: { title?: string; is_completed?: boolean }) {
return api.patch<Todo>(`/api/todos/${id}`, data)
},
delete(id: string) {
return api.delete(`/api/todos/${id}`)
},
aiGenerate() {
return api.post<TodoListResponse>('/api/todos/ai-generate')
},
summary(date?: string) {
return api.get<TodoSummary>('/api/todos/summary', {
params: date ? { date_str: date } : {},
})
},
}