Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
23e63b2
feat(obs): 新增 Observability 配置结构、默认值与校验
sjwauto123 Jul 30, 2026
f074ecf
feat(obs): Agent 执行链路接入可观测性回调
sjwauto123 Jul 30, 2026
9aee45c
feat(obs): 可观测性核心模块:Recorder / 采样 / PII / 指标 / Gin 中间件
sjwauto123 Jul 30, 2026
0e1f552
feat(obs): 新增可观测性 + 反馈数据库实体与 Repository
sjwauto123 Jul 30, 2026
44ae192
feat(obs): Service 层接入反馈/追踪/指标查询与上下文打点
sjwauto123 Jul 30, 2026
9046f80
feat(obs): Chat 模块路由与 Controller 接入可观测性 API
sjwauto123 Jul 30, 2026
7cc169e
feat(obs): App 装配 + 鉴权中间件接入可观测性
sjwauto123 Jul 30, 2026
f0738b2
feat(obs): 前端:chat 类型/API 定义/响应头 trace_id 提取/useChat 扩展
sjwauto123 Jul 30, 2026
ba8b70d
feat(obs): 前端:ChatPage 消息气泡点赞点踩 + 原因标签弹窗 + Trace ID 展示
sjwauto123 Jul 30, 2026
fbb9e99
feat(obs): 前端:管理后台可观测性面板 + 统计卡 + Span 树组件
sjwauto123 Jul 30, 2026
d6eefaf
fix(obs): 后端对齐可观测性接口结构 + 权限 + 新增 Admin Traces 路由
sjwauto123 Jul 30, 2026
f832897
fix(obs): 前端可观测性联调——修正API路径、权限模型与响应结构映射
sjwauto123 Jul 30, 2026
7c89063
feat(可观测性):扩展 TraceResponse 结构,透出 Agent 任务与执行步骤详情
sjwauto123 Aug 1, 2026
2d1a825
fix(可观测性):补齐 Chat 可观测性 Agent 链路 + 修复深度模式 panic
sjwauto123 Aug 1, 2026
b265c90
feat(Agent+可观测性):接入 Eino Callback 与可观测性记录基础设施,打通 Agent 步骤记录
sjwauto123 Aug 1, 2026
0e275b2
refactor(Repository):拆分文档分块与用户记忆抽象,移除过时的 chunk/memory/tool_service/ch…
sjwauto123 Aug 1, 2026
7355d60
refactor(Service+API):统一 chat 内部命名为 chat_* 前缀,合并 model 模块路由并新增 user_m…
sjwauto123 Aug 1, 2026
d06622e
chore(App+Config):装配 Eino Adapter 与可观测性依赖,补齐 go.mod/go.sum 与配置项
sjwauto123 Aug 1, 2026
eb3aa76
feat(前端):管理台接入可观测性 Agent 链路渲染 + 鉴权/路由适配 + 文档接口调整
sjwauto123 Aug 1, 2026
087a74c
feat(全栈): 修复模型记忆功能并为已修改文件补充中文注释
sjwauto123 Aug 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 60 additions & 13 deletions design/vue/src/api/admin.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { request } from './client'
import type { ModelInfo } from '@/types/model'
import type { ModelInfo, ModelTestResult } from '@/types/model'
import type { ToolTypeInfo, ToolProviderInfo } from '@/types/tool'
import type { AdminUser } from '@/types/auth'
import type { AdminSession } from '@/types/chat'
import type { AdminSession, MetricsSnapshot, TraceSummary, ChatTraceDetail } from '@/types/chat'

// ── Admin Users ──
// ── 用户管理 ──

// 分页查询用户列表
export function adminListUsers(params: {
page: number
pageSize: number
Expand All @@ -24,6 +25,7 @@ export function adminListUsers(params: {
return request<{ list: AdminUser[]; total: number; page: number; pageSize: number; pages: number }>(`/admin/users?${query.toString()}`)
}

// 创建用户
export function adminCreateUser(data: {
username: string
email: string
Expand All @@ -34,6 +36,7 @@ export function adminCreateUser(data: {
return request<AdminUser>('/admin/users', { method: 'POST', body: data })
}

// 更新用户信息
export function adminUpdateUser(id: string, data: Partial<{
username: string
email: string
Expand All @@ -43,29 +46,35 @@ export function adminUpdateUser(id: string, data: Partial<{
return request<null>(`/admin/users/${id}`, { method: 'PUT', body: data })
}

// 删除用户
export function adminDeleteUser(id: string) {
return request<null>(`/admin/users/${id}`, { method: 'DELETE' })
}

// 重置用户密码
export function adminResetUserPassword(id: string, data: { password: string }) {
return request<null>(`/admin/users/${id}/reset-password`, { method: 'POST', body: data })
}

// ── Admin Models ──
// ── 模型管理 ──

// 获取模型列表
export function adminListModels() {
return request<{ models: ModelInfo[] }>('/models')
}

// 创建模型配置
export function adminCreateModel(data: {
provider: string
model_id: string
base_url: string
api_key: string
max_context_length: number
}) {
return request<ModelInfo>('/models', { method: 'POST', body: data })
}

// 更新模型配置
export function adminUpdateModel(
id: string,
data: Partial<{
Expand All @@ -74,37 +83,36 @@ export function adminUpdateModel(
base_url: string
api_key: string
is_enabled: boolean
max_context_length: number
}>,
) {
return request<ModelInfo>(`/models/${id}`, { method: 'PUT', body: data })
}

// 删除模型
export function adminDeleteModel(id: string) {
return request<null>(`/models/${id}`, { method: 'DELETE' })
}

// 测试模型连通性
export function adminTestModel(data: {
provider: string
model_id: string
base_url: string
api_key: string
config?: Record<string, unknown>
}) {
return request<{
success: boolean
message: string
error?: string
response_time_ms: number
details?: string
}>('/models/test', { method: 'POST', body: data })
return request<ModelTestResult>('/models/test', { method: 'POST', body: data })
}

// ── Admin Tools ──
// ── 工具管理 ──

// 获取工具类型列表
export function adminListToolTypes() {
return request<{ tool_types: ToolTypeInfo[] }>('/admin/tool-types')
}

// 创建工具类型
export function adminCreateToolType(data: {
name: string
tool_key: string
Expand All @@ -114,6 +122,7 @@ export function adminCreateToolType(data: {
return request<ToolTypeInfo>('/admin/tool-types', { method: 'POST', body: data })
}

// 更新工具类型
export function adminUpdateToolType(
id: string,
data: Partial<{
Expand All @@ -127,14 +136,17 @@ export function adminUpdateToolType(
return request<ToolTypeInfo>(`/admin/tool-types/${id}`, { method: 'PUT', body: data })
}

// 删除工具类型
export function adminDeleteToolType(id: string) {
return request<null>(`/admin/tool-types/${id}`, { method: 'DELETE' })
}

// 获取指定工具类型下的供应商列表
export function adminListToolProviders(toolTypeId: string) {
return request<{ providers: ToolProviderInfo[] }>(`/admin/tool-types/${toolTypeId}/providers`)
}

// 创建工具供应商
export function adminCreateToolProvider(
toolTypeId: string,
data: {
Expand All @@ -154,6 +166,7 @@ export function adminCreateToolProvider(
})
}

// 更新工具供应商
export function adminUpdateToolProvider(
toolTypeId: string,
providerId: string,
Expand All @@ -175,12 +188,14 @@ export function adminUpdateToolProvider(
})
}

// 删除工具供应商
export function adminDeleteToolProvider(toolTypeId: string, providerId: string) {
return request<null>(`/admin/tool-types/${toolTypeId}/providers/${providerId}`, {
method: 'DELETE',
})
}

// 测试工具连通性
export function adminTestTool(data: {
provider_type: string
provider_config?: Record<string, unknown>
Expand All @@ -197,8 +212,9 @@ export function adminTestTool(data: {
}>('/admin/tools/test', { method: 'POST', body: data })
}

// ── Admin Sessions ──
// ── 会话管理 ──

// 分页查询会话列表
export function adminListSessions(params: {
page: number
pageSize: number
Expand All @@ -213,10 +229,41 @@ export function adminListSessions(params: {
return request<{ list: AdminSession[]; total: number; page: number; pageSize: number; pages: number }>(`/admin/sessions?${query.toString()}`)
}

// 删除会话
export function adminDeleteSession(id: string) {
return request<null>(`/admin/sessions/${id}`, { method: 'DELETE' })
}

// 清理过期会话
export function adminCleanupSessions() {
return request<{ deleted: number }>('/admin/sessions/cleanup', { method: 'POST' })
}

// ── 可观测性(后台) ──

// 获取可观测性指标快照
export function adminGetObservabilityMetrics() {
return request<MetricsSnapshot>('/admin/observability/metrics')
}

// 分页查询 Trace 列表
export function adminListTraces(params: {
page?: number
pageSize?: number
session_id?: string
status?: string
rating?: number
}) {
const query = new URLSearchParams()
if (params.page !== undefined) query.set('page', String(params.page))
if (params.pageSize !== undefined) query.set('pageSize', String(params.pageSize))
if (params.session_id) query.set('session_id', params.session_id)
if (params.status) query.set('status', params.status)
if (params.rating !== undefined) query.set('rating', String(params.rating))
return request<{ traces: TraceSummary[]; total: number }>(`/admin/observability/traces?${query.toString()}`)
}

// 获取 Trace 详情
export function adminGetTrace(traceId: string) {
return request<ChatTraceDetail>(`/admin/observability/traces/${traceId}`)
}
35 changes: 33 additions & 2 deletions design/vue/src/api/chat.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { request, streamRequest } from './client'
import { request, streamRequest, StreamReaderWithMeta } from './client'
import type {
ChatSession,
CreateSessionRequest,
UpdateSessionRequest,
SendMessageRequest,
ListSessionsResponse,
ListMessagesResponse,
FeedbackRequest,
FeedbackInfo,
TraceSummary,
ChatTraceDetail,
} from '@/types/chat'

// ── Sessions ──
Expand Down Expand Up @@ -40,6 +44,33 @@ export function getMessages(sessionId: string) {
}

/** Send message via SSE streaming. Returns a ReadableStream reader. */
export function sendMessage(sessionId: string, data: SendMessageRequest, signal?: AbortSignal) {
export function sendMessage(sessionId: string, data: SendMessageRequest, signal?: AbortSignal): Promise<StreamReaderWithMeta> {
return streamRequest(`/chat/sessions/${sessionId}/messages`, data, signal)
}

// ── Feedback ──

export function submitFeedback(messageId: string, data: FeedbackRequest) {
return request<null>(`/chat/messages/${messageId}/feedback`, { method: 'POST', body: data })
}

export function listFeedbacks(params?: { page?: number; pageSize?: number; rating?: number }) {
const query = new URLSearchParams()
if (params?.page !== undefined) query.set('page', String(params.page))
if (params?.pageSize !== undefined) query.set('pageSize', String(params.pageSize))
if (params?.rating !== undefined) query.set('rating', String(params.rating))
return request<{ feedbacks: FeedbackInfo[]; total: number }>(`/chat/feedbacks?${query.toString()}`)
}

// ── Traces ──

export function getTrace(traceId: string) {
return request<ChatTraceDetail>(`/chat/traces/${traceId}`)
}

export function listSessionTraces(sessionId: string, params?: { page?: number; pageSize?: number }) {
const query = new URLSearchParams()
if (params?.page !== undefined) query.set('page', String(params.page))
if (params?.pageSize !== undefined) query.set('pageSize', String(params.pageSize))
return request<{ traces: TraceSummary[]; total: number }>(`/chat/sessions/${sessionId}/traces?${query.toString()}`)
}
30 changes: 26 additions & 4 deletions design/vue/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ export async function request<T>(
throw new Error(text || `HTTP ${res.status}`)
}

const data = await res.json()
const traceId = res.headers.get('X-Trace-ID') || undefined
const requestId = res.headers.get('X-Request-ID') || undefined

const data = (await res.json()) as ApiResponse<T>
// Server-side token invalid / auth error
if (data.code === 401 || data.code === 403) {
removeToken()
Expand All @@ -84,6 +87,9 @@ export async function request<T>(
if (data.code !== 0) {
throw new Error(data.message || '请求失败')
}
if (traceId || requestId) {
data._meta = { trace_id: traceId, request_id: requestId }
}
return data
}

Expand Down Expand Up @@ -120,7 +126,10 @@ export async function formRequest<T>(
throw new Error(text || `HTTP ${res.status}`)
}

const data = await res.json()
const traceId = res.headers.get('X-Trace-ID') || undefined
const requestId = res.headers.get('X-Request-ID') || undefined

const data = (await res.json()) as ApiResponse<T>
if (data.code === 401 || data.code === 403) {
removeToken()
routerInstance?.push('/login')
Expand All @@ -129,6 +138,9 @@ export async function formRequest<T>(
if (data.code !== 0) {
throw new Error(data.message || '请求失败')
}
if (traceId || requestId) {
data._meta = { trace_id: traceId, request_id: requestId }
}
return data
}

Expand All @@ -151,12 +163,16 @@ export async function blobRequest(path: string): Promise<Blob> {
return res.blob()
}

export type StreamReaderWithMeta = ReadableStreamDefaultReader<Uint8Array> & {
_meta?: { trace_id?: string; request_id?: string }
}

/** SSE stream request — returns a ReadableStream reader */
export async function streamRequest(
path: string,
body: unknown,
signal?: AbortSignal,
): Promise<ReadableStreamDefaultReader<Uint8Array>> {
): Promise<StreamReaderWithMeta> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
Expand All @@ -183,5 +199,11 @@ export async function streamRequest(
throw new Error(text || `HTTP ${res.status}`)
}

return res.body!.getReader()
const reader = res.body!.getReader() as StreamReaderWithMeta
const traceId = res.headers.get('X-Trace-ID') || undefined
const requestId = res.headers.get('X-Request-ID') || undefined
if (traceId || requestId) {
reader._meta = { trace_id: traceId, request_id: requestId }
}
return reader
}
Loading