fix(pkb): fix create KB and upload issues - remove simulated upload, fix department mapping, add upload modal

Fixed issues:
- Remove simulateUpload function from DashboardPage Step 3
- Map department to description field when creating KB
- Add upload modal in WorkspacePage knowledge assets tab
- Fix DocumentUpload import path (../../stores to ../stores)

Known issue: Dify API validation error during document upload (file uploaded but DB record failed, needs investigation)

Testing: KB creation works, upload dialog opens correctly
This commit is contained in:
2026-01-13 13:17:20 +08:00
parent d595037316
commit 4088275290
280 changed files with 4344 additions and 150 deletions

View File

@@ -87,6 +87,9 @@ vite.config.*.timestamp-*

View File

@@ -54,6 +54,9 @@ exec nginx -g 'daemon off;'

View File

@@ -210,6 +210,9 @@ http {

View File

@@ -43,3 +43,4 @@ apiClient.interceptors.response.use(
export default apiClient;

View File

@@ -204,3 +204,6 @@ export function useAuth(): AuthContextType {
export { AuthContext };

View File

@@ -240,3 +240,6 @@ export async function logout(): Promise<void> {
}
}

View File

@@ -6,3 +6,6 @@ export { AuthProvider, useAuth, AuthContext } from './AuthContext';
export * from './types';
export * from './api';

View File

@@ -32,3 +32,4 @@ export async function fetchUserModules(): Promise<string[]> {
return result.data || [];
}

View File

@@ -99,3 +99,6 @@ export interface AuthContextType extends AuthState {
hasRole: (...roles: UserRole[]) => boolean;
}

View File

@@ -14,10 +14,25 @@ import type {
PaginatedResponse,
ProjectStatistics
} from '../types';
import { getAccessToken } from '../../../framework/auth/api';
// API基础URL
const API_BASE_URL = '/api/v1/asl';
/**
* 获取带认证的请求头
*/
function getAuthHeaders(): HeadersInit {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
const token = getAccessToken();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return headers;
}
// 通用请求函数
async function request<T = any>(
url: string,
@@ -26,7 +41,7 @@ async function request<T = any>(
const response = await fetch(`${API_BASE_URL}${url}`, {
...options,
headers: {
'Content-Type': 'application/json',
...getAuthHeaders(),
...options?.headers,
},
});
@@ -239,7 +254,8 @@ export async function exportScreeningResults(
).toString();
const response = await fetch(
`${API_BASE_URL}/projects/${projectId}/screening/results/export?${queryString}`
`${API_BASE_URL}/projects/${projectId}/screening/results/export?${queryString}`,
{ headers: getAuthHeaders() }
);
if (!response.ok) {
@@ -409,7 +425,8 @@ export async function exportFulltextResults(
taskId: string
): Promise<Blob> {
const response = await fetch(
`${API_BASE_URL}/fulltext-screening/tasks/${taskId}/export`
`${API_BASE_URL}/fulltext-screening/tasks/${taskId}/export`,
{ headers: getAuthHeaders() }
);
if (!response.ok) {

View File

@@ -557,6 +557,9 @@ export default FulltextDetailDrawer;

View File

@@ -3,8 +3,24 @@
* 病历结构化机器人 API调用
*/
import { getAccessToken } from '../../../framework/auth/api';
const API_BASE = '/api/v1/dc/tool-b';
/**
* 获取带认证的请求头
*/
function getAuthHeaders(): HeadersInit {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
const token = getAccessToken();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return headers;
}
export interface UploadFileResponse {
fileKey: string;
url: string;
@@ -86,8 +102,16 @@ export async function uploadFile(file: File): Promise<UploadFileResponse> {
const formData = new FormData();
formData.append('file', file);
// 文件上传不设置 Content-Type让浏览器自动设置 multipart/form-data
const token = getAccessToken();
const headers: HeadersInit = {};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${API_BASE}/upload`, {
method: 'POST',
headers,
body: formData,
});
@@ -110,7 +134,7 @@ export async function uploadFile(file: File): Promise<UploadFileResponse> {
export async function healthCheck(request: HealthCheckRequest): Promise<HealthCheckResponse> {
const response = await fetch(`${API_BASE}/health-check`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: getAuthHeaders(),
body: JSON.stringify(request),
});
@@ -136,7 +160,9 @@ export async function healthCheck(request: HealthCheckRequest): Promise<HealthCh
* 获取模板列表
*/
export async function getTemplates(): Promise<Template[]> {
const response = await fetch(`${API_BASE}/templates`);
const response = await fetch(`${API_BASE}/templates`, {
headers: getAuthHeaders(),
});
if (!response.ok) {
throw new Error(`Get templates failed: ${response.statusText}`);
@@ -152,7 +178,7 @@ export async function getTemplates(): Promise<Template[]> {
export async function createTask(request: CreateTaskRequest): Promise<CreateTaskResponse> {
const response = await fetch(`${API_BASE}/tasks`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: getAuthHeaders(),
body: JSON.stringify(request),
});
@@ -168,7 +194,9 @@ export async function createTask(request: CreateTaskRequest): Promise<CreateTask
* 查询任务进度
*/
export async function getTaskProgress(taskId: string): Promise<TaskProgress> {
const response = await fetch(`${API_BASE}/tasks/${taskId}/progress`);
const response = await fetch(`${API_BASE}/tasks/${taskId}/progress`, {
headers: getAuthHeaders(),
});
if (!response.ok) {
throw new Error(`Get task progress failed: ${response.statusText}`);
@@ -182,7 +210,9 @@ export async function getTaskProgress(taskId: string): Promise<TaskProgress> {
* 获取验证网格数据
*/
export async function getTaskItems(taskId: string): Promise<{ items: ExtractionItem[] }> {
const response = await fetch(`${API_BASE}/tasks/${taskId}/items`);
const response = await fetch(`${API_BASE}/tasks/${taskId}/items`, {
headers: getAuthHeaders(),
});
if (!response.ok) {
throw new Error(`Get task items failed: ${response.statusText}`);
@@ -202,7 +232,7 @@ export async function resolveConflict(
): Promise<{ success: boolean }> {
const response = await fetch(`${API_BASE}/items/${itemId}/resolve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: getAuthHeaders(),
body: JSON.stringify({ field: fieldName, chosenValue: value }), // 🔑 后端期望field而非fieldName
});
@@ -220,7 +250,9 @@ export async function resolveConflict(
export async function exportResults(taskId: string): Promise<Blob> {
console.log('[Export] Starting export for taskId:', taskId);
const response = await fetch(`${API_BASE}/tasks/${taskId}/export`);
const response = await fetch(`${API_BASE}/tasks/${taskId}/export`, {
headers: getAuthHeaders(),
});
console.log('[Export] Response status:', response.status, response.statusText);
console.log('[Export] Response headers:', {

View File

@@ -6,7 +6,7 @@
* - AI功能生成代码、执行代码、一步到位处理、获取历史
*/
import axios from 'axios';
import apiClient from '../../../common/api/axios';
const BASE_URL = '/api/v1/dc/tool-c';
@@ -90,7 +90,7 @@ export const uploadFile = async (file: File): Promise<UploadResponse> => {
const formData = new FormData();
formData.append('file', file);
const response = await axios.post(`${BASE_URL}/sessions/upload`, formData, {
const response = await apiClient.post(`${BASE_URL}/sessions/upload`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 30000, // 30秒超时
});
@@ -102,7 +102,7 @@ export const uploadFile = async (file: File): Promise<UploadResponse> => {
* 获取Session元数据
*/
export const getSession = async (sessionId: string): Promise<SessionData> => {
const response = await axios.get(`${BASE_URL}/sessions/${sessionId}`);
const response = await apiClient.get(`${BASE_URL}/sessions/${sessionId}`);
return response.data;
};
@@ -110,7 +110,7 @@ export const getSession = async (sessionId: string): Promise<SessionData> => {
* 获取预览数据(⭐ 已改为全量加载)
*/
export const getPreviewData = async (sessionId: string): Promise<PreviewData> => {
const response = await axios.get(`${BASE_URL}/sessions/${sessionId}/preview`);
const response = await apiClient.get(`${BASE_URL}/sessions/${sessionId}/preview`);
return response.data;
};
@@ -118,7 +118,7 @@ export const getPreviewData = async (sessionId: string): Promise<PreviewData> =>
* 获取完整数据
*/
export const getFullData = async (sessionId: string): Promise<PreviewData> => {
const response = await axios.get(`${BASE_URL}/sessions/${sessionId}/full`);
const response = await apiClient.get(`${BASE_URL}/sessions/${sessionId}/full`);
return response.data;
};
@@ -126,7 +126,7 @@ export const getFullData = async (sessionId: string): Promise<PreviewData> => {
* 更新心跳延长10分钟
*/
export const updateHeartbeat = async (sessionId: string): Promise<{ success: boolean }> => {
const response = await axios.post(`${BASE_URL}/sessions/${sessionId}/heartbeat`);
const response = await apiClient.post(`${BASE_URL}/sessions/${sessionId}/heartbeat`);
return response.data;
};
@@ -134,7 +134,7 @@ export const updateHeartbeat = async (sessionId: string): Promise<{ success: boo
* 删除Session
*/
export const deleteSession = async (sessionId: string): Promise<{ success: boolean }> => {
const response = await axios.delete(`${BASE_URL}/sessions/${sessionId}`);
const response = await apiClient.delete(`${BASE_URL}/sessions/${sessionId}`);
return response.data;
};
@@ -154,7 +154,7 @@ export const generateCode = async (
messageId: string;
};
}> => {
const response = await axios.post(`${BASE_URL}/ai/generate`, {
const response = await apiClient.post(`${BASE_URL}/ai/generate`, {
sessionId,
message,
});
@@ -176,7 +176,7 @@ export const executeCode = async (
} | null;
error?: string;
}> => {
const response = await axios.post(`${BASE_URL}/ai/execute`, {
const response = await apiClient.post(`${BASE_URL}/ai/execute`, {
sessionId,
code,
messageId,
@@ -192,7 +192,7 @@ export const processMessage = async (
message: string,
maxRetries: number = 3
): Promise<AIProcessResponse> => {
const response = await axios.post(
const response = await apiClient.post(
`${BASE_URL}/ai/process`,
{
sessionId,
@@ -213,7 +213,7 @@ export const getChatHistory = async (
sessionId: string,
limit: number = 10
): Promise<ChatHistoryResponse> => {
const response = await axios.get(`${BASE_URL}/ai/history/${sessionId}?limit=${limit}`);
const response = await apiClient.get(`${BASE_URL}/ai/history/${sessionId}?limit=${limit}`);
return response.data;
};
@@ -238,7 +238,7 @@ export const getSessionStatus = async (
};
}> => {
const params = jobId ? { jobId } : {};
const response = await axios.get(`${BASE_URL}/sessions/${sessionId}/status`, { params });
const response = await apiClient.get(`${BASE_URL}/sessions/${sessionId}/status`, { params });
return response.data;
};

View File

@@ -150,6 +150,9 @@ export const useAssets = (activeTab: AssetTabType) => {

View File

@@ -140,6 +140,9 @@ export const useRecentTasks = () => {

View File

@@ -339,6 +339,9 @@ export default DropnaDialog;

View File

@@ -424,6 +424,9 @@ export default MetricTimePanel;

View File

@@ -310,6 +310,9 @@ export default PivotPanel;

View File

@@ -110,6 +110,9 @@ export function useSessionStatus({

View File

@@ -102,6 +102,9 @@ export interface DataStats {

View File

@@ -98,6 +98,9 @@ export type AssetTabType = 'all' | 'processed' | 'raw';

View File

@@ -4,6 +4,7 @@
*/
import axios from 'axios';
import { getAccessToken } from '../../../framework/auth/api';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000';
@@ -15,6 +16,18 @@ const api = axios.create({
},
});
// 请求拦截器 - 自动添加 Authorization header
api.interceptors.request.use(
(config) => {
const token = getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
/**
* 知识库类型定义
*/
@@ -221,3 +234,4 @@ export const documentSelectionApi = {

View File

@@ -102,7 +102,9 @@ const DashboardPage: React.FC = () => {
const handleCreateSubmit = async () => {
try {
const kb = await createKnowledgeBase(formData.name, formData.department);
// 后端期望 description 字段,将 department 作为描述的一部分
const description = `${formData.department || ''}科 知识库`;
const kb = await createKnowledgeBase(formData.name, description);
message.success('知识库创建成功!');
setIsModalOpen(false);
navigate(`/knowledge-base/workspace/${kb.id}`);
@@ -111,32 +113,7 @@ const DashboardPage: React.FC = () => {
}
};
// 模拟文件上传
const simulateUpload = () => {
const newFile = {
id: Math.random().toString(),
name: `New_Clinical_Protocol_v${files.length + 1}.pdf`,
size: "3.5 MB",
status: 'uploading',
progress: 0
};
setFiles(prev => [...prev, newFile]);
let progress = 0;
const interval = setInterval(() => {
progress += 2;
setFiles(prev => prev.map(f => {
if (f.id !== newFile.id) return f;
let status = 'uploading';
if (progress > 15) status = 'analyzing_layout';
if (progress > 50) status = 'extracting_table';
if (progress > 85) status = 'indexing';
if (progress >= 100) status = 'ready';
return { ...f, progress: Math.min(progress, 100), status };
}));
if (progress >= 100) clearInterval(interval);
}, 100);
};
// 删除模拟上传功能Step 3留作后续实现真实上传组件
const getStatusText = (status: string) => {
switch (status) {
@@ -350,14 +327,14 @@ const DashboardPage: React.FC = () => {
{createStep === 3 && (
<div className="max-w-3xl mx-auto space-y-6 mt-4">
<div
onClick={simulateUpload}
className="border-2 border-dashed border-gray-300 rounded-xl p-12 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-50 hover:border-blue-500 transition-all group bg-white"
className="border-2 border-dashed border-gray-300 rounded-xl p-12 flex flex-col items-center justify-center bg-white"
>
<div className="w-20 h-20 bg-blue-50 rounded-full flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
<Upload className="w-10 h-10 text-blue-600" />
<div className="w-20 h-20 bg-blue-50 rounded-full flex items-center justify-center mb-6">
<Upload className="w-10 h-10 text-blue-400" />
</div>
<p className="text-xl font-bold text-slate-700"> PDF </p>
<p className="text-sm text-slate-400 mt-2"> PDF (MinerU )</p>
<p className="text-xl font-bold text-slate-700"></p>
<p className="text-sm text-slate-500 mt-2"></p>
<p className="text-xs text-slate-400 mt-3">💡 "完成""知识资产"Tab中上传PDF文档</p>
</div>
{files.length > 0 && (

View File

@@ -289,3 +289,6 @@ export default KnowledgePage;

View File

@@ -227,3 +227,6 @@ export const useKnowledgeBaseStore = create<KnowledgeBaseState>((set, get) => ({

View File

@@ -44,3 +44,6 @@ export interface BatchTemplate {

View File

@@ -1,7 +1,7 @@
/**
* RVW模块API
*/
import axios from 'axios';
import apiClient from '../../../common/api/axios';
import type { ReviewTask, ReviewReport, ApiResponse, AgentType } from '../types';
const API_BASE = '/api/v2/rvw';
@@ -9,7 +9,7 @@ const API_BASE = '/api/v2/rvw';
// 获取任务列表
export async function getTasks(status?: string): Promise<ReviewTask[]> {
const params = status && status !== 'all' ? { status } : {};
const response = await axios.get<ApiResponse<ReviewTask[]>>(`${API_BASE}/tasks`, { params });
const response = await apiClient.get<ApiResponse<ReviewTask[]>>(`${API_BASE}/tasks`, { params });
return response.data.data || [];
}
@@ -21,7 +21,7 @@ export async function uploadManuscript(file: File, selectedAgents?: AgentType[])
formData.append('selectedAgents', JSON.stringify(selectedAgents));
}
const response = await axios.post<ApiResponse<{ taskId: string }>>(`${API_BASE}/tasks`, formData, {
const response = await apiClient.post<ApiResponse<{ taskId: string }>>(`${API_BASE}/tasks`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
@@ -34,19 +34,19 @@ export async function uploadManuscript(file: File, selectedAgents?: AgentType[])
// 获取任务详情
export async function getTask(taskId: string): Promise<ReviewTask> {
const response = await axios.get<ApiResponse<ReviewTask>>(`${API_BASE}/tasks/${taskId}`);
const response = await apiClient.get<ApiResponse<ReviewTask>>(`${API_BASE}/tasks/${taskId}`);
return response.data.data!;
}
// 获取任务报告
export async function getTaskReport(taskId: string): Promise<ReviewReport> {
const response = await axios.get<ApiResponse<ReviewReport>>(`${API_BASE}/tasks/${taskId}/report`);
const response = await apiClient.get<ApiResponse<ReviewReport>>(`${API_BASE}/tasks/${taskId}/report`);
return response.data.data!;
}
// 运行审查任务返回jobId供轮询
export async function runTask(taskId: string, agents: AgentType[]): Promise<{ taskId: string; jobId: string }> {
const response = await axios.post<ApiResponse<{ taskId: string; jobId: string }>>(`${API_BASE}/tasks/${taskId}/run`, { agents });
const response = await apiClient.post<ApiResponse<{ taskId: string; jobId: string }>>(`${API_BASE}/tasks/${taskId}/run`, { agents });
if (!response.data.success) {
throw new Error(response.data.error || '运行失败');
}
@@ -55,7 +55,7 @@ export async function runTask(taskId: string, agents: AgentType[]): Promise<{ ta
// 批量运行审查任务
export async function batchRunTasks(taskIds: string[], agents: AgentType[]): Promise<void> {
const response = await axios.post<ApiResponse<void>>(`${API_BASE}/tasks/batch/run`, { taskIds, agents });
const response = await apiClient.post<ApiResponse<void>>(`${API_BASE}/tasks/batch/run`, { taskIds, agents });
if (!response.data.success) {
throw new Error(response.data.error || '批量运行失败');
}
@@ -63,7 +63,7 @@ export async function batchRunTasks(taskIds: string[], agents: AgentType[]): Pro
// 删除任务
export async function deleteTask(taskId: string): Promise<void> {
await axios.delete(`${API_BASE}/tasks/${taskId}`);
await apiClient.delete(`${API_BASE}/tasks/${taskId}`);
}
// 轮询任务状态
@@ -129,3 +129,4 @@ export function formatTime(dateStr: string): string {
}

View File

@@ -122,3 +122,6 @@ export default function AgentModal({ visible, taskCount, onClose, onConfirm }: A
}

View File

@@ -42,3 +42,6 @@ export default function BatchToolbar({ selectedCount, onRunBatch, onClearSelecti
}

View File

@@ -65,3 +65,6 @@ export default function FilterChips({ filters, counts, onFilterChange }: FilterC
}

View File

@@ -55,3 +55,6 @@ export default function Header({ onUpload }: HeaderProps) {
}

View File

@@ -109,3 +109,6 @@ export default function ReportDetail({ report, onBack }: ReportDetailProps) {
}

View File

@@ -37,3 +37,6 @@ export default function ScoreRing({ score, size = 'medium', showLabel = true }:
}

View File

@@ -72,3 +72,6 @@ export default function Sidebar({ currentView, onViewChange, onSettingsClick }:
}

View File

@@ -14,3 +14,6 @@ export { default as ReportDetail } from './ReportDetail';
export { default as TaskDetail } from './TaskDetail';

View File

@@ -283,3 +283,6 @@ export default function Dashboard() {
}

View File

@@ -232,3 +232,6 @@
}

View File

@@ -365,3 +365,6 @@ export default function LoginPage() {
);
}

View File

@@ -335,3 +335,4 @@ const TenantListPage = () => {
export default TenantListPage;

View File

@@ -244,3 +244,4 @@ export async function fetchModuleList(): Promise<ModuleInfo[]> {
return result.data;
}

View File

@@ -53,6 +53,9 @@ export { default as Placeholder } from './Placeholder';

View File

@@ -33,6 +33,9 @@ interface ImportMeta {