diff --git a/.env.micro b/.env.micro
index 79b12e939ef8e3680f37fa5c70b8c307ca325a4c..d9d530cdcf0fa679c052bd03f4867be923a8df1b 100755
--- a/.env.micro
+++ b/.env.micro
@@ -1,3 +1,5 @@
VITE_BASE_URL=/copilot/
VITE_MICRO_ROUTER_URL=/eulercopilot/
VITE_BASE_PROXY_URL=http://10.211.55.10:8002
+
+VITE_WITCHAIND_PROXY_URL=http://10.211.55.10:9988
\ No newline at end of file
diff --git a/.env.tmp b/.env.tmp
new file mode 100644
index 0000000000000000000000000000000000000000..5f4505610e48cef3635b5a437d4f57632bd15105
--- /dev/null
+++ b/.env.tmp
@@ -0,0 +1,5 @@
+VITE_BASE_URL=/copilot/
+VITE_MICRO_ROUTER_URL=/eulercopilot/
+VITE_BASE_PROXY_URL=http://10.211.55.10:8002
+
+VITE_WITCHAIND_PROXY_URL=http://10.211.55.10:9988
diff --git a/env.d.ts b/env.d.ts
index 43df7b98287cdcdfc7b3a48603a071a08312bde7..22da69e82e65fad96211cc6979bc4e637cdfaab1 100644
--- a/env.d.ts
+++ b/env.d.ts
@@ -8,6 +8,18 @@
// PURPOSE.
// See the Mulan PSL v2 for more details.
///
+
+declare module '*.svg' {
+ const content: string
+ export default content
+}
+
+declare module '*.svg?component' {
+ import type { DefineComponent } from 'vue'
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
+
declare interface Window {
onHtmlEventDispatch: any;
eulercopilot: any;
@@ -38,4 +50,6 @@ declare interface ImportMetaEnv {
readonly VITE_MAIL_URL: string;
readonly VITE_BULLETIN_URL: string;
readonly VITE_HISS_URL: string;
+
+ readonly VITE_WITCHAIND_PROXY_URL: string;
}
diff --git a/src/apis/index.ts b/src/apis/index.ts
index 9791c928b8a2eb230a416d6b7a2b9f1efc048077..f6e27f0aa681b66b61ca1b0d8cbf64f9e49f6285 100644
--- a/src/apis/index.ts
+++ b/src/apis/index.ts
@@ -18,6 +18,7 @@ import {
modelApi,
mcpApi,
llmApi,
+ documentApi,
} from './paths';
import { workFlowApi } from './workFlow';
import { appCenterApi, promptApi, kbApi } from './appCenter';
@@ -35,6 +36,7 @@ export const api = {
...modelApi,
...mcpApi,
...llmApi,
+ ...documentApi,
...promptApi,
...kbApi,
};
diff --git a/src/apis/paths/README-file-upload.md b/src/apis/paths/README-file-upload.md
new file mode 100644
index 0000000000000000000000000000000000000000..9dbdf5ccec12115ca083b596f4c0fda20093eb77
--- /dev/null
+++ b/src/apis/paths/README-file-upload.md
@@ -0,0 +1,173 @@
+# 文件上传API使用指南
+
+## 概述
+
+该项目提供了两种方式来处理文件类型变量的上传:
+
+1. **查询参数方式**(推荐)- 符合当前后端接口设计
+2. **Form字段方式**(备用方案)- 适用于后端修改为使用Form()注解的情况
+
+## API接口说明
+
+### 1. 查询参数方式(当前使用)
+
+```typescript
+export const uploadFilesForVariable = (
+ formData: FormData,
+ sessionId: string,
+ varName: string,
+ varType: string,
+ scope: string = 'conversation'
+)
+```
+
+**特点:**
+- `scope`、`var_name`、`var_type` 作为URL查询参数传递
+- 文件通过FormData传递
+- 符合当前后端接口设计:`scope: str = "system"`
+
+**请求示例:**
+```
+POST /api/document/{conversation_id}?scope=conversation&var_name=myFile&var_type=file
+Content-Type: multipart/form-data
+
+[FormData with files]
+```
+
+### 2. Form字段方式(备用方案)
+
+```typescript
+export const uploadFilesForVariableWithFormFields = (
+ files: File | File[],
+ sessionId: string,
+ varName: string,
+ varType: string,
+ scope: string = 'conversation'
+)
+```
+
+**特点:**
+- 所有参数都作为FormData字段传递
+- 适用于后端使用`Form()`注解的情况
+
+**请求示例:**
+```
+POST /api/document/{conversation_id}
+Content-Type: multipart/form-data
+
+------WebKitFormBoundary
+Content-Disposition: form-data; name="documents"; filename="file.pdf"
+Content-Type: application/pdf
+
+[文件内容]
+------WebKitFormBoundary
+Content-Disposition: form-data; name="scope"
+
+conversation
+------WebKitFormBoundary
+Content-Disposition: form-data; name="var_name"
+
+myFile
+------WebKitFormBoundary
+Content-Disposition: form-data; name="var_type"
+
+file
+------WebKitFormBoundary--
+```
+
+## 使用配置
+
+在组件中通过 `USE_FORM_FIELDS` 常量来控制使用哪种方式:
+
+```typescript
+// 配置:选择使用查询参数还是Form字段方式
+const USE_FORM_FIELDS = false // true: 使用Form字段, false: 使用查询参数(推荐)
+```
+
+## 后端兼容性
+
+### 当前后端设计
+
+```python
+async def document_upload(
+ conversation_id: str,
+ documents: Annotated[list[UploadFile], File(...)],
+ scope: str = "system", # 默认参数 = 查询参数
+ var_name: Optional[str] = None, # 默认参数 = 查询参数
+ var_type: Optional[str] = None, # 默认参数 = 查询参数
+)
+```
+
+### 如果后端改为Form字段
+
+```python
+async def document_upload(
+ conversation_id: str,
+ documents: Annotated[list[UploadFile], File(...)],
+ scope: Annotated[str, Form()] = "system", # Form字段
+ var_name: Annotated[Optional[str], Form()] = None, # Form字段
+ var_type: Annotated[Optional[str], Form()] = None, # Form字段
+)
+```
+
+## FastAPI Form处理最佳实践
+
+### 1. 混合文件和表单数据
+
+当需要同时处理文件和表单数据时:
+
+```python
+from fastapi import FastAPI, Form, File, UploadFile
+
+@app.post("/upload/")
+async def upload_file(
+ file: UploadFile = File(...),
+ scope: str = Form(...), # 必须使用Form()
+ var_name: str = Form(...), # 必须使用Form()
+ var_type: str = Form(...) # 必须使用Form()
+):
+ return {"filename": file.filename, "scope": scope}
+```
+
+### 2. 前端FormData构造
+
+```javascript
+const formData = new FormData();
+
+// 添加文件
+formData.append('documents', file);
+
+// 添加表单字段
+formData.append('scope', 'conversation');
+formData.append('var_name', 'myFile');
+formData.append('var_type', 'file');
+
+// 发送请求
+fetch('/api/document/123', {
+ method: 'POST',
+ body: formData,
+ // 不要手动设置Content-Type,让浏览器自动设置
+});
+```
+
+### 3. 注意事项
+
+1. **Content-Type**: 不要手动设置`Content-Type: multipart/form-data`,让浏览器自动设置边界
+2. **参数类型**: Form字段只能是简单类型(string, number, boolean)
+3. **文件数组**: 多个文件使用相同的字段名`documents`
+4. **后端兼容**: 确保前端发送方式与后端接收方式匹配
+
+## 切换指南
+
+如果需要从查询参数方式切换到Form字段方式:
+
+1. 修改后端接口,添加`Form()`注解
+2. 修改前端配置:`USE_FORM_FIELDS = true`
+3. 测试文件上传功能
+
+## 调试建议
+
+1. 使用浏览器开发者工具查看Network请求
+2. 检查Content-Type和请求体格式
+3. 对比后端接口期望的参数格式
+4. 查看后端日志确认参数接收情况
\ No newline at end of file
diff --git a/src/apis/paths/conversation.ts b/src/apis/paths/conversation.ts
index 8c3057bff8a692ffa90b3a1044a9ebfc1b7d7dd0..1527ebb87d3dc6a3506cabdf95f16dd24023858d 100644
--- a/src/apis/paths/conversation.ts
+++ b/src/apis/paths/conversation.ts
@@ -257,6 +257,92 @@ export const uploadFiles = (
);
};
+// 新增:专门用于变量文件上传的API
+export const uploadFilesForVariable = (
+ formData,
+ sessionId,
+ varName,
+ varType,
+ scope = 'conversation',
+ flowId?: string
+): Promise<
+ [
+ any,
+ (
+ | FcResponse<{
+ documents: Array;
+ }>
+ | undefined
+ ),
+ ]
+> => {
+ // 方式1:URL查询参数(推荐,符合后端接口定义)
+ const params = new URLSearchParams({
+ scope,
+ var_name: varName,
+ var_type: varType
+ });
+
+ // 如果提供了flowId,添加到查询参数中
+ if (flowId) {
+ params.append('flow_id', flowId);
+ }
+
+ return post(
+ `/api/document/${sessionId}?${params.toString()}`,
+ formData,
+ {},
+ {
+ 'Content-Type': 'multipart/form-data',
+ },
+ );
+};
+
+// 新增:支持Form字段方式的文件上传API(备用方案)
+export const uploadFilesForVariableWithFormFields = (
+ files,
+ sessionId,
+ varName,
+ varType,
+ scope = 'conversation'
+): Promise<
+ [
+ any,
+ (
+ | FcResponse<{
+ documents: Array;
+ }>
+ | undefined
+ ),
+ ]
+> => {
+ // 方式2:Form字段方式
+ const formData = new FormData();
+
+ // 添加文件
+ if (Array.isArray(files)) {
+ files.forEach(file => {
+ formData.append('documents', file);
+ });
+ } else {
+ formData.append('documents', files);
+ }
+
+ // 添加变量相关参数作为Form字段
+ formData.append('scope', scope);
+ formData.append('var_name', varName);
+ formData.append('var_type', varType);
+
+ return post(
+ `/api/document/${sessionId}`,
+ formData as any,
+ {},
+ {
+ 'Content-Type': 'multipart/form-data',
+ },
+ );
+};
+
export const deleteUploadedFile = (
documentId: any,
): Promise<[any, FcResponse | undefined]> => {
@@ -275,5 +361,7 @@ export const sessionApi = {
stopGeneration: stopGeneration,
getUploadFiles,
uploadFiles,
+ uploadFilesForVariable,
+ uploadFilesForVariableWithFormFields,
deleteUploadedFile,
};
diff --git a/src/apis/paths/document.ts b/src/apis/paths/document.ts
new file mode 100644
index 0000000000000000000000000000000000000000..368f28f48a683187fed746f0921179c875da56b8
--- /dev/null
+++ b/src/apis/paths/document.ts
@@ -0,0 +1,33 @@
+// Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved.
+// licensed under the Mulan PSL v2.
+// You can use this software according to the terms and conditions of the Mulan PSL v2.
+// You may obtain a copy of Mulan PSL v2 at:
+// http://license.coscl.org.cn/MulanPSL2
+// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+// PURPOSE.
+// See the Mulan PSL v2 for more details.
+import { witchainDGet } from 'src/apis/server';
+import type { FcResponse } from 'src/apis/server';
+
+/**
+ * 获取文档解析方法列表
+ * @returns Promise<[any, FcResponse | undefined]>
+ */
+export const getParseMethodList = (): Promise<[any, FcResponse | undefined]> => {
+ return witchainDGet('/witchainD/other/parse_method');
+};
+
+/**
+ * 文档解析方法接口类型定义
+ */
+export interface ParseMethod {
+ id: string;
+ name: string;
+ description?: string;
+ supported_formats?: string[];
+}
+
+export const documentApi = {
+ getParseMethodList,
+};
\ No newline at end of file
diff --git a/src/apis/paths/index.ts b/src/apis/paths/index.ts
index ddf41fa545611e17388959db525a4f5b42c43619..2b705945a8ae70f6bd8e1c24118fea5a397308f6 100644
--- a/src/apis/paths/index.ts
+++ b/src/apis/paths/index.ts
@@ -17,3 +17,4 @@ export * from './api';
export * from './model';
export * from './mcp';
export * from './llm';
+export * from './document';
diff --git a/src/apis/server.ts b/src/apis/server.ts
index 6b059ea89987a072560ff4a2aaab52b579567b0f..cb783568932df3074ae1e6fa3f3749600d5c2982 100644
--- a/src/apis/server.ts
+++ b/src/apis/server.ts
@@ -18,7 +18,7 @@ import type {
} from 'axios';
import { ElMessage } from 'element-plus';
import { successMsg } from 'src/components/Message';
-import { getBaseProxyUrl } from 'src/utils/tools';
+import { getBaseProxyUrl, getWitchainDProxyUrl } from 'src/utils/tools';
import i18n from 'src/i18n';
export interface FcResponse {
@@ -44,69 +44,85 @@ if (import.meta.env.MODE === 'electron-production') {
});
}
-let server: ReturnType; // axios 实例
+let server: ReturnType; // 主API服务 axios 实例
+let witchainDServer: ReturnType; // WitchainD服务 axios 实例
export const initServer = async () => {
const baseURL = await getBaseProxyUrl();
+ const witchainDURL = await getWitchainDProxyUrl();
+
+ // 初始化主API服务实例
server = axios.create({
baseURL,
- // API 请求的默认前缀
timeout: 60 * 1000, // 请求超时时间
});
+
+ // 初始化WitchainD服务实例
+ witchainDServer = axios.create({
+ baseURL: witchainDURL,
+ timeout: 60 * 1000, // 请求超时时间
+ });
+
+ // 设置拦截器的通用函数
+ const setupInterceptors = (axiosInstance: ReturnType) => {
+ // request interceptor
+ axiosInstance.interceptors.request.use(
+ (
+ config: InternalAxiosRequestConfig,
+ ):
+ | InternalAxiosRequestConfig
+ | Promise> => {
+ return handleChangeRequestHeader(config);
+ },
+ (error) => {
+ return Promise.reject(error);
+ },
+ );
- // request interceptor
- server.interceptors.request.use(
- (
- config: InternalAxiosRequestConfig,
- ):
- | InternalAxiosRequestConfig
- | Promise> => {
- return handleChangeRequestHeader(config);
- },
- (error) => {
- return Promise.reject(error);
- },
- );
-
- // response interceptor
- server.interceptors.response.use(
- (response: AxiosResponse): AxiosResponse | Promise => {
- if (response.status !== 200) {
- ElMessage({
- showClose: true,
- message: response.statusText,
- icon: IconError,
- customClass: 'o-message--error',
- duration: 3000,
- });
- return Promise.reject(new Error(response.statusText));
- }
- return Promise.resolve(response);
- },
- async (error: AxiosError) => {
- if (
- error.status !== 401 &&
- error.status !== 403 &&
- error.status !== 409
- ) {
- ElMessage({
- showClose: true,
- message:
- ((error as any)?.response?.data?.message as string) ||
- error.message,
- icon: IconError,
- customClass: 'o-message--error',
- duration: 3000,
- });
- }
- if (error.status === 409) {
- // 处理错误码为409的情况
- successMsg(i18n.global.t('history.latestConversation'));
- return Promise.reject(error as any);
- }
- return await handleStatusError(error);
- },
- );
+ // response interceptor
+ axiosInstance.interceptors.response.use(
+ (response: AxiosResponse): AxiosResponse | Promise => {
+ if (response.status !== 200) {
+ ElMessage({
+ showClose: true,
+ message: response.statusText,
+ icon: IconError,
+ customClass: 'o-message--error',
+ duration: 3000,
+ });
+ return Promise.reject(new Error(response.statusText));
+ }
+ return Promise.resolve(response);
+ },
+ async (error: AxiosError) => {
+ if (
+ error.status !== 401 &&
+ error.status !== 403 &&
+ error.status !== 409
+ ) {
+ ElMessage({
+ showClose: true,
+ message:
+ ((error as any)?.response?.data?.message as string) ||
+ error.message,
+ icon: IconError,
+ customClass: 'o-message--error',
+ duration: 3000,
+ });
+ }
+ if (error.status === 409) {
+ // 处理错误码为409的情况
+ successMsg(i18n.global.t('history.latestConversation'));
+ return Promise.reject(error as any);
+ }
+ return await handleStatusError(error);
+ },
+ );
+ };
+
+ // 为两个服务实例设置拦截器
+ setupInterceptors(server);
+ setupInterceptors(witchainDServer);
};
/**
* request with get
@@ -188,3 +204,44 @@ export const del = async (
return [error as IError, undefined];
}
};
+
+// WitchainD 服务专用方法
+/**
+ * WitchainD service - request with get
+ */
+export const witchainDGet = async (
+ url: string,
+ params: IAnyObj = {},
+): Promise<[IError, FcResponse | undefined]> => {
+ try {
+ const result = await witchainDServer.get(url, { params: params });
+ return [null, result.data as FcResponse];
+ } catch (error) {
+ return [error as IError, undefined];
+ }
+};
+
+/**
+ * WitchainD service - request with post
+ */
+export const witchainDPost = async (
+ url: string,
+ data: IAnyObj = {},
+ params: IAnyObj = {},
+ headers: IAnyObj = {},
+): Promise<[IError, FcResponse | undefined]> => {
+ try {
+ const result = await witchainDServer.post(url, data, {
+ params: params,
+ headers: headers as AxiosHeaders,
+ });
+ return [null, result.data as FcResponse];
+ } catch (error) {
+ return [error as IError, undefined];
+ }
+};
+
+/**
+ * 获取 WitchainD axios 实例(用于更复杂的请求)
+ */
+export const getWitchainDServer = () => witchainDServer;
diff --git a/src/assets/svgs/audio.svg b/src/assets/svgs/audio.svg
new file mode 100644
index 0000000000000000000000000000000000000000..f5222acef003639f6f5e047b0158cbc54e221d4e
--- /dev/null
+++ b/src/assets/svgs/audio.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/svgs/document.svg b/src/assets/svgs/document.svg
new file mode 100644
index 0000000000000000000000000000000000000000..a85fc4eb0f9fb793b006850c3998fee6624981af
--- /dev/null
+++ b/src/assets/svgs/document.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/svgs/image.svg b/src/assets/svgs/image.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7e43f5498ceb2a7bf60b16aee22ab6b0a10ba958
--- /dev/null
+++ b/src/assets/svgs/image.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/svgs/other-file.svg b/src/assets/svgs/other-file.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6043296abd79c08745f2b9f45600741c392f6460
--- /dev/null
+++ b/src/assets/svgs/other-file.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/svgs/video.svg b/src/assets/svgs/video.svg
new file mode 100644
index 0000000000000000000000000000000000000000..8144b18f3f72dbab054d89fbf7289a2e78832840
--- /dev/null
+++ b/src/assets/svgs/video.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/components/VariableChooser.vue b/src/components/VariableChooser.vue
index 3f0f3bcb8bfd8ebedf2fc6679f50ac1aec5db3d4..2cc4c945df3853260b91e050fa6bcf83d1b38a46 100644
--- a/src/components/VariableChooser.vue
+++ b/src/components/VariableChooser.vue
@@ -30,6 +30,8 @@
{{ modelValue || getVariableDisplayName(selectedVariable) }}
+
+ {{ getVariableTypeDisplay(selectedVariable.var_type) }}