Fix secret (#6738)

* fix: nosql inject

* fix: nosql

* fix: ts

* doc

* fix: update feedbacksession
This commit is contained in:
Archer
2026-04-10 13:58:10 +08:00
committed by GitHub
parent 6173a74510
commit fc6953fcb3
26 changed files with 498 additions and 422 deletions
-1
View File
@@ -142,7 +142,6 @@ FastGPT 项目特定的代码规范和约定:
- **TypeScript 问题**: any 类型滥用、类型定义不完整、不安全断言 - **TypeScript 问题**: any 类型滥用、类型定义不完整、不安全断言
- **异步错误处理**: 未处理 Promise、错误信息丢失、静默失败 - **异步错误处理**: 未处理 Promise、错误信息丢失、静默失败
- **React 性能**: 不必要的重渲染、渲染中创建对象、缺少 memoization - **React 性能**: 不必要的重渲染、渲染中创建对象、缺少 memoization
- **工作流节点**: isEntry 未重置、交互历史未清理、白名单遗漏
- **安全漏洞**: 注入攻击、XSS、文件上传漏洞 - **安全漏洞**: 注入攻击、XSS、文件上传漏洞
📖 **详细清单**: [common-issues-checklist.md](./common-issues-checklist.md) 📖 **详细清单**: [common-issues-checklist.md](./common-issues-checklist.md)
@@ -7,10 +7,8 @@
- [1. TypeScript 问题](#1-typescript-问题) - [1. TypeScript 问题](#1-typescript-问题)
- [2. 异步错误处理问题](#2-异步错误处理问题) - [2. 异步错误处理问题](#2-异步错误处理问题)
- [3. React 性能问题](#3-react-性能问题) - [3. React 性能问题](#3-react-性能问题)
- [4. 工作流节点问题](#4-工作流节点问题) - [4. 安全漏洞问题](#4-安全漏洞问题)
- [5. 安全漏洞问题](#5-安全漏洞问题) - [5. 环境配置问题](#5-环境配置问题)
- [6. 代码重复问题](#6-代码重复问题)
- [7. 环境配置问题](#7-环境配置问题)
--- ---
@@ -377,169 +375,96 @@ const ExpensiveList = ({ items }: { items: Item[] }) => {
--- ---
## 4. 工作流节点问题
### 🔴 4.1 isEntry 标志未重置 ## 4. 安全漏洞问题
**问题识别**: ### 🔴 4.1 NoSQL 注入 (接口入参风险)
- 交互节点执行逻辑中第二阶段没有设置 `node.isEntry = false`
- 节点可能重复执行
- 交互节点功能异常
**快速修复**: **核心风险**: MongoDB 查询操作符 (`$gt``$where``$regex``$ne` 等) 可以通过 HTTP 请求体注入。当接口直接将入参透传到数据库查询时,攻击者可以构造恶意对象绕过权限校验或泄露数据。
```typescript
// ❌ 问题代码
export const dispatchInteractiveNode = async (props: Props) => {
const { isEntry } = props.node;
if (!isEntry) { **典型攻击场景**:
return { interactive: { ... } }; ```
} // 攻击者发送的请求体
POST /api/login
// 处理用户输入 { "username": { "$gt": "" }, "password": { "$gt": "" } }
return { data: { ... } }; // → MongoDB 查询变为 { username: { $gt: "" }, password: { $gt: "" } }
// 忘记重置 isEntry! // → 匹配所有用户,绕过密码校验
};
// ✅ 修复方案
export const dispatchInteractiveNode = async (props: Props) => {
const { node, lastInteractive } = props;
const { isEntry } = node;
// 第一阶段: 返回交互请求
if (!isEntry || lastInteractive?.type !== 'interactiveType') {
return {
[DispatchNodeResponseKeyEnum.interactive]: {
type: 'interactiveType',
params: { /* ... */ }
}
};
}
// 第二阶段: 处理用户输入
node.isEntry = false; // 🔴 必须: 重置入口标志
return {
data: { /* ... */ },
[DispatchNodeResponseKeyEnum.rewriteHistories]: histories.slice(0, -2)
};
};
``` ```
**审查建议**: 🔴 严重问题,必须修复
---
### 🔴 4.2 交互历史未清理
**问题识别**: **问题识别**:
- 交互节点返回值中没有 `rewriteHistories` - 接口入参未经 zod/类型校验直接传入查询条件
- 用户会看到交互过程中产生的临时消息 - 查询字段类型声明为 `any``object`
- 使用 `req.body.xxx` 直接拼入 `find()``findOne()``updateOne()`
- 动态构建查询对象时未限制字段类型为原始值
**快速修复**: **高危模式**:
```typescript ```typescript
// ❌ 问题代码 // ❌ 危险: 入参直接作为查询字段值
export const dispatchInteractiveNode = async (props: Props) => { const { username, password } = req.body;
// 处理用户输入后 await db.users.findOne({ username, password });
return {
data: { result: userInput }
// 忘记清理交互对话的历史记录
};
};
// ✅ 修复方案 // ❌ 危险: 对象字段透传进查询
export const dispatchInteractiveNode = async (props: Props) => { async function getUser({ filter }: { filter: object }) {
const { histories } = props; return db.users.findOne(filter); // filter 可以是任意操作符
// 处理用户输入后
return {
data: { result: userInput },
// 移除交互对话的历史记录 (用户问题 + 系统响应 = 2条)
[DispatchNodeResponseKeyEnum.rewriteHistories]: histories.slice(0, -2)
};
};
```
**审查建议**: 🔴 严重问题,必须修复
---
### 🔴 4.3 isEntry 白名单遗漏
**问题识别**:
- 新增交互节点但未更新 isEntry 白名单
- 节点在恢复时 isEntry 被重置,导致流程错误
**快速修复**:
```typescript
// ❌ 问题代码
// packages/service/core/workflow/dispatch/index.ts
runtimeNodes.forEach((item) => {
if (
item.flowNodeType !== FlowNodeTypeEnum.userSelect &&
item.flowNodeType !== FlowNodeTypeEnum.formInput
// 新的交互节点类型未添加到白名单
) {
item.isEntry = false;
}
});
// ✅ 修复方案
runtimeNodes.forEach((item) => {
if (
item.flowNodeType !== FlowNodeTypeEnum.userSelect &&
item.flowNodeType !== FlowNodeTypeEnum.formInput &&
item.flowNodeType !== FlowNodeTypeEnum.yourNodeType // 新增
) {
item.isEntry = false;
}
});
```
**审查建议**: 🔴 严重问题,必须修复
---
## 5. 安全漏洞问题
### 🔴 5.1 SQL/NoSQL 注入
**问题识别**:
- 用户输入直接用于数据库查询
- 没有输入验证和清理
- 使用字符串拼接构建查询
**快速修复**:
```typescript
// ❌ 问题代码
async function searchUsers(query: string) {
return await db.users.find({ name: query });
// 如果 query = { "$gt": "" },会返回所有用户
} }
// ✅ 修复方案 // ❌ 危险: updateOne 条件字段未校验
async function searchUsers(query: string): Promise<User[]> { await db.collection.updateOne(
if (!query || query.length > 100) { { _id: req.body.id }, // id 可能是 { $gt: "" }
{ $set: req.body.update } // update 可能注入 $where 等
);
```
**快速修复**:
```typescript
// ✅ 方案 1: zod schema 严格约束入参类型(推荐)
import { z } from 'zod';
const LoginSchema = z.object({
username: z.string().min(1).max(50),
password: z.string().min(1).max(100)
});
async function login(req: Request) {
// parse 失败直接抛出,不会进入查询逻辑
const { username, password } = LoginSchema.parse(req.body);
return db.users.findOne({ username, password });
}
// ✅ 方案 2: 显式提取原始值,拒绝对象类型
async function searchUsers(query: unknown): Promise<User[]> {
if (typeof query !== 'string' || query.length > 100) {
throw new Error('Invalid query'); throw new Error('Invalid query');
} }
return db.users.find({
const sanitizedQuery = query.replace(/[^\w\s]/g, ''); name: { $regex: query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), $options: 'i' }
return await db.users.find({
name: {
$regex: sanitizedQuery,
$options: 'i'
}
}).limit(10).toArray(); }).limit(10).toArray();
} }
// ✅ 方案 3: 动态查询条件使用白名单字段
const ALLOWED_FILTER_FIELDS = ['status', 'type', 'teamId'] as const;
function buildSafeFilter(raw: Record<string, unknown>) {
return ALLOWED_FILTER_FIELDS.reduce((acc, key) => {
if (raw[key] !== undefined && typeof raw[key] === 'string') {
acc[key] = raw[key];
}
return acc;
}, {} as Record<string, string>);
}
``` ```
**审查重点**:
- [ ] 所有接口入参是否经过 zod schema 或等效校验
- [ ] 查询条件字段是否均为原始类型 (`string``number``boolean`)
- [ ] 是否存在将 `req.body` 的对象字段直接传入 MongoDB 操作符位置的情况
- [ ] `_id` 字段是否使用 `new Types.ObjectId(id)` 强制转换
**审查建议**: 🔴 严重问题,必须修复 **审查建议**: 🔴 严重问题,必须修复
--- ---
### 🔴 5.2 XSS 攻击 ### 🔴 4.2 XSS 攻击
**问题识别**: **问题识别**:
- 使用 `dangerouslySetInnerHTML` - 使用 `dangerouslySetInnerHTML`
@@ -587,7 +512,7 @@ const UserProfile = ({ user }: { user: User }) => {
--- ---
### 🔴 5.3 文件上传漏洞 ### 🔴 4.3 文件上传漏洞
**问题识别**: **问题识别**:
- 没有文件类型验证 - 没有文件类型验证
@@ -640,99 +565,10 @@ app.post('/upload', async (req, res) => {
--- ---
## 6. 代码重复问题
### 🟡 6.1 重复的逻辑 ## 5. 环境配置问题
**问题识别**: ### 🔴 5.1 硬编码配置
- 相同或相似的代码出现在多处
- 复制粘贴的代码
- 修改 bug 时需要改多处
**快速修复**:
```typescript
// ❌ 问题代码
function validateEmail1(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function validateEmail2(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// ✅ 修复方案
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function validateEmail(email: string): boolean {
return EMAIL_REGEX.test(email);
}
```
**审查建议**: 🟡 建议改进
---
### 🟡 6.2 重复的组件结构
**问题识别**:
- 多个组件有相似的结构和布局
- 只有细微差别
- 可以抽取共享逻辑或样式
**快速修复**:
```typescript
// ❌ 问题代码
const UserList1 = ({ users }: { users: User[] }) => {
return (
<Box p={4} borderWidth="1px" borderRadius="md">
<VStack spacing={3}>
{users.map(user => (
<Box key={user.id} p={3} bg="gray.100">
<Text>{user.name}</Text>
</Box>
))}
</VStack>
</Box>
);
};
// ✅ 修复方案
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
const GenericList = <T,>({ items, renderItem }: ListProps<T>) => {
return (
<Box p={4} borderWidth="1px" borderRadius="md">
<VStack spacing={3}>
{items.map((item, index) => (
<Box key={index} p={3} bg="gray.100">
{renderItem(item)}
</Box>
))}
</VStack>
</Box>
);
};
const UserList = ({ users }: { users: User[] }) => {
return (
<GenericList
items={users}
renderItem={(user) => <Text>{user.name}</Text>}
/>
);
};
```
**审查建议**: 🟡 建议改进
---
## 7. 环境配置问题
### 🔴 7.1 硬编码配置
**问题识别**: **问题识别**:
- 配置值直接写在代码中 - 配置值直接写在代码中
@@ -758,7 +594,7 @@ if (!API_KEY) {
--- ---
### 🟡 7.2 环境变量未验证 ### 🟡 5.2 环境变量未验证
**问题识别**: **问题识别**:
- 直接使用环境变量而不验证 - 直接使用环境变量而不验证
@@ -806,7 +642,6 @@ const config = getConfig();
- [ ] 滥用 `any` 类型 - [ ] 滥用 `any` 类型
- [ ] 未处理的 Promise rejection - [ ] 未处理的 Promise rejection
- [ ] 工作流节点 `isEntry` 未重置
- [ ] 硬编码敏感信息 - [ ] 硬编码敏感信息
- [ ] SQL/NoSQL 注入漏洞 - [ ] SQL/NoSQL 注入漏洞
- [ ] XSS 攻击漏洞 - [ ] XSS 攻击漏洞
@@ -818,7 +653,6 @@ const config = getConfig();
- [ ] 错误信息丢失 - [ ] 错误信息丢失
- [ ] React 不必要的重渲染 - [ ] React 不必要的重渲染
- [ ] 环境变量未验证 - [ ] 环境变量未验证
- [ ] 代码重复
### 🟢 可选优化 (锦上添花) ### 🟢 可选优化 (锦上添花)
@@ -13,4 +13,5 @@ description: 'FastGPT V4.14.11 更新说明'
## 🐛 修复 ## 🐛 修复
1. 对话 Agent 模式,模型存在刷新后被重置问题。 1. 对话 Agent 模式,模型存在刷新后被重置问题。
2. 部分接口未正确进行权限校验。 2. 部分接口未正确进行权限校验。
3. 修复部分接口 nosql 注入分析。
+1 -1
View File
@@ -222,7 +222,7 @@
"document/content/docs/self-host/upgrading/4-14/4141.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4141.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/41410.en.mdx": "2026-03-31T23:15:29+08:00", "document/content/docs/self-host/upgrading/4-14/41410.en.mdx": "2026-03-31T23:15:29+08:00",
"document/content/docs/self-host/upgrading/4-14/41410.mdx": "2026-04-08T16:15:25+08:00", "document/content/docs/self-host/upgrading/4-14/41410.mdx": "2026-04-08T16:15:25+08:00",
"document/content/docs/self-host/upgrading/4-14/41411.mdx": "2026-04-07T21:48:43+08:00", "document/content/docs/self-host/upgrading/4-14/41411.mdx": "2026-04-09T15:12:39+08:00",
"document/content/docs/self-host/upgrading/4-14/4142.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4142.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/4142.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4142.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/4143.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4143.en.mdx": "2026-03-03T17:39:47+08:00",
+53 -39
View File
@@ -21,8 +21,10 @@ import type {
YuqueServer YuqueServer
} from './apiDataset/type'; } from './apiDataset/type';
import type { SourceMemberType } from '../../support/user/type'; import type { SourceMemberType } from '../../support/user/type';
import type { DatasetDataIndexTypeEnum } from './data/constants'; import { DatasetDataIndexTypeEnum } from './data/constants';
import type { ParentIdType } from '../../common/parentFolder/type'; import type { ParentIdType } from '../../common/parentFolder/type';
import z from 'zod';
import { ObjectIdSchema } from '../../common/type/mongo';
export type ChunkSettingsType = { export type ChunkSettingsType = {
trainingType?: DatasetCollectionDataProcessModeEnum; trainingType?: DatasetCollectionDataProcessModeEnum;
@@ -146,35 +148,42 @@ export type DatasetCollectionTagsSchemaType = {
tag: string; tag: string;
}; };
export type DatasetDataIndexItemType = { export const DatasetDataIndexItemSchema = z.object({
type: `${DatasetDataIndexTypeEnum}`; type: z.enum(DatasetDataIndexTypeEnum).meta({ description: '索引类型' }),
dataId: string; // pg data id dataId: z.string().meta({ description: 'vectorDB ID' }),
text: string; text: z.string().meta({ description: '索引文本' })
}; });
export type DatasetDataIndexItemType = z.infer<typeof DatasetDataIndexItemSchema>;
export type DatasetDataFieldType = { export const DatasetDataFieldSchema = z.object({
q: string; // large chunks or question q: z.string().meta({ description: '问题/主文本' }),
a?: string; // answer or custom content a: z.string().optional().meta({ description: '回答/补充文本' }),
imageId?: string; imageId: z.string().optional().meta({ description: '图片 ID' })
}; });
export type DatasetDataSchemaType = DatasetDataFieldType & { export type DatasetDataFieldType = z.infer<typeof DatasetDataFieldSchema>;
_id: string;
userId: string; export const DatasetDataHistorySchema = DatasetDataFieldSchema.extend({
teamId: string; updateTime: z.date().meta({ description: '更新时间' })
tmbId: string; });
datasetId: string; export type DatasetDataHistoryType = z.infer<typeof DatasetDataHistorySchema>;
collectionId: string;
chunkIndex: number; export const DatasetDataSchema = DatasetDataFieldSchema.extend({
updateTime: Date; _id: ObjectIdSchema.meta({ description: '数据 ID' }),
history?: (DatasetDataFieldType & { userId: ObjectIdSchema.meta({ description: '用户 ID' }),
updateTime: Date; teamId: ObjectIdSchema.meta({ description: '团队 ID' }),
})[]; tmbId: ObjectIdSchema.meta({ description: '团队成员 ID' }),
forbid?: boolean; datasetId: ObjectIdSchema.meta({ description: '数据集 ID' }),
fullTextToken: string; collectionId: ObjectIdSchema.meta({ description: '集合 ID' }),
indexes: DatasetDataIndexItemType[]; chunkIndex: z.int().min(0).meta({ description: '块索引' }),
rebuilding?: boolean; updateTime: z.date().meta({ description: '更新时间' }),
imageDescMap?: Record<string, string>; history: z.array(DatasetDataHistorySchema).optional().meta({ description: '历史版本' }),
}; forbid: z.boolean().optional().meta({ description: '是否禁用' }),
fullTextToken: z.string().meta({ description: '全文 token' }),
indexes: z.array(DatasetDataIndexItemSchema).meta({ description: '向量索引' }),
rebuilding: z.boolean().optional().meta({ description: '重建中' }),
imageDescMap: z.record(z.string(), z.string()).optional().meta({ description: '图片描述映射' })
});
export type DatasetDataSchemaType = z.infer<typeof DatasetDataSchema>;
export type DatasetDataTextSchemaType = { export type DatasetDataTextSchemaType = {
_id: string; _id: string;
@@ -308,13 +317,18 @@ export type SearchDataResponseItemType = Omit<
// score: number; // score: number;
}; };
export type DatasetCiteItemType = { export const DatasetCiteItemSchema = z
_id: string; .object({
q: string; _id: ObjectIdSchema.meta({ description: '数据 ID' }),
a?: string; q: z.string().meta({ description: '问题/主文本' }),
imagePreivewUrl?: string; a: z.string().optional().meta({ description: '回答/补充文本' }),
history?: DatasetDataSchemaType['history']; imagePreivewUrl: z.string().optional().meta({ description: '图片预览 URL' }),
updateTime: DatasetDataSchemaType['updateTime']; history: DatasetDataSchema.shape.history.optional(),
index: DatasetDataSchemaType['chunkIndex']; updateTime: DatasetDataSchema.shape.updateTime,
updated?: boolean; index: DatasetDataSchema.shape.chunkIndex,
}; updated: z.boolean().optional().meta({ description: '是否已更新' })
})
.meta({
description: '知识库引用数据列表'
});
export type DatasetCiteItemType = z.infer<typeof DatasetCiteItemSchema>;
@@ -0,0 +1,26 @@
import { z } from 'zod';
import { OutLinkChatAuthSchema } from '../../../../support/permission/chat';
import { ChatMessageSchema } from '../api';
/* ============================================================================
* API: 创建问题引导
* Route: POST /api/core/ai/agent/createQuestionGuide
* Method: POST
* Description: 根据对话历史生成推荐的引导问题列表
* Tags: ['AI', 'Agent', 'Read']
* ============================================================================ */
export const CreateQuestionGuideBodySchema = OutLinkChatAuthSchema.extend({
messages: z.array(ChatMessageSchema).meta({
description: '对话历史消息列表'
})
});
export type CreateQuestionGuideBodyType = z.infer<typeof CreateQuestionGuideBodySchema>;
export const CreateQuestionGuideResponseSchema = z.array(z.string()).meta({
example: ['你能帮我做什么?', '如何使用这个功能?'],
description: '推荐的引导问题列表'
});
export type CreateQuestionGuideResponseType = z.infer<typeof CreateQuestionGuideResponseSchema>;
@@ -0,0 +1,30 @@
import type { OpenAPIPath } from '../../../type';
import { TagsMap } from '../../../tag';
import { CreateQuestionGuideBodySchema, CreateQuestionGuideResponseSchema } from './api';
export const AgentPath: OpenAPIPath = {
'/core/ai/agent/createQuestionGuide': {
post: {
summary: '创建问题引导',
description: '根据对话历史生成推荐的引导问题列表',
tags: [TagsMap.aiCommon],
requestBody: {
content: {
'application/json': {
schema: CreateQuestionGuideBodySchema
}
}
},
responses: {
200: {
description: '成功返回推荐的引导问题列表',
content: {
'application/json': {
schema: CreateQuestionGuideResponseSchema
}
}
}
}
}
}
};
+21
View File
@@ -31,3 +31,24 @@ export const LLMRequestRecordSchema = z.object({
}); });
export type LLMRequestRecordSchemaType = z.infer<typeof LLMRequestRecordSchema>; export type LLMRequestRecordSchemaType = z.infer<typeof LLMRequestRecordSchema>;
/* ============================================================================
* 共享 Schema
* ============================================================================ */
export const ChatMessageSchema = z.object({
role: z.enum(['user', 'assistant', 'system', 'tool', 'function']).meta({
example: 'user',
description: '消息角色'
}),
content: z
.union([z.string(), z.array(z.object())])
.optional()
.meta({
example: '你好',
description: '消息内容'
}),
name: z.string().optional().meta({ description: '发送者名称' }),
tool_calls: z.array(z.object()).optional().meta({ description: '工具调用' }),
tool_call_id: z.string().optional().meta({ description: '工具调用 ID' })
});
+2
View File
@@ -2,9 +2,11 @@ import type { OpenAPIPath } from '../../type';
import { TagsMap } from '../../tag'; import { TagsMap } from '../../tag';
import { GetLLMRequestRecordParamsSchema, LLMRequestRecordSchema } from './api'; import { GetLLMRequestRecordParamsSchema, LLMRequestRecordSchema } from './api';
import { SandboxPath } from './sandbox'; import { SandboxPath } from './sandbox';
import { AgentPath } from './agent';
export const AIPath: OpenAPIPath = { export const AIPath: OpenAPIPath = {
...SandboxPath, ...SandboxPath,
...AgentPath,
'/core/ai/record/getRecord': { '/core/ai/record/getRecord': {
get: { get: {
@@ -7,6 +7,8 @@ import { GetRecentlyUsedAppsResponseSchema } from './api';
import { TagsMap } from '../../tag'; import { TagsMap } from '../../tag';
import { ChatControllerPath } from './controler'; import { ChatControllerPath } from './controler';
import { HelperBotPath } from './helperBot'; import { HelperBotPath } from './helperBot';
import { ChatQuotePath } from './quote/index';
import { ChatInputGuidePath } from './inputGuide/index';
export const ChatPath: OpenAPIPath = { export const ChatPath: OpenAPIPath = {
...ChatSettingPath, ...ChatSettingPath,
@@ -15,6 +17,8 @@ export const ChatPath: OpenAPIPath = {
...ChatHistoryPath, ...ChatHistoryPath,
...ChatControllerPath, ...ChatControllerPath,
...HelperBotPath, ...HelperBotPath,
...ChatQuotePath,
...ChatInputGuidePath,
'/core/chat/recentlyUsed': { '/core/chat/recentlyUsed': {
get: { get: {
@@ -0,0 +1,32 @@
import { z } from 'zod';
import { PaginationSchema } from '../../../api';
import { ObjectIdSchema } from '../../../../common/type/mongo';
/* ============================================================================
* API: 获取对话输入引导列表
* Route: POST /api/core/chat/inputGuide/list
* Method: POST
* Description: 获取指定应用的对话输入引导列表,支持关键词搜索
* Tags: ['Chat', 'InputGuide', 'Read']
* ============================================================================ */
export const ChatInputGuideListBodySchema = PaginationSchema.extend({
appId: ObjectIdSchema.describe('应用 ID'),
searchKey: z.string().max(200).optional().default('').meta({
example: '如何使用',
description: '搜索关键词,用于模糊匹配引导文本'
})
});
export type ChatInputGuideListBodyType = z.infer<typeof ChatInputGuideListBodySchema>;
export const ChatInputGuideItemSchema = z.object({
_id: z.coerce.string().meta({ example: '68ad85a7463006c963799a05', description: '引导 ID' }),
appId: z.coerce.string().meta({ example: '68ad85a7463006c963799a06', description: '应用 ID' }),
text: z.string().meta({ example: '如何开始使用?', description: '引导文本' })
});
export const ChatInputGuideListResponseSchema = z.object({
list: z.array(ChatInputGuideItemSchema).meta({ description: '引导列表' }),
total: z.number().meta({ example: 10, description: '总数' })
});
export type ChatInputGuideListResponseType = z.infer<typeof ChatInputGuideListResponseSchema>;
@@ -0,0 +1,30 @@
import type { OpenAPIPath } from '../../../type';
import { TagsMap } from '../../../tag';
import { ChatInputGuideListBodySchema, ChatInputGuideListResponseSchema } from './api';
export const ChatInputGuidePath: OpenAPIPath = {
'/core/chat/inputGuide/list': {
post: {
summary: '获取对话输入引导列表',
description: '获取指定应用的对话输入引导列表,支持关键词模糊搜索和分页',
tags: [TagsMap.chatInputGuide],
requestBody: {
content: {
'application/json': {
schema: ChatInputGuideListBodySchema
}
}
},
responses: {
200: {
description: '成功返回输入引导列表',
content: {
'application/json': {
schema: ChatInputGuideListResponseSchema
}
}
}
}
}
}
};
@@ -0,0 +1,75 @@
import { z } from 'zod';
import { ObjectIdSchema } from '../../../../common/type/mongo';
import { OutLinkChatAuthSchema } from '../../../../support/permission/chat';
import { DatasetCiteItemSchema } from '../../../../core/dataset/type';
/* ============================================================================
* API: 获取对话引用数据
* Route: POST /api/core/chat/quote/getQuote
* Method: POST
* Description: 获取指定对话消息的数据集引用列表
* Tags: ['Chat', 'Quote', 'Read']
* ============================================================================ */
export const GetQuoteBodySchema = OutLinkChatAuthSchema.extend({
appId: ObjectIdSchema.describe('应用 ID'),
chatId: z.string().min(1).max(256).meta({
example: 'chat_abc123',
description: '对话 ID'
}),
chatItemDataId: z.string().min(1).max(256).meta({
example: 'item_abc123',
description: '对话消息 dataId'
}),
datasetDataIdList: z
.array(z.string().min(1).max(256))
.max(200)
.meta({
example: ['68ad85a7463006c963799a05'],
description: '数据集数据 ID 列表'
}),
collectionIdList: z
.array(z.string().min(1).max(256))
.max(200)
.meta({
example: ['68ad85a7463006c963799a06'],
description: '集合 ID 列表'
})
});
export type GetQuoteBodyType = z.infer<typeof GetQuoteBodySchema>;
export const GetQuoteResponseSchema = z.array(DatasetCiteItemSchema);
export type GetQuoteResponseType = z.infer<typeof GetQuoteResponseSchema>;
/* ============================================================================
* API: 获取集合分页引用数据
* Route: POST /api/core/chat/quote/getCollectionQuote
* Method: POST
* Description: 以链式分页方式获取指定集合的引用数据,支持前后翻页
* Tags: ['Chat', 'Quote', 'Read']
* ============================================================================ */
export const GetCollectionQuoteBodySchema = OutLinkChatAuthSchema.extend({
appId: ObjectIdSchema.describe('应用 ID'),
chatId: z.string().min(1).max(256).describe('对话 ID'),
chatItemDataId: z.string().min(1).max(256).describe('对话消息 dataId'),
collectionId: ObjectIdSchema.describe('集合 ID'),
pageSize: z.number().int().min(1).max(30).default(15).describe('每页条数,范围 [1, 30]'),
anchor: z.number().optional().describe('当前锚点 chunkIndex'),
initialId: z.string().optional().describe('初始定位数据 ID'),
nextId: z.string().optional().describe('向后翻页的游标 ID'),
prevId: z.string().optional().describe('向前翻页的游标 ID')
});
export type GetCollectionQuoteBodyType = z.infer<typeof GetCollectionQuoteBodySchema>;
export const GetCollectionQuoteResSchema = z.object({
list: z.array(
DatasetCiteItemSchema.extend({
id: z.string().describe('数据 IDalias _id'),
anchor: z.number().optional().describe('chunk 序号(alias index')
})
),
hasMorePrev: z.boolean().describe('是否还有更多前置数据'),
hasMoreNext: z.boolean().describe('是否还有更多后置数据')
});
export type GetCollectionQuoteResType = z.infer<typeof GetCollectionQuoteResSchema>;
@@ -0,0 +1,59 @@
import type { OpenAPIPath } from '../../../type';
import { TagsMap } from '../../../tag';
import {
GetQuoteBodySchema,
GetQuoteResponseSchema,
GetCollectionQuoteBodySchema,
GetCollectionQuoteResSchema
} from './api';
export const ChatQuotePath: OpenAPIPath = {
'/core/chat/quote/getQuote': {
post: {
summary: '获取对话引用数据',
description: '获取指定对话消息的数据集引用列表,需要对话访问权限',
tags: [TagsMap.chatPage],
requestBody: {
content: {
'application/json': {
schema: GetQuoteBodySchema
}
}
},
responses: {
200: {
description: '成功返回引用数据列表',
content: {
'application/json': {
schema: GetQuoteResponseSchema
}
}
}
}
}
},
'/core/chat/quote/getCollectionQuote': {
post: {
summary: '获取集合分页引用数据',
description: '以链式分页方式获取指定集合的引用数据,支持前后翻页,需要对话访问权限',
tags: [TagsMap.chatPage],
requestBody: {
content: {
'application/json': {
schema: GetCollectionQuoteBodySchema
}
}
},
responses: {
200: {
description: '成功返回分页引用数据',
content: {
'application/json': {
schema: GetCollectionQuoteResSchema
}
}
}
}
}
}
};
+1
View File
@@ -23,6 +23,7 @@ export const TagsMap = {
chatController: '对话操作', chatController: '对话操作',
chatFeedback: '对话反馈', chatFeedback: '对话反馈',
chatSetting: '门户页配置', chatSetting: '门户页配置',
chatInputGuide: '对话输入引导',
// Dataset // Dataset
datasetCollection: '集合', datasetCollection: '集合',
@@ -1,8 +1,5 @@
import { Box, Flex, HStack } from '@chakra-ui/react'; import { Box, Flex, HStack } from '@chakra-ui/react';
import { import { type SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
type DatasetCiteItemType,
type SearchDataResponseItemType
} from '@fastgpt/global/core/dataset/type';
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils'; import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@@ -110,7 +107,7 @@ const CollectionReader = ({
const formatedDataList = useMemo( const formatedDataList = useMemo(
() => () =>
datasetDataList.map((item: DatasetCiteItemType) => { datasetDataList.map((item) => {
const isCurrentSelected = currentQuoteItem?.id === item._id; const isCurrentSelected = currentQuoteItem?.id === item._id;
const quoteIndex = filterResults.findIndex((res) => res.id === item._id); const quoteIndex = filterResults.findIndex((res) => res.id === item._id);
@@ -1,12 +1,9 @@
import type { NextApiResponse } from 'next'; import type { NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push'; import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide'; import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
import { type ApiRequestProps } from '@fastgpt/service/type/next'; import { type ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { type OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { type ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
import { type AuthModeType } from '@fastgpt/service/support/permission/type'; import { type AuthModeType } from '@fastgpt/service/support/permission/type';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { authOutLinkValid } from '@fastgpt/service/support/permission/publish/authLink'; import { authOutLinkValid } from '@fastgpt/service/support/permission/publish/authLink';
@@ -17,53 +14,47 @@ import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getDefaultLLMModel } from '@fastgpt/service/core/ai/model'; import { getDefaultLLMModel } from '@fastgpt/service/core/ai/model';
import {
CreateQuestionGuideBodySchema,
CreateQuestionGuideResponseSchema,
type CreateQuestionGuideResponseType
} from '@fastgpt/global/openapi/core/ai/agent/api';
import { type OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { type ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
async function handler( async function handler(
req: ApiRequestProps< req: ApiRequestProps,
OutLinkChatAuthProps & { _res: NextApiResponse
messages: ChatCompletionMessageParam[]; ): Promise<CreateQuestionGuideResponseType> {
} const { messages } = CreateQuestionGuideBodySchema.parse(req.body);
>,
res: NextApiResponse<any>
) {
try {
const { messages } = req.body;
const { tmbId, teamId } = await authChatCert({ const { tmbId, teamId } = await authChatCert({
req, req,
authToken: true, authToken: true,
authApiKey: true authApiKey: true
}); });
const qgModel = getDefaultLLMModel(); const qgModel = getDefaultLLMModel();
const { result, inputTokens, outputTokens } = await createQuestionGuide({ const { result, inputTokens, outputTokens } = await createQuestionGuide({
messages, messages: messages as ChatCompletionMessageParam[],
model: qgModel.model model: qgModel.model
}); });
jsonRes(res, { pushQuestionGuideUsage({
data: result model: qgModel.model,
}); inputTokens,
outputTokens,
teamId,
tmbId
});
pushQuestionGuideUsage({ return CreateQuestionGuideResponseSchema.parse(result);
model: qgModel.model,
inputTokens,
outputTokens,
teamId,
tmbId
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
} }
export default NextAPI(handler); export default NextAPI(handler);
/* /*
Abandoned Abandoned
Different chat source Different chat source
1. token (header) 1. token (header)
@@ -89,8 +89,7 @@ async function handler(
} }
}, },
{ {
sort: { createTime: -1 }, sort: { createTime: -1 }
session
} }
); );
} }
@@ -1,8 +1,9 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import type { import {
GetHelperBotChatRecordsParamsType, GetHelperBotChatRecordsParamsSchema,
GetHelperBotChatRecordsResponseType type GetHelperBotChatRecordsParamsType,
type GetHelperBotChatRecordsResponseType
} from '@fastgpt/global/openapi/core/chat/helperBot/api'; } from '@fastgpt/global/openapi/core/chat/helperBot/api';
import { authHelperBotChatCrud } from '@/service/support/permission/auth/chat'; import { authHelperBotChatCrud } from '@/service/support/permission/auth/chat';
import { MongoHelperBotChatItem } from '../../../../../../../../packages/service/core/chat/HelperBot/chatItemSchema'; import { MongoHelperBotChatItem } from '../../../../../../../../packages/service/core/chat/HelperBot/chatItemSchema';
@@ -18,15 +19,15 @@ async function handler(
req: ApiRequestProps<getRecordsBody, getRecordsQuery>, req: ApiRequestProps<getRecordsBody, getRecordsQuery>,
res: ApiResponseType<any> res: ApiResponseType<any>
): Promise<getRecordsResponse> { ): Promise<getRecordsResponse> {
const { type, chatId } = req.query; const { type, chatId } = GetHelperBotChatRecordsParamsSchema.parse(req.query);
const { chat, userId } = await authHelperBotChatCrud({ const { userId } = await authHelperBotChatCrud({
type, type,
chatId, chatId,
req, req,
authToken: true authToken: true
}); });
const { offset, pageSize } = parsePaginationRequest(req); const { offset } = parsePaginationRequest(req);
const [histories, total] = await Promise.all([ const [histories, total] = await Promise.all([
MongoHelperBotChatItem.find({ userId, chatId }).sort({ _id: -1 }).skip(offset).limit(20).lean(), MongoHelperBotChatItem.find({ userId, chatId }).sort({ _id: -1 }).skip(offset).limit(20).lean(),
@@ -1,31 +1,29 @@
import type { NextApiResponse } from 'next'; import type { NextApiResponse } from 'next';
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema'; import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
import { type PaginationProps, type PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { type ApiRequestProps } from '@fastgpt/service/type/next'; import { type ApiRequestProps } from '@fastgpt/service/type/next';
import { type ChatInputGuideSchemaType } from '@fastgpt/global/core/chat/inputGuide/type';
import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
export type ChatInputGuideProps = PaginationProps<{ import {
appId: string; ChatInputGuideListBodySchema,
searchKey: string; ChatInputGuideListResponseSchema,
}>; type ChatInputGuideListResponseType
export type ChatInputGuideResponse = PaginationResponse<ChatInputGuideSchemaType>; } from '@fastgpt/global/openapi/core/chat/inputGuide/api';
async function handler( async function handler(
req: ApiRequestProps<ChatInputGuideProps>, req: ApiRequestProps,
res: NextApiResponse<any> res: NextApiResponse<any>
): Promise<ChatInputGuideResponse> { ): Promise<ChatInputGuideListResponseType> {
const { appId, searchKey } = req.body; const { appId, searchKey } = ChatInputGuideListBodySchema.parse(req.body);
const { offset, pageSize } = parsePaginationRequest(req); const { offset, pageSize } = parsePaginationRequest(req);
await authApp({ req, appId, authToken: true, per: ReadPermissionVal }); await authApp({ req, appId, authToken: true, per: ReadPermissionVal });
const params = { const params = {
appId, appId,
...(searchKey && { text: { $regex: new RegExp(searchKey, 'i') } }) ...(searchKey && { text: { $regex: replaceRegChars(searchKey), $options: 'i' } })
}; };
const [result, total] = await Promise.all([ const [result, total] = await Promise.all([
@@ -33,10 +31,10 @@ async function handler(
MongoChatInputGuide.countDocuments(params) MongoChatInputGuide.countDocuments(params)
]); ]);
return { return ChatInputGuideListResponseSchema.parse({
list: result, list: result,
total total
}; });
} }
export default NextAPI(handler); export default NextAPI(handler);
@@ -1,15 +1,8 @@
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { authChatCrud, authCollectionInChat } from '@/service/support/permission/auth/chat'; import { authChatCrud, authCollectionInChat } from '@/service/support/permission/auth/chat';
import { import { type DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type';
type DatasetCiteItemType,
type DatasetDataSchemaType
} from '@fastgpt/global/core/dataset/type';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { type ApiRequestProps } from '@fastgpt/service/type/next'; import { type ApiRequestProps } from '@fastgpt/service/type/next';
import {
type LinkedListResponse,
type LinkedPaginationProps
} from '@fastgpt/web/common/fetch/type';
import { type FilterQuery, Types } from 'mongoose'; import { type FilterQuery, Types } from 'mongoose';
import { quoteDataFieldSelector } from '@/service/core/chat/constants'; import { quoteDataFieldSelector } from '@/service/core/chat/constants';
import { processChatTimeFilter } from '@/service/core/chat/utils'; import { processChatTimeFilter } from '@/service/core/chat/utils';
@@ -17,27 +10,14 @@ import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller'; import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller';
import { getFormatDatasetCiteList } from '@fastgpt/service/core/dataset/data/controller'; import { getFormatDatasetCiteList } from '@fastgpt/service/core/dataset/data/controller';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import {
export type GetCollectionQuoteProps = LinkedPaginationProps<{}, number> & { GetCollectionQuoteBodySchema,
chatId: string; type GetCollectionQuoteResType
chatItemDataId: string; } from '@fastgpt/global/openapi/core/chat/quote/api';
collectionId: string;
appId: string;
shareId?: string;
outLinkUid?: string;
teamId?: string;
teamToken?: string;
};
export type GetCollectionQuoteRes = LinkedListResponse<DatasetCiteItemType, number>;
type BaseMatchType = FilterQuery<DatasetDataSchemaType>; type BaseMatchType = FilterQuery<DatasetDataSchemaType>;
async function handler( async function handler(req: ApiRequestProps): Promise<GetCollectionQuoteResType> {
req: ApiRequestProps<GetCollectionQuoteProps>
): Promise<GetCollectionQuoteRes> {
const { const {
initialId, initialId,
prevId, prevId,
@@ -52,10 +32,10 @@ async function handler(
outLinkUid, outLinkUid,
teamId, teamId,
teamToken, teamToken,
pageSize = 15 pageSize
} = req.body; } = GetCollectionQuoteBodySchema.parse(req.body);
const limitedPageSize = Math.min(pageSize, 30); const limitedPageSize = pageSize;
const [collection, { chat, showFullText }, chatItem] = await Promise.all([ const [collection, { chat, showFullText }, chatItem] = await Promise.all([
getCollectionWithDataset(collectionId), getCollectionWithDataset(collectionId),
@@ -125,7 +105,7 @@ async function handleInitialLoad({
pageSize: number; pageSize: number;
chatTime: Date; chatTime: Date;
baseMatch: BaseMatchType; baseMatch: BaseMatchType;
}): Promise<GetCollectionQuoteRes> { }): Promise<GetCollectionQuoteResType> {
const centerNode = await MongoDatasetData.findOne( const centerNode = await MongoDatasetData.findOne(
{ {
_id: new Types.ObjectId(initialId) _id: new Types.ObjectId(initialId)
@@ -192,7 +172,7 @@ async function handlePaginatedLoad({
pageSize: number; pageSize: number;
chatTime: Date; chatTime: Date;
baseMatch: BaseMatchType; baseMatch: BaseMatchType;
}): Promise<GetCollectionQuoteRes> { }): Promise<GetCollectionQuoteResType> {
const { list, hasMore } = prevId const { list, hasMore } = prevId
? await getPrevNodes(prevId, nextAnchor, pageSize, baseMatch) ? await getPrevNodes(prevId, nextAnchor, pageSize, baseMatch)
: await getNextNodes(nextId!, nextAnchor, pageSize, baseMatch); : await getNextNodes(nextId!, nextAnchor, pageSize, baseMatch);
@@ -6,38 +6,25 @@ import { quoteDataFieldSelector } from '@/service/core/chat/constants';
import { processChatTimeFilter } from '@/service/core/chat/utils'; import { processChatTimeFilter } from '@/service/core/chat/utils';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { getFormatDatasetCiteList } from '@fastgpt/service/core/dataset/data/controller'; import { getFormatDatasetCiteList } from '@fastgpt/service/core/dataset/data/controller';
import type { DatasetCiteItemType } from '@fastgpt/global/core/dataset/type';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import {
GetQuoteBodySchema,
GetQuoteResponseSchema,
type GetQuoteResponseType
} from '@fastgpt/global/openapi/core/chat/quote/api';
export type GetQuoteProps = { async function handler(req: ApiRequestProps): Promise<GetQuoteResponseType> {
datasetDataIdList: string[];
collectionIdList: string[];
chatId: string;
chatItemDataId: string;
appId: string;
shareId?: string;
outLinkUid?: string;
teamId?: string;
teamToken?: string;
};
export type GetQuotesRes = DatasetCiteItemType[];
async function handler(req: ApiRequestProps<GetQuoteProps>): Promise<GetQuotesRes> {
const { const {
appId, appId,
chatId, chatId,
chatItemDataId, chatItemDataId,
shareId, shareId,
outLinkUid, outLinkUid,
teamId, teamId,
teamToken, teamToken,
collectionIdList, collectionIdList,
datasetDataIdList datasetDataIdList
} = req.body; } = GetQuoteBodySchema.parse(req.body);
const [{ chat, showCite }, chatItem] = await Promise.all([ const [{ chat, showCite }, chatItem] = await Promise.all([
authChatCrud({ authChatCrud({
@@ -60,12 +47,10 @@ async function handler(req: ApiRequestProps<GetQuoteProps>): Promise<GetQuotesRe
quoteDataFieldSelector quoteDataFieldSelector
).lean(); ).lean();
// Get image preview url
const formatPreviewUrlList = getFormatDatasetCiteList(list); const formatPreviewUrlList = getFormatDatasetCiteList(list);
const quoteList = processChatTimeFilter(formatPreviewUrlList, chatItem.time); const quoteList = processChatTimeFilter(formatPreviewUrlList, chatItem.time);
return quoteList; return GetQuoteResponseSchema.parse(quoteList);
} }
export default NextAPI(handler); export default NextAPI(handler);
@@ -6,12 +6,6 @@ import { NextAPI } from '@/service/middleware/entry';
import { type OutLinkSchema } from '@fastgpt/global/support/outLink/type'; import { type OutLinkSchema } from '@fastgpt/global/support/outLink/type';
import type { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import type { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
export const ApiMetadata = {
name: '获取应用内所有 Outlink',
author: 'Finley',
version: '0.1.0'
};
export type OutLinkListQuery = { export type OutLinkListQuery = {
appId: string; // 应用 ID appId: string; // 应用 ID
type: `${PublishChannelEnum}`; type: `${PublishChannelEnum}`;
@@ -39,7 +39,7 @@ const formatIndexes = async ({
indexPrefix?: string; indexPrefix?: string;
}): Promise< }): Promise<
{ {
type: `${DatasetDataIndexTypeEnum}`; type: DatasetDataIndexTypeEnum;
text: string; text: string;
dataId?: string; dataId?: string;
}[] }[]
+11 -8
View File
@@ -12,12 +12,15 @@ import type {
getChatRecordsBody, getChatRecordsBody,
getChatRecordsResponse getChatRecordsResponse
} from '@/pages/api/core/chat/record/getRecords_v2'; } from '@/pages/api/core/chat/record/getRecords_v2';
import type { GetQuoteProps, GetQuotesRes } from '@/pages/api/core/chat/quote/getQuote';
import type { import type {
GetCollectionQuoteProps, GetQuoteBodyType,
GetCollectionQuoteRes GetQuoteResponseType
} from '@/pages/api/core/chat/quote/getCollectionQuote'; } from '@fastgpt/global/openapi/core/chat/quote/api';
import type { ChatSettingModelType, ChatSettingType } from '@fastgpt/global/core/chat/setting/type'; import type { ChatSettingModelType, ChatSettingType } from '@fastgpt/global/core/chat/setting/type';
import type {
GetCollectionQuoteBodyType,
GetCollectionQuoteResType
} from '@fastgpt/global/openapi/core/chat/quote/api';
import type { import type {
GetChatFavouriteListParamsType, GetChatFavouriteListParamsType,
UpdateFavouriteAppParamsType UpdateFavouriteAppParamsType
@@ -57,11 +60,11 @@ export const getChatRecords = (data: getChatRecordsBody) =>
export const delChatRecordById = (data: DeleteChatItemProps) => export const delChatRecordById = (data: DeleteChatItemProps) =>
POST(`/core/chat/item/delete`, data); POST(`/core/chat/item/delete`, data);
export const getQuoteDataList = (data: GetQuoteProps) => export const getQuoteDataList = (data: GetQuoteBodyType) =>
POST<GetQuotesRes>(`/core/chat/quote/getQuote`, data); POST<GetQuoteResponseType>(`/core/chat/quote/getQuote`, data);
export const getCollectionQuote = (data: GetCollectionQuoteProps) => export const getCollectionQuote = (data: GetCollectionQuoteBodyType) =>
POST<GetCollectionQuoteRes>(`/core/chat/quote/getCollectionQuote`, data); POST<GetCollectionQuoteResType>(`/core/chat/quote/getCollectionQuote`, data);
/*---------- chat setting ------------*/ /*---------- chat setting ------------*/
export const getChatSetting = () => GET<ChatSettingType>('/proApi/core/chat/setting/detail'); export const getChatSetting = () => GET<ChatSettingType>('/proApi/core/chat/setting/detail');
@@ -1,8 +1,8 @@
import { GET, POST, DELETE, PUT } from '@/web/common/api/request'; import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import type { import type {
ChatInputGuideProps, ChatInputGuideListBodyType,
ChatInputGuideResponse ChatInputGuideListResponseType
} from '@/pages/api/core/chat/inputGuide/list'; } from '@fastgpt/global/openapi/core/chat/inputGuide/api';
import type { import type {
countChatInputGuideTotalQuery, countChatInputGuideTotalQuery,
countChatInputGuideTotalResponse countChatInputGuideTotalResponse
@@ -24,8 +24,8 @@ export const getCountChatInputGuideTotal = (data: countChatInputGuideTotalQuery)
/** /**
* Get chat input guide list * Get chat input guide list
*/ */
export const getChatInputGuideList = (data: ChatInputGuideProps) => export const getChatInputGuideList = (data: ChatInputGuideListBodyType) =>
POST<ChatInputGuideResponse>(`/core/chat/inputGuide/list`, data); POST<ChatInputGuideListResponseType>(`/core/chat/inputGuide/list`, data);
export const queryChatInputGuideList = (data: QueryChatInputGuideBody, url?: string) => { export const queryChatInputGuideList = (data: QueryChatInputGuideBody, url?: string) => {
if (url) { if (url) {