添加交叉评查菜单页面,添加单点登录相关逻辑(待完善)
This commit is contained in:
@@ -9,3 +9,4 @@ node_modules
|
|||||||
.history
|
.history
|
||||||
|
|
||||||
logs/
|
logs/
|
||||||
|
docreview-frontend-deploy.tar.gz
|
||||||
|
|||||||
@@ -0,0 +1,295 @@
|
|||||||
|
import { UPLOAD_URL } from '../../config/api-config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从不同格式的 API 响应中提取数据
|
||||||
|
* @param responseData API 响应数据
|
||||||
|
* @returns 提取后的数据或 null
|
||||||
|
*/
|
||||||
|
function extractApiData<T>(responseData: unknown): T | null {
|
||||||
|
if (!responseData) return null;
|
||||||
|
|
||||||
|
// 格式1: { code: number, msg: string, data: T }
|
||||||
|
if (typeof responseData === 'object' && responseData !== null &&
|
||||||
|
'code' in responseData &&
|
||||||
|
'data' in responseData &&
|
||||||
|
(responseData as { data: unknown }).data) {
|
||||||
|
return (responseData as { data: T }).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式2: 直接是数据对象
|
||||||
|
return responseData as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 案卷类型枚举
|
||||||
|
export enum CaseType {
|
||||||
|
ADMINISTRATIVE_PENALTY = "administrative_penalty",
|
||||||
|
ADMINISTRATIVE_PERMIT = "administrative_permit"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 案卷类型到type_id的映射
|
||||||
|
export const CASE_TYPE_TO_TYPE_ID: Record<CaseType, number> = {
|
||||||
|
[CaseType.ADMINISTRATIVE_PENALTY]: 3, // 行政处罚
|
||||||
|
[CaseType.ADMINISTRATIVE_PERMIT]: 2, // 行政许可
|
||||||
|
};
|
||||||
|
|
||||||
|
// 文件上传响应接口
|
||||||
|
export interface CrossCheckingFileUploadResponse {
|
||||||
|
success: boolean;
|
||||||
|
result?: {
|
||||||
|
id: number;
|
||||||
|
file_name: string;
|
||||||
|
file_size: number;
|
||||||
|
file_url: string;
|
||||||
|
type_id: number;
|
||||||
|
type_description: string;
|
||||||
|
document_number: string | null;
|
||||||
|
storage_type: string;
|
||||||
|
is_test_document: boolean;
|
||||||
|
remark: string | null;
|
||||||
|
background_processing: boolean;
|
||||||
|
evaluation_level: string;
|
||||||
|
};
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传的文件接口
|
||||||
|
export interface CrossCheckingUploadedFile {
|
||||||
|
id: string; // 本地生成的唯一ID
|
||||||
|
file: File;
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
type: string;
|
||||||
|
uploadType: 'single' | 'multiple';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将文件转换为二进制数据
|
||||||
|
*/
|
||||||
|
export async function uploadFileToBinary(file: File): Promise<ArrayBuffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
if (reader.result instanceof ArrayBuffer) {
|
||||||
|
resolve(reader.result);
|
||||||
|
} else {
|
||||||
|
reject(new Error('文件读取失败'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.onerror = () => reject(new Error('文件读取失败'));
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传交叉评查文件到服务器
|
||||||
|
* @param binaryData 文件的二进制数据
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @param fileType 文件类型
|
||||||
|
* @param typeId 文档类型ID
|
||||||
|
* @param priority 优先级
|
||||||
|
* @param documentNumber 文档编号(可选)
|
||||||
|
* @param remark 备注信息(可选)
|
||||||
|
* @param isTestDocument 是否为测试文档
|
||||||
|
* @param documentId 关联的文档ID(用于附件上传)
|
||||||
|
* @param isReupload 是否为重新上传
|
||||||
|
* @returns 上传结果
|
||||||
|
*/
|
||||||
|
export async function uploadCrossCheckingDocument(
|
||||||
|
binaryData: ArrayBuffer,
|
||||||
|
fileName: string,
|
||||||
|
fileType: string,
|
||||||
|
typeId: number,
|
||||||
|
priority: string = 'normal',
|
||||||
|
documentNumber: string = '',
|
||||||
|
remark: string = '',
|
||||||
|
isTestDocument: boolean = false,
|
||||||
|
documentId: number | null = null,
|
||||||
|
isReupload: boolean = false
|
||||||
|
): Promise<{data: CrossCheckingFileUploadResponse; error?: never} | {data?: never; error: string; status?: number}> {
|
||||||
|
try {
|
||||||
|
console.log('【交叉评查上传】开始上传文档:', { fileName, fileSize: binaryData.byteLength, typeId });
|
||||||
|
|
||||||
|
// 创建FormData对象
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
// 将二进制数据转换为Blob并添加到FormData
|
||||||
|
const blob = new Blob([binaryData], { type: fileType });
|
||||||
|
formData.append('file', blob, fileName);
|
||||||
|
console.log('【交叉评查上传】Blob已创建,文件大小:', blob.size);
|
||||||
|
|
||||||
|
// 将信息添加到一个JSON对象中
|
||||||
|
const uploadInfo = {
|
||||||
|
type_id: typeId,
|
||||||
|
evaluation_level: priority,
|
||||||
|
document_number: documentNumber || null,
|
||||||
|
remark: remark || null,
|
||||||
|
is_test_document: isTestDocument,
|
||||||
|
document_id: documentId || null,
|
||||||
|
is_reupload: isReupload
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加JSON字符串到FormData
|
||||||
|
formData.append('upload_info', JSON.stringify(uploadInfo));
|
||||||
|
console.log('【交叉评查上传】FormData准备完成:', JSON.stringify(uploadInfo));
|
||||||
|
|
||||||
|
// 根据是否有documentId决定使用哪个接口
|
||||||
|
const uploadEndpoint = '/batch_upload';
|
||||||
|
const uploadUrl = UPLOAD_URL + uploadEndpoint;
|
||||||
|
console.log('【交叉评查上传】准备发送请求到服务器:', uploadUrl);
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
try {
|
||||||
|
console.log('【交叉评查上传】开始fetch请求...');
|
||||||
|
const response = await fetch(uploadUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-File-Name': encodeURIComponent(fileName)
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('【交叉评查上传】收到服务器响应:', { status: response.status, statusText: response.statusText });
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
console.error(`【交叉评查上传】上传失败 (${response.status}): ${errorText}`);
|
||||||
|
return {
|
||||||
|
error: `上传失败: ${response.status} ${response.statusText} - ${errorText}`,
|
||||||
|
status: response.status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('【交叉评查上传】开始解析JSON响应');
|
||||||
|
let responseData;
|
||||||
|
try {
|
||||||
|
responseData = await response.json();
|
||||||
|
console.log('【交叉评查上传】JSON响应解析成功:', responseData);
|
||||||
|
} catch (jsonError) {
|
||||||
|
console.error('【交叉评查上传】JSON解析失败:', jsonError);
|
||||||
|
return {
|
||||||
|
error: `解析响应JSON失败: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`,
|
||||||
|
status: 500
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractedData = extractApiData<CrossCheckingFileUploadResponse>(responseData);
|
||||||
|
console.log('【交叉评查上传】提取的数据:', extractedData);
|
||||||
|
|
||||||
|
if (!extractedData) {
|
||||||
|
console.error('【交叉评查上传】无法提取数据');
|
||||||
|
return { error: '处理上传响应失败', status: 500 };
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('【交叉评查上传】上传成功,返回数据');
|
||||||
|
return { data: extractedData as CrossCheckingFileUploadResponse };
|
||||||
|
} catch (fetchError) {
|
||||||
|
console.error('【交叉评查上传】fetch请求失败:', fetchError);
|
||||||
|
return {
|
||||||
|
error: `fetch请求错误: ${fetchError instanceof Error ? fetchError.message : '未知错误'}`,
|
||||||
|
status: 500
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('【交叉评查上传】上传过程中发生错误:', error);
|
||||||
|
return {
|
||||||
|
error: error instanceof Error ? error.message : '上传失败',
|
||||||
|
status: 500
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量上传交叉评查文件
|
||||||
|
* @param files 文件列表
|
||||||
|
* @param typeId 文档类型ID
|
||||||
|
* @param priority 优先级
|
||||||
|
* @param documentNumber 文档编号
|
||||||
|
* @param remark 备注信息
|
||||||
|
* @param isTestDocument 是否为测试文档
|
||||||
|
* @returns 上传结果列表
|
||||||
|
*/
|
||||||
|
export async function batchUploadCrossCheckingFiles(
|
||||||
|
files: CrossCheckingUploadedFile[],
|
||||||
|
typeId: number,
|
||||||
|
priority: string = 'normal',
|
||||||
|
documentNumber: string = '',
|
||||||
|
remark: string = '',
|
||||||
|
isTestDocument: boolean = false
|
||||||
|
): Promise<{
|
||||||
|
successes: Array<{file: CrossCheckingUploadedFile; result: CrossCheckingFileUploadResponse}>;
|
||||||
|
failures: Array<{file: CrossCheckingUploadedFile; error: string}>;
|
||||||
|
}> {
|
||||||
|
const successes: Array<{file: CrossCheckingUploadedFile; result: CrossCheckingFileUploadResponse}> = [];
|
||||||
|
const failures: Array<{file: CrossCheckingUploadedFile; error: string}> = [];
|
||||||
|
|
||||||
|
console.log('【交叉评查批量上传】开始批量上传文件,文件数量:', files.length);
|
||||||
|
|
||||||
|
for (const fileInfo of files) {
|
||||||
|
try {
|
||||||
|
console.log('【交叉评查批量上传】上传文件:', fileInfo.name);
|
||||||
|
|
||||||
|
// 转换文件为二进制格式
|
||||||
|
const binaryData = await uploadFileToBinary(fileInfo.file);
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
|
const result = await uploadCrossCheckingDocument(
|
||||||
|
binaryData,
|
||||||
|
fileInfo.file.name,
|
||||||
|
fileInfo.file.type,
|
||||||
|
typeId,
|
||||||
|
priority,
|
||||||
|
documentNumber,
|
||||||
|
remark,
|
||||||
|
isTestDocument,
|
||||||
|
null, // 交叉评查文件通常没有关联的文档ID
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
console.error('【交叉评查批量上传】文件上传失败:', fileInfo.name, result.error);
|
||||||
|
failures.push({
|
||||||
|
file: fileInfo,
|
||||||
|
error: result.error
|
||||||
|
});
|
||||||
|
} else if (result.data) {
|
||||||
|
console.log('【交叉评查批量上传】文件上传成功:', fileInfo.name);
|
||||||
|
successes.push({
|
||||||
|
file: fileInfo,
|
||||||
|
result: result.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('【交叉评查批量上传】处理文件时发生错误:', fileInfo.name, error);
|
||||||
|
failures.push({
|
||||||
|
file: fileInfo,
|
||||||
|
error: error instanceof Error ? error.message : '上传失败'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('【交叉评查批量上传】批量上传完成,成功:', successes.length, '失败:', failures.length);
|
||||||
|
return { successes, failures };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成唯一文件ID
|
||||||
|
* @returns 唯一ID字符串
|
||||||
|
*/
|
||||||
|
export function generateFileId(): string {
|
||||||
|
return `cross_checking_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文件大小显示
|
||||||
|
* @param bytes 字节数
|
||||||
|
* @returns 格式化后的文件大小字符串
|
||||||
|
*/
|
||||||
|
export function formatFileSize(bytes: number): string {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
@@ -132,6 +132,7 @@ export async function uploadFileToBinary(file: File): Promise<ArrayBuffer> {
|
|||||||
* @param remark 备注信息(可选)
|
* @param remark 备注信息(可选)
|
||||||
* @param isTestDocument 是否为测试文档
|
* @param isTestDocument 是否为测试文档
|
||||||
* @param documentId 关联的文档ID(用于合同附件上传)
|
* @param documentId 关联的文档ID(用于合同附件上传)
|
||||||
|
* @param isReupload 是否为重新上传
|
||||||
* @returns 上传结果
|
* @returns 上传结果
|
||||||
*/
|
*/
|
||||||
export async function uploadDocumentToServer(
|
export async function uploadDocumentToServer(
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
|
|||||||
onClick={() => onTabChange('filecompare')}
|
onClick={() => onTabChange('filecompare')}
|
||||||
type="button"
|
type="button"
|
||||||
aria-pressed={activeTab === 'filecompare'}
|
aria-pressed={activeTab === 'filecompare'}
|
||||||
>
|
>
|
||||||
<i className="ri-flip-horizontal-line"></i> 结构比对
|
<i className="ri-flip-horizontal-line"></i> 结构比对
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+12
-11
@@ -32,18 +32,19 @@ interface ApiConfig {
|
|||||||
const configs: Record<string, ApiConfig> = {
|
const configs: Record<string, ApiConfig> = {
|
||||||
// 开发环境
|
// 开发环境
|
||||||
development: {
|
development: {
|
||||||
// baseUrl: 'http://172.16.0.55:8008',
|
baseUrl: 'http://172.16.0.55:8008',
|
||||||
// baseUrl: 'http://172.16.0.81:3000',
|
// baseUrl: 'http://172.16.0.81:3000',
|
||||||
baseUrl: 'http://nas.7bm.co:3000',
|
// baseUrl: 'http://nas.7bm.co:3000',
|
||||||
documentUrl: 'http://172.16.0.81:9000/docauditai/',
|
// documentUrl: 'http://172.16.0.81:9000/docauditai/',
|
||||||
|
documentUrl: 'http://172.16.0.55:8008/docauditai/',
|
||||||
uploadUrl: 'http://172.16.0.55:8008/admin/documents',
|
uploadUrl: 'http://172.16.0.55:8008/admin/documents',
|
||||||
// uploadUrl: 'http://172.16.0.58:8008/admin/documents',
|
// uploadUrl: 'http://172.16.0.58:8008/admin/documents',
|
||||||
// uploadUrl: 'http://172.16.0.58:8008/admin/documents',
|
// uploadUrl: 'http://172.16.0.58:8008/admin/documents',
|
||||||
oauth: {
|
oauth: {
|
||||||
serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
|
serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
|
||||||
clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO', // 需要替换为实际的Client ID
|
clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO',
|
||||||
clientSecret: 'your_client_secret', // 需要替换为实际的Client Secret
|
clientSecret: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb', // 需要替换为实际的Client Secret
|
||||||
redirectUri: 'http://localhost:3000/callback', // 回调地址
|
redirectUri: 'http://10.79.97.17/', // 回调地址
|
||||||
appId: 'idaasoauth2' // 应用ID,用于登出
|
appId: 'idaasoauth2' // 应用ID,用于登出
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -65,16 +66,16 @@ const configs: Record<string, ApiConfig> = {
|
|||||||
// 生产环境
|
// 生产环境
|
||||||
production: {
|
production: {
|
||||||
// postgrest
|
// postgrest
|
||||||
baseUrl: 'http://10.79.97.16:8000',
|
baseUrl: 'http://10.79.97.17:8000',
|
||||||
// minio
|
// minio
|
||||||
documentUrl: 'http://10.76.244.156:9000/docauditai/',
|
documentUrl: 'http://10.76.244.156:9000/docauditai/',
|
||||||
// 文件上传
|
// 文件上传
|
||||||
uploadUrl: 'http://10.79.97.16:8000/admin/documents/upload',
|
uploadUrl: 'http://10.79.97.17:8000/admin/documents',
|
||||||
oauth: {
|
oauth: {
|
||||||
serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
|
serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
|
||||||
clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO', // 需要替换为实际的Client ID
|
clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO',
|
||||||
clientSecret: 'your_client_secret', // 需要替换为实际的Client Secret
|
clientSecret: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb', // 需要替换为实际的Client Secret
|
||||||
redirectUri: 'http://10.79.97.17/callback', // 回调地址
|
redirectUri: 'http://10.79.97.17/', // 回调地址
|
||||||
appId: 'idaasoauth2' // 应用ID,用于登出
|
appId: 'idaasoauth2' // 应用ID,用于登出
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+23
-19
@@ -28,16 +28,9 @@ const extractAppId = (appUrl: string): string => {
|
|||||||
return appUrl;
|
return appUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取配置值并添加调试日志
|
// 获取Dify API配置
|
||||||
const getApiUrl = () => {
|
const getDifyApiUrl = () => {
|
||||||
// 在Remix中,我们使用本地API路由作为代理,而不是直接访问Dify API
|
return getEnvVar('NEXT_PUBLIC_API_URL', 'https://api.dify.ai/v1');
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
// 客户端:使用相对路径访问本地API
|
|
||||||
return '/api';
|
|
||||||
} else {
|
|
||||||
// 服务端:也使用相对路径
|
|
||||||
return '/api';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAppId = () => {
|
const getAppId = () => {
|
||||||
@@ -46,22 +39,36 @@ const getAppId = () => {
|
|||||||
// console.log('🔧 Chat Config Debug:', {
|
// console.log('🔧 Chat Config Debug:', {
|
||||||
// rawAppId,
|
// rawAppId,
|
||||||
// extractedAppId,
|
// extractedAppId,
|
||||||
// apiUrl: getApiUrl(),
|
// difyApiUrl: getDifyApiUrl(),
|
||||||
// hasApiKey: !!getEnvVar('NEXT_PUBLIC_APP_KEY', ''),
|
// hasApiKey: !!getEnvVar('NEXT_PUBLIC_APP_KEY', ''),
|
||||||
// difyApiUrl: getEnvVar('NEXT_PUBLIC_API_URL', ''),
|
|
||||||
// });
|
// });
|
||||||
return extractedAppId;
|
return extractedAppId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 生成用户ID (模拟服务端逻辑)
|
||||||
|
const generateUserId = () => {
|
||||||
|
const appId = getAppId();
|
||||||
|
// 生成或获取会话ID (可以使用localStorage或其他方式)
|
||||||
|
let sessionId = '';
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
sessionId = localStorage.getItem('dify_session_id') || '';
|
||||||
|
if (!sessionId) {
|
||||||
|
sessionId = 'sess_' + Math.random().toString(36).substring(2, 15);
|
||||||
|
localStorage.setItem('dify_session_id', sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `user_${appId}:${sessionId}`;
|
||||||
|
};
|
||||||
|
|
||||||
// 聊天应用配置
|
// 聊天应用配置
|
||||||
export const CHAT_CONFIG = {
|
export const CHAT_CONFIG = {
|
||||||
// API相关配置 - 使用本地API路由作为代理
|
// API相关配置 - 直接使用Dify API
|
||||||
API_URL: getApiUrl(),
|
API_URL: getDifyApiUrl(),
|
||||||
APP_ID: getAppId(),
|
APP_ID: getAppId(),
|
||||||
API_KEY: getEnvVar('NEXT_PUBLIC_APP_KEY', ''),
|
API_KEY: getEnvVar('NEXT_PUBLIC_APP_KEY', ''),
|
||||||
|
|
||||||
// Dify API 配置(用于服务端)
|
// 用户生成函数
|
||||||
DIFY_API_URL: getEnvVar('NEXT_PUBLIC_API_URL', 'https://api.dify.ai/v1'),
|
generateUserId,
|
||||||
|
|
||||||
// 应用信息
|
// 应用信息
|
||||||
APP_INFO: {
|
APP_INFO: {
|
||||||
@@ -76,9 +83,6 @@ export const CHAT_CONFIG = {
|
|||||||
isShowPrompt: false,
|
isShowPrompt: false,
|
||||||
promptTemplate: 'I want you to act as a javascript console.',
|
promptTemplate: 'I want you to act as a javascript console.',
|
||||||
|
|
||||||
// API相关
|
|
||||||
API_PREFIX: '/api',
|
|
||||||
|
|
||||||
// 本地化
|
// 本地化
|
||||||
LOCALE_COOKIE_NAME: 'locale',
|
LOCALE_COOKIE_NAME: 'locale',
|
||||||
|
|
||||||
|
|||||||
@@ -113,8 +113,6 @@ export default function useConversation() {
|
|||||||
isSetToLocalStorage = true,
|
isSetToLocalStorage = true,
|
||||||
newConversationName = ''
|
newConversationName = ''
|
||||||
) => {
|
) => {
|
||||||
// console.log('🔄 设置当前会话ID:', { id, appId, isSetToLocalStorage });
|
|
||||||
|
|
||||||
doSetCurrConversationId(id);
|
doSetCurrConversationId(id);
|
||||||
|
|
||||||
if (isSetToLocalStorage && id !== '-1') {
|
if (isSetToLocalStorage && id !== '-1') {
|
||||||
@@ -130,18 +128,10 @@ export default function useConversation() {
|
|||||||
|
|
||||||
globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo));
|
globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo));
|
||||||
|
|
||||||
// console.log('💾 会话ID已保存到localStorage:', {
|
|
||||||
// appUrlKey,
|
|
||||||
// conversationId: id,
|
|
||||||
// fullStorage: conversationIdInfo
|
|
||||||
// });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存会话ID到本地存储失败:', error);
|
console.error('保存会话ID到本地存储失败:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不进行URL导航,保持单页面应用模式
|
|
||||||
// console.log('✅ 会话切换完成,当前会话ID:', id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import chatInputStyles from "~/styles/components/chat-with-llm/chat-input.css?ur
|
|||||||
import chatSidebarStyles from "~/styles/components/chat-with-llm/sidebar.css?url";
|
import chatSidebarStyles from "~/styles/components/chat-with-llm/sidebar.css?url";
|
||||||
import chatThoughtProcessStyles from "~/styles/components/chat-with-llm/thought-process.css?url";
|
import chatThoughtProcessStyles from "~/styles/components/chat-with-llm/thought-process.css?url";
|
||||||
import chatMarkdownStyles from "~/styles/components/chat-with-llm/markdown.css?url";
|
import chatMarkdownStyles from "~/styles/components/chat-with-llm/markdown.css?url";
|
||||||
|
// 导入测试文件用于调试
|
||||||
|
import "~/utils/dify-test.client";
|
||||||
|
|
||||||
export function links() {
|
export function links() {
|
||||||
return [
|
return [
|
||||||
@@ -32,6 +34,11 @@ export const meta: MetaFunction = () => {
|
|||||||
/**
|
/**
|
||||||
* 聊天主页面
|
* 聊天主页面
|
||||||
* 实现单页面应用模式,所有会话切换都在同一页面内完成
|
* 实现单页面应用模式,所有会话切换都在同一页面内完成
|
||||||
|
*
|
||||||
|
* 调试说明:
|
||||||
|
* - 打开浏览器开发者工具的控制台
|
||||||
|
* - 输入 window.testDify() 可以测试Dify API连接
|
||||||
|
* - 查看网络选项卡可以监控API请求
|
||||||
*/
|
*/
|
||||||
export default function ChatWithLLMIndex() {
|
export default function ChatWithLLMIndex() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,523 @@
|
|||||||
|
import { useState, useRef } from "react";
|
||||||
|
import { type MetaFunction, type ActionFunctionArgs } from "@remix-run/node";
|
||||||
|
import { Form, useNavigation } from "@remix-run/react";
|
||||||
|
import { UploadArea, type UploadAreaRef } from "~/components/ui/UploadArea";
|
||||||
|
import { Button } from "~/components/ui/Button";
|
||||||
|
import { messageService } from "~/components/ui/MessageModal";
|
||||||
|
import { toastService } from "~/components/ui/Toast";
|
||||||
|
import crossCheckingUploadStyles from "~/styles/pages/cross-checking-upload.css?url";
|
||||||
|
import {
|
||||||
|
CaseType,
|
||||||
|
CASE_TYPE_TO_TYPE_ID,
|
||||||
|
type CrossCheckingUploadedFile,
|
||||||
|
generateFileId,
|
||||||
|
formatFileSize,
|
||||||
|
batchUploadCrossCheckingFiles
|
||||||
|
} from "~/api/cross-checking/cross-files-upload";
|
||||||
|
|
||||||
|
export const meta: MetaFunction = () => {
|
||||||
|
return [
|
||||||
|
{ title: "交叉评查上传 - 中国烟草AI合同及卷宗审核系统" },
|
||||||
|
{ name: "description", content: "交叉评查案卷上传和任务创建" }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handle = {
|
||||||
|
breadcrumb: "交叉评查上传"
|
||||||
|
};
|
||||||
|
|
||||||
|
export function links() {
|
||||||
|
return [
|
||||||
|
{ rel: "stylesheet", href: crossCheckingUploadStyles }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤枚举
|
||||||
|
const STEPS = [
|
||||||
|
{ id: 1, label: "创建任务" },
|
||||||
|
{ id: 2, label: "创建评查小组" },
|
||||||
|
{ id: 3, label: "选择卷宗" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const caseType = formData.get("caseType") as string;
|
||||||
|
const uploadType = formData.get("uploadType") as string;
|
||||||
|
|
||||||
|
console.log("交叉评查上传:", { caseType, uploadType });
|
||||||
|
|
||||||
|
// 这里可以处理上传后的业务逻辑
|
||||||
|
// 例如创建任务记录等
|
||||||
|
return Response.json({ success: true, message: "文件上传成功" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CrossCheckingUpload() {
|
||||||
|
// 基础状态
|
||||||
|
const [caseType, setCaseType] = useState<CaseType>(CaseType.ADMINISTRATIVE_PENALTY);
|
||||||
|
const [currentStep] = useState(1);
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
|
// 上传配置状态 - 设置默认值
|
||||||
|
const [priority] = useState<string>("normal");
|
||||||
|
const [documentNumber] = useState<string>("");
|
||||||
|
const [remark] = useState<string>("");
|
||||||
|
const [isTestDocument] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// 文件管理状态
|
||||||
|
const [singleFiles, setSingleFiles] = useState<CrossCheckingUploadedFile[]>([]);
|
||||||
|
const [multipleFiles, setMultipleFiles] = useState<CrossCheckingUploadedFile[]>([]);
|
||||||
|
const [uploadType, setUploadType] = useState<'none' | 'single' | 'multiple'>('none');
|
||||||
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
|
||||||
|
// 引用
|
||||||
|
const singleUploadRef = useRef<UploadAreaRef>(null);
|
||||||
|
const multipleUploadRef = useRef<UploadAreaRef>(null);
|
||||||
|
|
||||||
|
// 获取当前typeId
|
||||||
|
const currentTypeId = CASE_TYPE_TO_TYPE_ID[caseType];
|
||||||
|
|
||||||
|
// 处理案卷类型切换
|
||||||
|
const handleCaseTypeChange = (type: CaseType) => {
|
||||||
|
if (isUploading) {
|
||||||
|
toastService.warning("上传进行中,无法切换案卷类型");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCaseType(type);
|
||||||
|
// 清空已选择的文件和重置上传方式
|
||||||
|
clearAllFiles();
|
||||||
|
console.log("案卷类型切换为:", type, "typeId:", CASE_TYPE_TO_TYPE_ID[type]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清空所有文件
|
||||||
|
const clearAllFiles = () => {
|
||||||
|
setSingleFiles([]);
|
||||||
|
setMultipleFiles([]);
|
||||||
|
setUploadType('none');
|
||||||
|
// 重置文件输入框
|
||||||
|
singleUploadRef.current?.resetFileInput();
|
||||||
|
multipleUploadRef.current?.resetFileInput();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理单案件文件选择
|
||||||
|
const handleSingleFilesSelected = (files: FileList) => {
|
||||||
|
if (uploadType === 'multiple') {
|
||||||
|
toastService.warning("已选择多案件导入方式,无法选择单案件文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validFiles: CrossCheckingUploadedFile[] = [];
|
||||||
|
let hasInvalidFiles = false;
|
||||||
|
|
||||||
|
Array.from(files).forEach(file => {
|
||||||
|
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
|
||||||
|
validFiles.push({
|
||||||
|
id: generateFileId(),
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
uploadType: 'single'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
hasInvalidFiles = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasInvalidFiles) {
|
||||||
|
messageService.error('只能上传PDF格式的文件', {
|
||||||
|
title: '文件类型错误',
|
||||||
|
confirmText: '确定',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validFiles.length > 0) {
|
||||||
|
setSingleFiles(prev => [...prev, ...validFiles]);
|
||||||
|
setUploadType('single');
|
||||||
|
console.log("选择单案件文件:", validFiles.length, "个");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理多案件文件选择
|
||||||
|
const handleMultipleFilesSelected = (files: FileList) => {
|
||||||
|
if (uploadType === 'single') {
|
||||||
|
toastService.warning("已选择单案件导入方式,无法选择多案件文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validFiles: CrossCheckingUploadedFile[] = [];
|
||||||
|
let hasInvalidFiles = false;
|
||||||
|
|
||||||
|
Array.from(files).forEach(file => {
|
||||||
|
const isZip = file.type === 'application/zip' ||
|
||||||
|
file.type === 'application/x-zip-compressed' ||
|
||||||
|
file.name.toLowerCase().endsWith('.zip');
|
||||||
|
const isRar = file.type === 'application/x-rar-compressed' ||
|
||||||
|
file.name.toLowerCase().endsWith('.rar');
|
||||||
|
const is7z = file.type === 'application/x-7z-compressed' ||
|
||||||
|
file.name.toLowerCase().endsWith('.7z');
|
||||||
|
const isTar = file.type === 'application/x-tar' ||
|
||||||
|
file.name.toLowerCase().endsWith('.tar');
|
||||||
|
|
||||||
|
if (isZip || isRar || is7z || isTar) {
|
||||||
|
validFiles.push({
|
||||||
|
id: generateFileId(),
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
uploadType: 'multiple'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
hasInvalidFiles = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasInvalidFiles) {
|
||||||
|
messageService.error('只能上传ZIP或RAR格式的压缩文件', {
|
||||||
|
title: '文件类型错误',
|
||||||
|
confirmText: '确定',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validFiles.length > 0) {
|
||||||
|
setMultipleFiles(prev => [...prev, ...validFiles]);
|
||||||
|
setUploadType('multiple');
|
||||||
|
console.log("选择多案件文件:", validFiles.length, "个");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除单个文件
|
||||||
|
const handleRemoveFile = (fileId: string, type: 'single' | 'multiple') => {
|
||||||
|
if (isUploading) {
|
||||||
|
toastService.warning("上传进行中,无法删除文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'single') {
|
||||||
|
setSingleFiles(prev => {
|
||||||
|
const newFiles = prev.filter(f => f.id !== fileId);
|
||||||
|
if (newFiles.length === 0) {
|
||||||
|
setUploadType('none');
|
||||||
|
singleUploadRef.current?.resetFileInput();
|
||||||
|
}
|
||||||
|
return newFiles;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setMultipleFiles(prev => {
|
||||||
|
const newFiles = prev.filter(f => f.id !== fileId);
|
||||||
|
if (newFiles.length === 0) {
|
||||||
|
setUploadType('none');
|
||||||
|
multipleUploadRef.current?.resetFileInput();
|
||||||
|
}
|
||||||
|
return newFiles;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清空文件列表
|
||||||
|
const handleClearFiles = (type: 'single' | 'multiple') => {
|
||||||
|
if (isUploading) {
|
||||||
|
toastService.warning("上传进行中,无法清空文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'single') {
|
||||||
|
setSingleFiles([]);
|
||||||
|
singleUploadRef.current?.resetFileInput();
|
||||||
|
} else {
|
||||||
|
setMultipleFiles([]);
|
||||||
|
multipleUploadRef.current?.resetFileInput();
|
||||||
|
}
|
||||||
|
setUploadType('none');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理完成上传
|
||||||
|
const handleCompleteUpload = async () => {
|
||||||
|
const filesToUpload = uploadType === 'single' ? singleFiles : multipleFiles;
|
||||||
|
|
||||||
|
if (filesToUpload.length === 0) {
|
||||||
|
toastService.error("请先选择要上传的文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsUploading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("开始批量上传文件:", filesToUpload.length, "个,案卷类型:", caseType, "typeId:", currentTypeId);
|
||||||
|
|
||||||
|
const result = await batchUploadCrossCheckingFiles(
|
||||||
|
filesToUpload,
|
||||||
|
currentTypeId,
|
||||||
|
priority,
|
||||||
|
documentNumber,
|
||||||
|
remark,
|
||||||
|
isTestDocument
|
||||||
|
);
|
||||||
|
|
||||||
|
const { successes, failures } = result;
|
||||||
|
|
||||||
|
if (failures.length === 0) {
|
||||||
|
// 全部成功
|
||||||
|
toastService.success(`成功上传 ${successes.length} 个文件`);
|
||||||
|
messageService.success(`文件上传完成!成功上传 ${successes.length} 个文件,现在可以进行下一步操作。`, {
|
||||||
|
title: '上传成功',
|
||||||
|
confirmText: '确定',
|
||||||
|
onConfirm: () => {
|
||||||
|
// 清空文件列表
|
||||||
|
clearAllFiles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (successes.length === 0) {
|
||||||
|
// 全部失败
|
||||||
|
toastService.error(`文件上传失败,共 ${failures.length} 个文件上传失败`);
|
||||||
|
messageService.error(`所有文件上传失败。失败原因:${failures[0].error}`, {
|
||||||
|
title: '上传失败',
|
||||||
|
confirmText: '确定',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 部分成功
|
||||||
|
toastService.warning(`部分文件上传成功:成功 ${successes.length} 个,失败 ${failures.length} 个`);
|
||||||
|
messageService.warning(
|
||||||
|
`部分文件上传完成:\n成功:${successes.length} 个文件\n失败:${failures.length} 个文件\n\n失败文件:\n${failures.map(f => `${f.file.name}: ${f.error}`).join('\n')}`,
|
||||||
|
{
|
||||||
|
title: '部分上传成功',
|
||||||
|
confirmText: '确定',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("批量上传失败:", error);
|
||||||
|
toastService.error("文件上传过程中发生错误");
|
||||||
|
messageService.error(`文件上传失败:${error instanceof Error ? error.message : '未知错误'}`, {
|
||||||
|
title: '上传失败',
|
||||||
|
confirmText: '确定',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsUploading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否可以完成
|
||||||
|
const canComplete = (singleFiles.length > 0 || multipleFiles.length > 0) && !isUploading;
|
||||||
|
const isSubmitting = navigation.state === "submitting";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 py-8">
|
||||||
|
<div className="max-w-6xl mx-auto px-4">
|
||||||
|
{/* 步骤指示器 */}
|
||||||
|
<div className="steps-indicator">
|
||||||
|
{STEPS.map((step) => (
|
||||||
|
<div
|
||||||
|
key={step.id}
|
||||||
|
className={`step-item ${step.id < currentStep ? 'completed' : ''}`}
|
||||||
|
>
|
||||||
|
<div className={`step-circle ${step.id <= currentStep ? 'active' : 'inactive'}`}>
|
||||||
|
{step.id}
|
||||||
|
</div>
|
||||||
|
<div className="step-label">{step.label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 案卷类型选择器 */}
|
||||||
|
<div className="case-type-selector">
|
||||||
|
<div className="case-type-options">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`case-type-option ${caseType === CaseType.ADMINISTRATIVE_PENALTY ? 'active' : 'inactive'}`}
|
||||||
|
onClick={() => handleCaseTypeChange(CaseType.ADMINISTRATIVE_PENALTY)}
|
||||||
|
disabled={isUploading}
|
||||||
|
>
|
||||||
|
行政处罚
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`case-type-option ${caseType === CaseType.ADMINISTRATIVE_PERMIT ? 'active' : 'inactive'}`}
|
||||||
|
onClick={() => handleCaseTypeChange(CaseType.ADMINISTRATIVE_PERMIT)}
|
||||||
|
disabled={isUploading}
|
||||||
|
>
|
||||||
|
行政许可
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 文件上传区域 */}
|
||||||
|
<Form method="post" encType="multipart/form-data">
|
||||||
|
<input type="hidden" name="caseType" value={caseType} />
|
||||||
|
<input type="hidden" name="uploadType" value={uploadType} />
|
||||||
|
|
||||||
|
<div className="upload-section">
|
||||||
|
{/* 单案件导入 */}
|
||||||
|
<div className="upload-item">
|
||||||
|
<div className="upload-item-header">
|
||||||
|
<i className="upload-item-icon ri-file-text-line"></i>
|
||||||
|
<span>单案件导入</span>
|
||||||
|
{uploadType === 'single' && singleFiles.length > 0 && (
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
icon="ri-delete-bin-line"
|
||||||
|
onClick={() => handleClearFiles('single')}
|
||||||
|
disabled={isUploading}
|
||||||
|
>
|
||||||
|
清空
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<UploadArea
|
||||||
|
ref={singleUploadRef}
|
||||||
|
onFilesSelected={handleSingleFilesSelected}
|
||||||
|
className="custom-upload-area"
|
||||||
|
accept=".pdf"
|
||||||
|
multiple={true}
|
||||||
|
icon="ri-file-upload-line"
|
||||||
|
buttonText="选择文件"
|
||||||
|
mainText="点击或拖拽文件到此区域上传"
|
||||||
|
tipText={
|
||||||
|
<div className="upload-tip-error">
|
||||||
|
请上传案件相关PDF文件
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
disabled={uploadType === 'multiple' || isUploading}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 单案件文件列表 */}
|
||||||
|
{singleFiles.length > 0 && (
|
||||||
|
<div className="mt-4 space-y-2">
|
||||||
|
<div className="text-sm font-medium text-gray-700">
|
||||||
|
已选择 {singleFiles.length} 个文件:
|
||||||
|
</div>
|
||||||
|
<div className="max-h-32 overflow-y-auto space-y-1">
|
||||||
|
{singleFiles.map((file) => (
|
||||||
|
<div key={file.id} className="flex items-center justify-between bg-gray-50 p-2 rounded">
|
||||||
|
<div className="flex items-center space-x-2 flex-1 min-w-0">
|
||||||
|
<i className="ri-file-pdf-line text-red-500"></i>
|
||||||
|
<span className="text-sm truncate">{file.name}</span>
|
||||||
|
<span className="text-xs text-gray-500">{formatFileSize(file.size)}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleRemoveFile(file.id, 'single')}
|
||||||
|
className="text-red-500 hover:text-red-700 p-1"
|
||||||
|
disabled={isUploading}
|
||||||
|
>
|
||||||
|
<i className="ri-close-line"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 多案件导入 */}
|
||||||
|
<div className="upload-item">
|
||||||
|
<div className="upload-item-header">
|
||||||
|
<i className="upload-item-icon ri-file-list-line"></i>
|
||||||
|
<span>多案件导入</span>
|
||||||
|
{uploadType === 'multiple' && multipleFiles.length > 0 && (
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
icon="ri-delete-bin-line"
|
||||||
|
onClick={() => handleClearFiles('multiple')}
|
||||||
|
disabled={isUploading}
|
||||||
|
>
|
||||||
|
清空
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<UploadArea
|
||||||
|
ref={multipleUploadRef}
|
||||||
|
onFilesSelected={handleMultipleFilesSelected}
|
||||||
|
className="custom-upload-area"
|
||||||
|
accept=".zip,.rar,.7z,.tar"
|
||||||
|
multiple={false}
|
||||||
|
icon="ri-folder-zip-line"
|
||||||
|
buttonText="选择文件"
|
||||||
|
mainText="点击或拖拽文件到此区域上传"
|
||||||
|
tipText={
|
||||||
|
<div className="upload-tip-error">
|
||||||
|
请上传多个案件作为压缩包zip、rar、7z、tar文件
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
disabled={uploadType === 'single' || isUploading}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 多案件文件列表 */}
|
||||||
|
{multipleFiles.length > 0 && (
|
||||||
|
<div className="mt-4 space-y-2">
|
||||||
|
<div className="text-sm font-medium text-gray-700">
|
||||||
|
已选择 {multipleFiles.length} 个压缩包:
|
||||||
|
</div>
|
||||||
|
<div className="max-h-32 overflow-y-auto space-y-1">
|
||||||
|
{multipleFiles.map((file) => (
|
||||||
|
<div key={file.id} className="flex items-center justify-between bg-gray-50 p-2 rounded">
|
||||||
|
<div className="flex items-center space-x-2 flex-1 min-w-0">
|
||||||
|
<i className="ri-folder-zip-line text-orange-500"></i>
|
||||||
|
<span className="text-sm truncate">{file.name}</span>
|
||||||
|
<span className="text-xs text-gray-500">{formatFileSize(file.size)}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleRemoveFile(file.id, 'multiple')}
|
||||||
|
className="text-red-500 hover:text-red-700 p-1"
|
||||||
|
disabled={isUploading}
|
||||||
|
>
|
||||||
|
<i className="ri-close-line"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 完成按钮 */}
|
||||||
|
<div className="complete-button-container">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="complete-button"
|
||||||
|
disabled={!canComplete}
|
||||||
|
onClick={handleCompleteUpload}
|
||||||
|
>
|
||||||
|
{isUploading || isSubmitting ? (
|
||||||
|
<>
|
||||||
|
<i className="ri-loader-4-line animate-spin mr-2"></i>
|
||||||
|
上传中...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"完成"
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
{/* 文件选择状态提示 */}
|
||||||
|
{!canComplete && !isUploading && (
|
||||||
|
<div className="text-center mt-4 text-gray-500 text-sm">
|
||||||
|
请至少选择一种导入方式的文件
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 上传进度提示 */}
|
||||||
|
{isUploading && (
|
||||||
|
<div className="text-center mt-4">
|
||||||
|
<div className="bg-blue-50 p-4 rounded-md border border-blue-100">
|
||||||
|
<div className="flex items-center justify-center text-blue-800 mb-2">
|
||||||
|
<i className="ri-loader-4-line animate-spin text-xl mr-2"></i>
|
||||||
|
<span className="font-medium">正在上传文件...</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-blue-700">
|
||||||
|
正在上传 {uploadType === 'single' ? singleFiles.length : multipleFiles.length} 个文件,请稍候
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "@remix-run/react";
|
||||||
import {type MetaFunction} from "@remix-run/node";
|
import { type MetaFunction } from "@remix-run/node";
|
||||||
|
|
||||||
export const meta: MetaFunction = () => {
|
export const meta: MetaFunction = () => {
|
||||||
return [
|
return [
|
||||||
{title: "文档列表 - 中国烟草AI合同及卷宗审核系统"},
|
{ title: "文档列表 - 中国烟草AI合同及卷宗审核系统" },
|
||||||
{name: "documents", content: "文档列表,新增,修改"}
|
{ name: "documents", content: "文档列表,新增,修改" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -306,7 +306,7 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="ml-1">
|
<div className="ml-1">
|
||||||
<p className="text-sm font-medium mb-0">{userRole === 'developer' ? '系统管理员' : '普通用户'}</p>
|
<p className="text-sm font-medium mb-0">{userRole === 'developer' ? '系统管理员' : '普通用户'}</p>
|
||||||
<p className="text-xs text-gray-500 mb-0">{userRole === 'developer' ? '超级管理员' : '标准权限'}</p>
|
{/* <p className="text-xs text-gray-500 mb-0">{userRole === 'developer' ? '超级管理员' : '标准权限'}</p> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* 登出操作 */}
|
{/* 登出操作 */}
|
||||||
|
|||||||
+45
-4
@@ -1,9 +1,9 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useSearchParams } from "@remix-run/react";
|
import { useSearchParams, Form } from "@remix-run/react";
|
||||||
import { type MetaFunction, type LoaderFunctionArgs, redirect } from "@remix-run/node";
|
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs, redirect } from "@remix-run/node";
|
||||||
import { OAuthClient } from "~/utils/oauth-client";
|
import { OAuthClient } from "~/utils/oauth-client";
|
||||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
import { OAUTH_CONFIG } from "~/config/api-config";
|
||||||
import { getUserSession, getSession } from "~/root";
|
import { getUserSession, getSession, createUserSession } from "~/root";
|
||||||
import styles from "~/styles/pages/login.css?url";
|
import styles from "~/styles/pages/login.css?url";
|
||||||
|
|
||||||
export const links = () => [
|
export const links = () => [
|
||||||
@@ -39,6 +39,23 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理表单提交的action函数
|
||||||
|
export async function action({ request }: ActionFunctionArgs) {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const intent = formData.get("intent");
|
||||||
|
|
||||||
|
if (intent === "temp_admin_login") {
|
||||||
|
// 获取重定向目标
|
||||||
|
const session = await getSession(request);
|
||||||
|
const redirectTo = session.get("redirectTo") || "/";
|
||||||
|
|
||||||
|
// 创建管理员会话
|
||||||
|
return createUserSession(true, 'developer', redirectTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const error = searchParams.get("error");
|
const error = searchParams.get("error");
|
||||||
@@ -131,10 +148,34 @@ export default function Login() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 临时管理员登录区域 */}
|
||||||
|
<div className="temp-login-section">
|
||||||
|
<div className="section-divider">
|
||||||
|
<span>或</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form method="post" className="temp-login-form">
|
||||||
|
<input type="hidden" name="intent" value="temp_admin_login" />
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="temp-admin-login-button"
|
||||||
|
>
|
||||||
|
<i className="ri-admin-line"></i>
|
||||||
|
临时管理员登录
|
||||||
|
</button>
|
||||||
|
<div className="temp-login-tips">
|
||||||
|
<p>
|
||||||
|
<i className="ri-alert-line"></i>
|
||||||
|
仅供开发测试使用,将以管理员身份登录
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="login-footer">
|
<div className="login-footer">
|
||||||
<p>© 2024 中国烟草 版权所有</p>
|
<p>© 2025 中国烟草 版权所有</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+660
-236
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,275 @@
|
|||||||
|
/* 交叉评查上传页面样式 */
|
||||||
|
|
||||||
|
/* 步骤指示器样式 */
|
||||||
|
.steps-indicator {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 2rem 0 3rem 0;
|
||||||
|
position: relative;
|
||||||
|
max-width: 600px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item:not(:last-child)::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 25px;
|
||||||
|
left: 75px;
|
||||||
|
width: calc(100% - 50px);
|
||||||
|
height: 2px;
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item.completed:not(:last-child)::after {
|
||||||
|
background-color: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-circle {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-circle.active {
|
||||||
|
background-color: #059669;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 0 0 4px rgba(5, 150, 105, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-circle.inactive {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #374151;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 案卷类型选择器样式 */
|
||||||
|
.case-type-selector {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-type-options {
|
||||||
|
display: flex;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-type-option {
|
||||||
|
padding: 0.75rem 2rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-type-option.active {
|
||||||
|
background-color: #059669;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-type-option.inactive {
|
||||||
|
color: #6b7280;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-type-option:hover:not(.active) {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 上传区域容器样式 */
|
||||||
|
.upload-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 3rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-item-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
color: #374151;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-item-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 覆盖UploadArea组件的默认样式 */
|
||||||
|
.custom-upload-area.upload-area {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 350px;
|
||||||
|
min-height: 200px;
|
||||||
|
border: 2px dashed #d1d5db !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
background-color: #fafafa !important;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
padding: 2rem 1.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-upload-area.upload-area:hover {
|
||||||
|
border-color: #059669 !important;
|
||||||
|
background-color: #f0fdf4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-upload-area.upload-area.dragover {
|
||||||
|
border-color: #059669 !important;
|
||||||
|
background-color: #f0fdf4 !important;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-upload-area .upload-icon {
|
||||||
|
font-size: 2.5rem !important;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-upload-area .upload-text {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-tip-error {
|
||||||
|
color: #dc2626;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 完成按钮样式 */
|
||||||
|
.complete-button-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complete-button {
|
||||||
|
background-color: #059669;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.875rem 3rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.complete-button:hover {
|
||||||
|
background-color: #047857;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.complete-button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.complete-button:disabled {
|
||||||
|
background-color: #9ca3af;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.upload-section {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-indicator {
|
||||||
|
margin: 1.5rem 0 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-circle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-type-option {
|
||||||
|
padding: 0.625rem 1.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-upload-area.upload-area {
|
||||||
|
max-width: 100%;
|
||||||
|
min-height: 180px;
|
||||||
|
padding: 1.5rem 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complete-button {
|
||||||
|
padding: 0.75rem 2.5rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item:not(:last-child)::after {
|
||||||
|
left: 60px;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -208,3 +208,113 @@
|
|||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 临时管理员登录样式 */
|
||||||
|
.temp-login-section {
|
||||||
|
margin-top: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-divider {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-divider::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-divider span {
|
||||||
|
background-color: white;
|
||||||
|
color: #9ca3af;
|
||||||
|
padding: 0 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-login-form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-admin-login-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.875rem 1.5rem;
|
||||||
|
background: linear-gradient(135deg, #f59e0b 0%, #f97316 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-admin-login-button:hover {
|
||||||
|
background: linear-gradient(135deg, #f97316 0%, #f59e0b 100%);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-admin-login-button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-admin-login-button i {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-login-tips {
|
||||||
|
text-align: center;
|
||||||
|
color: #f59e0b;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-login-tips p {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-login-tips i {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗色主题下的临时登录样式 */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.section-divider::before {
|
||||||
|
background-color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-divider span {
|
||||||
|
background-color: #1f2937;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { CHAT_CONFIG } from '../config/chat';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试Dify API连接
|
||||||
|
* 这个文件可以用来调试和测试前端直接调用Dify API的功能
|
||||||
|
*/
|
||||||
|
export const testDifyConnection = async () => {
|
||||||
|
console.log('🔧 Dify Configuration:', {
|
||||||
|
apiUrl: CHAT_CONFIG.API_URL,
|
||||||
|
appId: CHAT_CONFIG.APP_ID,
|
||||||
|
hasApiKey: !!CHAT_CONFIG.API_KEY,
|
||||||
|
apiKeyPreview: CHAT_CONFIG.API_KEY ? `${CHAT_CONFIG.API_KEY.substring(0, 10)}...` : 'No API Key',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!CHAT_CONFIG.API_URL || !CHAT_CONFIG.APP_ID || !CHAT_CONFIG.API_KEY) {
|
||||||
|
console.error('❌ Dify配置不完整,请检查环境变量');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = CHAT_CONFIG.generateUserId();
|
||||||
|
console.log('👤 Generated User ID:', user);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 测试获取应用参数
|
||||||
|
console.log('📋 测试获取应用参数...');
|
||||||
|
const response = await fetch(`${CHAT_CONFIG.API_URL}/parameters`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error('❌ 获取应用参数失败:', response.status, response.statusText);
|
||||||
|
const errorText = await response.text();
|
||||||
|
console.error('错误详情:', errorText);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('✅ 成功获取应用参数:', data);
|
||||||
|
|
||||||
|
// 测试获取会话列表
|
||||||
|
console.log('💬 测试获取会话列表...');
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
user,
|
||||||
|
limit: '10',
|
||||||
|
first_id: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const conversationsResponse = await fetch(`${CHAT_CONFIG.API_URL}/conversations?${params}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!conversationsResponse.ok) {
|
||||||
|
console.error('❌ 获取会话列表失败:', conversationsResponse.status, conversationsResponse.statusText);
|
||||||
|
const errorText = await conversationsResponse.text();
|
||||||
|
console.error('错误详情:', errorText);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conversationsData = await conversationsResponse.json();
|
||||||
|
console.log('✅ 成功获取会话列表:', conversationsData);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 测试Dify连接时发生错误:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在浏览器控制台中可以调用这个函数进行测试
|
||||||
|
* 使用方法:
|
||||||
|
* 1. 打开浏览器控制台
|
||||||
|
* 2. 输入: window.testDify()
|
||||||
|
* 3. 查看输出结果
|
||||||
|
*/
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
(window as any).testDify = testDifyConnection;
|
||||||
|
}
|
||||||
Generated
+1
@@ -6,6 +6,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "remix_docreview",
|
"name": "remix_docreview",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.6.1",
|
||||||
"@codemirror/lang-javascript": "^6.2.3",
|
"@codemirror/lang-javascript": "^6.2.3",
|
||||||
"@codemirror/theme-one-dark": "^6.1.2",
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@react-pdf-viewer/core": "^3.12.0",
|
"@react-pdf-viewer/core": "^3.12.0",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"typecheck": "tsc"
|
"typecheck": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.6.1",
|
||||||
"@codemirror/lang-javascript": "^6.2.3",
|
"@codemirror/lang-javascript": "^6.2.3",
|
||||||
"@codemirror/theme-one-dark": "^6.1.2",
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@react-pdf-viewer/core": "^3.12.0",
|
"@react-pdf-viewer/core": "^3.12.0",
|
||||||
|
|||||||
Generated
+12337
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -44,6 +44,6 @@ export default defineConfig({
|
|||||||
// 防止依赖预构建时触发页面刷新导致路由中断
|
// 防止依赖预构建时触发页面刷新导致路由中断
|
||||||
force: false,
|
force: false,
|
||||||
// 预构建这些依赖,避免首次加载时出现重新构建
|
// 预构建这些依赖,避免首次加载时出现重新构建
|
||||||
include: ['react-pdf', 'pdfjs-dist', 'dayjs', '@remix-run/node', 'react-dom', 'axios', 'dayjs/plugin/utc', 'react-router-dom', 'jszip', 'ahooks', 'antd', 'immer', '@ant-design/icons', 'react-markdown', 'remark-math', 'remark-breaks', 'rehype-katex','remark-gfm'],
|
include: ['react-pdf', 'pdfjs-dist', 'dayjs', '@remix-run/node', 'react-dom', 'axios', 'dayjs/plugin/utc', '@remix-run/react', 'react-router-dom', 'jszip', 'ahooks', 'antd', 'immer', '@ant-design/icons', 'react-markdown', 'remark-math', 'remark-breaks', 'rehype-katex', 'remark-gfm'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user