4.8.12 test (#3006)

* perf: oneapi error tip

* fix: qps limit condition error

* perf: Plan tip

* fix: permission modal ui

* perf: dataset slider ui

* perf: api key auth tmbId problem

* perf: dataset upload i18n

* fix: http json path check
This commit is contained in:
Archer
2024-10-28 22:47:45 +08:00
committed by GitHub
parent b712a821f8
commit e06d72e86e
29 changed files with 270 additions and 294 deletions

View File

@@ -12,7 +12,7 @@ import { jsonRes } from '../response';
export function useReqFrequencyLimit(seconds: number, limit: number) { export function useReqFrequencyLimit(seconds: number, limit: number) {
return async (req: ApiRequestProps, res: NextApiResponse) => { return async (req: ApiRequestProps, res: NextApiResponse) => {
const ip = requestIp.getClientIp(req); const ip = requestIp.getClientIp(req);
if (!ip && process.env.USE_IP_LIMIT !== 'true') { if (!ip || process.env.USE_IP_LIMIT !== 'true') {
return; return;
} }
try { try {

View File

@@ -45,6 +45,7 @@ import { computedMaxToken, llmCompletionsBodyFormat } from '../../../ai/utils';
import { WorkflowResponseType } from '../type'; import { WorkflowResponseType } from '../type';
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time'; import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
import { AiChatQuoteRoleType } from '@fastgpt/global/core/workflow/template/system/aiChat/type'; import { AiChatQuoteRoleType } from '@fastgpt/global/core/workflow/template/system/aiChat/type';
import { getErrText } from '@fastgpt/global/common/error/utils';
export type ChatProps = ModuleDispatchProps< export type ChatProps = ModuleDispatchProps<
AIChatNodeProps & { AIChatNodeProps & {
@@ -262,7 +263,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
}); });
if (user.openaiAccount?.baseUrl) { if (user.openaiAccount?.baseUrl) {
return Promise.reject(`您的 OpenAI key 出错了: ${JSON.stringify(requestBody)}`); return Promise.reject(`您的 OpenAI key 出错了: ${getErrText(error)}`);
} }
return Promise.reject(error); return Promise.reject(error);

View File

@@ -240,13 +240,22 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
.forEach((item) => { .forEach((item) => {
const key = item.key.startsWith('$') ? item.key : `$.${item.key}`; const key = item.key.startsWith('$') ? item.key : `$.${item.key}`;
results[item.key] = (() => { results[item.key] = (() => {
// 检查是否是简单的属性访问或单一索引访问 const result = JSONPath({ path: key, json: formatResponse });
if (/^\$(\.[a-zA-Z_][a-zA-Z0-9_]*)+$/.test(key) || /^\$(\[\d+\])+$/.test(key)) {
return JSONPath({ path: key, json: formatResponse })[0]; // 如果结果为空,返回 undefined
if (!result || result.length === 0) {
return undefined;
} }
// 如果无法确定,默认返回数组 // 以下情况返回数组:
return JSONPath({ path: key, json: formatResponse }); // 1. 使用通配符 *
// 2. 使用数组切片 [start:end]
// 3. 使用过滤表达式 [?(...)]
// 4. 使用递归下降 ..
// 5. 使用多个结果运算符 ,
const needArrayResult = /[*]|[\[][:?]|\.\.|\,/.test(key);
return needArrayResult ? result : result[0];
})(); })();
}); });

View File

@@ -19,7 +19,7 @@ const colorMap: Record<
} }
> = { > = {
white: { white: {
borderColor: 'myGray.400', borderColor: 'myGray.200',
bg: 'white', bg: 'white',
color: 'myGray.700' color: 'myGray.700'
}, },

View File

@@ -75,15 +75,22 @@ export const useConfirm = (props?: {
const [requesting, setRequesting] = useState(false); const [requesting, setRequesting] = useState(false);
useEffect(() => { useEffect(() => {
timer.current = setInterval(() => { if (isOpen) {
setCountDownAmount((val) => { setCountDownAmount(countDown);
if (val <= 0) { timer.current = setInterval(() => {
clearInterval(timer.current); setCountDownAmount((val) => {
} if (val <= 0) {
return val - 1; clearInterval(timer.current);
}); }
}, 1000); return val - 1;
}, []); });
}, 1000);
return () => {
clearInterval(timer.current);
};
}
}, [isOpen]);
return ( return (
<MyModal isOpen={isOpen} iconSrc={iconSrc} title={title} maxW={['90vw', '400px']}> <MyModal isOpen={isOpen} iconSrc={iconSrc} title={title} maxW={['90vw', '400px']}>

View File

@@ -27,7 +27,6 @@
"cron.every_month": "Run Monthly", "cron.every_month": "Run Monthly",
"cron.every_week": "Run Weekly", "cron.every_week": "Run Weekly",
"cron.interval": "Run at Intervals", "cron.interval": "Run at Intervals",
"current_settings": "Current Configuration",
"day": "Day", "day": "Day",
"document_quote": "Document Reference", "document_quote": "Document Reference",
"document_quote_tip": "Usually used to accept user-uploaded document content (requires document parsing), and can also be used to reference other string data.", "document_quote_tip": "Usually used to accept user-uploaded document content (requires document parsing), and can also be used to reference other string data.",

View File

@@ -624,7 +624,7 @@
"core.dataset.test.test result placeholder": "Test results will be displayed here", "core.dataset.test.test result placeholder": "Test results will be displayed here",
"core.dataset.test.test result tip": "Sort based on the similarity between the Dataset content and the test text. You can adjust the corresponding text based on the test results.\nNote: The data in the test records may have been modified. Clicking on a test data will display the latest data.", "core.dataset.test.test result tip": "Sort based on the similarity between the Dataset content and the test text. You can adjust the corresponding text based on the test results.\nNote: The data in the test records may have been modified. Clicking on a test data will display the latest data.",
"core.dataset.training.Agent queue": "QA Training Queue", "core.dataset.training.Agent queue": "QA Training Queue",
"core.dataset.training.Auto mode": "Enhanced Processing (Experimental)", "core.dataset.training.Auto mode": "Auto Processing",
"core.dataset.training.Auto mode Tip": "Increase the semantic richness of data blocks by generating related questions and summaries through sub-indexes and calling models, making it more conducive to retrieval. Requires more storage space and increases AI call times.", "core.dataset.training.Auto mode Tip": "Increase the semantic richness of data blocks by generating related questions and summaries through sub-indexes and calling models, making it more conducive to retrieval. Requires more storage space and increases AI call times.",
"core.dataset.training.Chunk mode": "Direct Segmentation", "core.dataset.training.Chunk mode": "Direct Segmentation",
"core.dataset.training.Full": "Estimated Over 5 Minutes", "core.dataset.training.Full": "Estimated Over 5 Minutes",

View File

@@ -6,6 +6,10 @@
"common_dataset": "General Dataset", "common_dataset": "General Dataset",
"common_dataset_desc": "Build a Dataset by importing files, web links, or manual input.", "common_dataset_desc": "Build a Dataset by importing files, web links, or manual input.",
"confirm_to_rebuild_embedding_tip": "Are you sure you want to switch the index for the Dataset?\nSwitching the index is a significant operation that requires re-indexing all data in your Dataset, which may take a long time. Please ensure your account has sufficient remaining points.\n\nAdditionally, you need to update the applications that use this Dataset to avoid conflicts with other indexed model Datasets.", "confirm_to_rebuild_embedding_tip": "Are you sure you want to switch the index for the Dataset?\nSwitching the index is a significant operation that requires re-indexing all data in your Dataset, which may take a long time. Please ensure your account has sufficient remaining points.\n\nAdditionally, you need to update the applications that use this Dataset to avoid conflicts with other indexed model Datasets.",
"custom_data_process_params": "Custom",
"custom_data_process_params_desc": "Customize data processing rules",
"data_process_params": "Processing parameters",
"data_process_setting": "Data processing configuration",
"dataset.no_collections": "No datasets available", "dataset.no_collections": "No datasets available",
"dataset.no_tags": "No tags available", "dataset.no_tags": "No tags available",
"external_file": "External File Library", "external_file": "External File Library",
@@ -17,6 +21,10 @@
"file_model_function_tip": "Enhances indexing and QA generation", "file_model_function_tip": "Enhances indexing and QA generation",
"filename": "Filename", "filename": "Filename",
"folder_dataset": "Folder", "folder_dataset": "Folder",
"ideal_chunk_length": "ideal block length",
"ideal_chunk_length_tips": "Segment according to the end symbol and combine multiple segments into one block. This value determines the estimated size of the block, if there is any fluctuation.",
"import.Auto mode Estimated Price Tips": "The text understanding model needs to be called, which requires more points: {{price}} points/1K tokens",
"import.Embedding Estimated Price Tips": "Only use the index model and consume a small amount of AI points: {{price}} points/1K tokens",
"permission.des.manage": "Can manage the entire knowledge base data and information", "permission.des.manage": "Can manage the entire knowledge base data and information",
"permission.des.read": "View knowledge base content", "permission.des.read": "View knowledge base content",
"permission.des.write": "Ability to add and change knowledge base content", "permission.des.write": "Ability to add and change knowledge base content",
@@ -33,6 +41,7 @@
"tag.tags": "Tags", "tag.tags": "Tags",
"tag.total_tags": "Total {{total}} tags", "tag.total_tags": "Total {{total}} tags",
"the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "The Dataset has indexes that are being trained or rebuilt", "the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "The Dataset has indexes that are being trained or rebuilt",
"training_mode": "Chunk mode",
"website_dataset": "Website Sync", "website_dataset": "Website Sync",
"website_dataset_desc": "Website sync allows you to build a Dataset directly using a web link." "website_dataset_desc": "Website sync allows you to build a Dataset directly using a web link."
} }

View File

@@ -54,7 +54,6 @@
"field_description_placeholder": "Describe the function of this input field. If it is a tool call parameter, this description will affect the quality of the model generation.", "field_description_placeholder": "Describe the function of this input field. If it is a tool call parameter, this description will affect the quality of the model generation.",
"field_name_already_exists": "Field name already exists", "field_name_already_exists": "Field name already exists",
"field_required": "Required", "field_required": "Required",
"field_used_as_reference": "Support reference",
"field_used_as_tool_input": "Used as Tool Call Parameter", "field_used_as_tool_input": "Used as Tool Call Parameter",
"filter_description": "Currently supports filtering by tags and creation time. Fill in the format as follows:\n{\n \"tags\": {\n \"$and\": [\"Tag 1\",\"Tag 2\"],\n \"$or\": [\"When there are $and tags, and is effective, or is not effective\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm format, collection creation time greater than this time\",\n \"$lte\": \"YYYY-MM-DD HH:mm format, collection creation time less than this time, can be used with $gte\"\n }\n}", "filter_description": "Currently supports filtering by tags and creation time. Fill in the format as follows:\n{\n \"tags\": {\n \"$and\": [\"Tag 1\",\"Tag 2\"],\n \"$or\": [\"When there are $and tags, and is effective, or is not effective\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm format, collection creation time greater than this time\",\n \"$lte\": \"YYYY-MM-DD HH:mm format, collection creation time less than this time, can be used with $gte\"\n }\n}",
"form_input_result": "User complete input result", "form_input_result": "User complete input result",
@@ -175,7 +174,6 @@
"tool_params.params_description_placeholder": "Name/Age/SQL statement..", "tool_params.params_description_placeholder": "Name/Age/SQL statement..",
"tool_params.params_name": "Name", "tool_params.params_name": "Name",
"tool_params.params_name_placeholder": "name/age/sql", "tool_params.params_name_placeholder": "name/age/sql",
"tool_params_config": "Tool params config",
"trigger_after_application_completion": "Will be triggered after the application is fully completed", "trigger_after_application_completion": "Will be triggered after the application is fully completed",
"update_link_error": "Error updating link", "update_link_error": "Error updating link",
"update_specified_node_output_or_global_variable": "Can update the output value of a specified node or update global variables", "update_specified_node_output_or_global_variable": "Can update the output value of a specified node or update global variables",
@@ -185,7 +183,6 @@
"user_form_input_name": "Name", "user_form_input_name": "Name",
"user_question": "User Question", "user_question": "User Question",
"user_question_tool_desc": "User input questions (questions need to be improved)", "user_question_tool_desc": "User input questions (questions need to be improved)",
"value_type": "Value type",
"variable_description": "Variable description", "variable_description": "Variable description",
"variable_picker_tips": "Type node name or variable name to search", "variable_picker_tips": "Type node name or variable name to search",
"variable_update": "Variable Update", "variable_update": "Variable Update",

View File

@@ -27,7 +27,6 @@
"cron.every_month": "每月执行", "cron.every_month": "每月执行",
"cron.every_week": "每周执行", "cron.every_week": "每周执行",
"cron.interval": "间隔执行", "cron.interval": "间隔执行",
"current_settings": "当前配置",
"day": "日", "day": "日",
"document_quote": "文档引用", "document_quote": "文档引用",
"document_quote_tip": "通常用于接受用户上传的文档内容(这需要文档解析),也可以用于引用其他字符串数据。", "document_quote_tip": "通常用于接受用户上传的文档内容(这需要文档解析),也可以用于引用其他字符串数据。",

View File

@@ -20,7 +20,6 @@
"Folder": "文件夹", "Folder": "文件夹",
"Login": "登录", "Login": "登录",
"Move": "移动", "Move": "移动",
"move.confirm": "确认移动",
"Name": "名称", "Name": "名称",
"None": "无", "None": "无",
"Rename": "重命名", "Rename": "重命名",
@@ -83,8 +82,6 @@
"code_error.team_error.un_auth": "无权操作该团队", "code_error.team_error.un_auth": "无权操作该团队",
"code_error.team_error.user_not_active": "用户未接受或已离开团队", "code_error.team_error.user_not_active": "用户未接受或已离开团队",
"code_error.team_error.website_sync_not_enough": "无权使用Web站点同步~", "code_error.team_error.website_sync_not_enough": "无权使用Web站点同步~",
"code_error.team_error.group_name_duplicate": "群组名称重复",
"code_error.team_error.user_not_active": "用户未接受或已离开团队",
"code_error.token_error_code.403": "登录状态无效,请重新登录", "code_error.token_error_code.403": "登录状态无效,请重新登录",
"code_error.user_error.balance_not_enough": "账号余额不足~", "code_error.user_error.balance_not_enough": "账号余额不足~",
"code_error.user_error.bin_visitor": "您的身份校验未通过", "code_error.user_error.bin_visitor": "您的身份校验未通过",
@@ -544,14 +541,14 @@
"core.dataset.externalFile": "外部文件库", "core.dataset.externalFile": "外部文件库",
"core.dataset.file": "文件", "core.dataset.file": "文件",
"core.dataset.folder": "目录", "core.dataset.folder": "目录",
"core.dataset.import.Auto mode Estimated Price Tips": "需调用文本理解模型,需要消耗较多 tokens{{price}} 积分/1K tokens", "core.dataset.import.Auto mode Estimated Price Tips": "需调用文本理解模型,需要消耗较多AI 积分{{price}} 积分/1K tokens",
"core.dataset.import.Auto process": "自动", "core.dataset.import.Auto process": "自动",
"core.dataset.import.Auto process desc": "自动设置分割和预处理规则", "core.dataset.import.Auto process desc": "自动设置分割和预处理规则",
"core.dataset.import.Chunk Range": "范围:{{min}}~{{max}}", "core.dataset.import.Chunk Range": "范围:{{min}}~{{max}}",
"core.dataset.import.Chunk Split": "直接分段", "core.dataset.import.Chunk Split": "直接分段",
"core.dataset.import.Chunk Split Tip": "将文本按一定的规则进行分段处理后,转成可进行语义搜索的格式,适合绝大多数场景。不需要调用模型额外处理,成本低。", "core.dataset.import.Chunk Split Tip": "将文本按一定的规则进行分段处理后,转成可进行语义搜索的格式,适合绝大多数场景。不需要调用模型额外处理,成本低。",
"core.dataset.import.Custom process": "自定义规则", "core.dataset.import.Custom process": "自定义规则",
"core.dataset.import.Custom process desc": "自定义设置分制和预处理规则", "core.dataset.import.Custom process desc": "自定义设置数据处理规则",
"core.dataset.import.Custom prompt": "自定义提示词", "core.dataset.import.Custom prompt": "自定义提示词",
"core.dataset.import.Custom split char": "自定义分隔符", "core.dataset.import.Custom split char": "自定义分隔符",
"core.dataset.import.Custom split char Tips": "允许你根据自定义的分隔符进行分块。通常用于已处理好的数据,使用特定的分隔符来精确分块。", "core.dataset.import.Custom split char Tips": "允许你根据自定义的分隔符进行分块。通常用于已处理好的数据,使用特定的分隔符来精确分块。",
@@ -633,7 +630,7 @@
"core.dataset.test.test result placeholder": "测试结果将在这里展示", "core.dataset.test.test result placeholder": "测试结果将在这里展示",
"core.dataset.test.test result tip": "根据知识库内容与测试文本的相似度进行排序,你可以根据测试结果调整对应的文本。\n注意测试记录中的数据可能已经被修改过点击某条测试数据后将展示最新的数据。", "core.dataset.test.test result tip": "根据知识库内容与测试文本的相似度进行排序,你可以根据测试结果调整对应的文本。\n注意测试记录中的数据可能已经被修改过点击某条测试数据后将展示最新的数据。",
"core.dataset.training.Agent queue": "QA 训练排队", "core.dataset.training.Agent queue": "QA 训练排队",
"core.dataset.training.Auto mode": "增强处理(实验)", "core.dataset.training.Auto mode": "增强处理",
"core.dataset.training.Auto mode Tip": "通过子索引以及调用模型生成相关问题与摘要,来增加数据块的语义丰富度,更利于检索。需要消耗更多的存储空间和增加 AI 调用次数。", "core.dataset.training.Auto mode Tip": "通过子索引以及调用模型生成相关问题与摘要,来增加数据块的语义丰富度,更利于检索。需要消耗更多的存储空间和增加 AI 调用次数。",
"core.dataset.training.Chunk mode": "直接分段", "core.dataset.training.Chunk mode": "直接分段",
"core.dataset.training.Full": "预计 5 分钟以上", "core.dataset.training.Full": "预计 5 分钟以上",
@@ -892,6 +889,7 @@
"item_description": "字段描述", "item_description": "字段描述",
"item_name": "字段名", "item_name": "字段名",
"key_repetition": "key 重复", "key_repetition": "key 重复",
"move.confirm": "确认移动",
"navbar.Account": "账号", "navbar.Account": "账号",
"navbar.Chat": "聊天", "navbar.Chat": "聊天",
"navbar.Datasets": "知识库", "navbar.Datasets": "知识库",

View File

@@ -6,6 +6,11 @@
"common_dataset": "通用知识库", "common_dataset": "通用知识库",
"common_dataset_desc": "可通过导入文件、网页链接或手动录入形式构建知识库", "common_dataset_desc": "可通过导入文件、网页链接或手动录入形式构建知识库",
"confirm_to_rebuild_embedding_tip": "确认为知识库切换索引?\n切换索引是一个非常重量的操作需要对您知识库内所有数据进行重新索引时间可能较长请确保账号内剩余积分充足。\n\n此外你还需要注意修改选择该知识库的应用避免它们与其他索引模型知识库混用。", "confirm_to_rebuild_embedding_tip": "确认为知识库切换索引?\n切换索引是一个非常重量的操作需要对您知识库内所有数据进行重新索引时间可能较长请确保账号内剩余积分充足。\n\n此外你还需要注意修改选择该知识库的应用避免它们与其他索引模型知识库混用。",
"custom_data_process_params": "自定义",
"custom_data_process_params_desc": "自定义设置数据处理规则",
"data.ideal_chunk_length": "理想分块长度",
"data_process_params": "处理参数",
"data_process_setting": "数据处理配置",
"dataset.no_collections": "暂无数据集", "dataset.no_collections": "暂无数据集",
"dataset.no_tags": "暂无标签", "dataset.no_tags": "暂无标签",
"external_file": "外部文件库", "external_file": "外部文件库",
@@ -17,6 +22,14 @@
"file_model_function_tip": "用于增强索引和 QA 生成", "file_model_function_tip": "用于增强索引和 QA 生成",
"filename": "文件名", "filename": "文件名",
"folder_dataset": "文件夹", "folder_dataset": "文件夹",
"ideal_chunk_length": "理想分块长度",
"ideal_chunk_length_tips": "按结束符号进行分段,并将多个分段组成一个分块,该值决定了分块的预估大小,如果会有上下浮动。",
"import.Auto mode Estimated Price Tips": "需调用文本理解模型需要消耗较多AI 积分:{{price}} 积分/1K tokens",
"import.Embedding Estimated Price Tips": "仅使用索引模型,消耗少量 AI 积分:{{price}} 积分/1K tokens",
"move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置,原先的权限设置失效。",
"permission.des.manage": "可管理整个知识库数据和信息",
"permission.des.read": "可查看知识库内容",
"permission.des.write": "可增加和变更知识库内容",
"rebuild_embedding_start_tip": "切换索引模型任务已开始", "rebuild_embedding_start_tip": "切换索引模型任务已开始",
"rebuilding_index_count": "重建中索引数量:{{count}}", "rebuilding_index_count": "重建中索引数量:{{count}}",
"tag.Add New": "新建", "tag.Add New": "新建",
@@ -30,10 +43,7 @@
"tag.tags": "标签", "tag.tags": "标签",
"tag.total_tags": "共{{total}}个标签", "tag.total_tags": "共{{total}}个标签",
"the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "知识库有训练中或正在重建的索引", "the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "知识库有训练中或正在重建的索引",
"training_mode": "处理方式",
"website_dataset": "Web 站点同步", "website_dataset": "Web 站点同步",
"website_dataset_desc": "Web 站点同步允许你直接使用一个网页链接构建知识库", "website_dataset_desc": "Web 站点同步允许你直接使用一个网页链接构建知识库"
"permission.des.read": "可查看知识库内容",
"permission.des.write": "可增加和变更知识库内容",
"permission.des.manage": "可管理整个知识库数据和信息",
"move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置,原先的权限设置失效。"
} }

View File

@@ -55,7 +55,6 @@
"field_description_placeholder": "描述该输入字段的功能,如果为工具调用参数,则该描述会影响模型生成的质量", "field_description_placeholder": "描述该输入字段的功能,如果为工具调用参数,则该描述会影响模型生成的质量",
"field_name_already_exists": "字段名已经存在", "field_name_already_exists": "字段名已经存在",
"field_required": "必填", "field_required": "必填",
"field_used_as_reference": "支持变量引用",
"field_used_as_tool_input": "作为工具调用参数", "field_used_as_tool_input": "作为工具调用参数",
"filter_description": "目前支持标签和创建时间过滤,需按照以下格式填写:\n{\n \"tags\": {\n \"$and\": [\"标签 1\",\"标签 2\"],\n \"$or\": [\"有 $and 标签时and 生效or 不生效\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm 格式即可,集合的创建时间大于该时间\",\n \"$lte\": \"YYYY-MM-DD HH:mm 格式即可,集合的创建时间小于该时间,可和 $gte 共同使用\"\n }\n}", "filter_description": "目前支持标签和创建时间过滤,需按照以下格式填写:\n{\n \"tags\": {\n \"$and\": [\"标签 1\",\"标签 2\"],\n \"$or\": [\"有 $and 标签时and 生效or 不生效\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm 格式即可,集合的创建时间大于该时间\",\n \"$lte\": \"YYYY-MM-DD HH:mm 格式即可,集合的创建时间小于该时间,可和 $gte 共同使用\"\n }\n}",
"form_input_result": "用户完整输入结果", "form_input_result": "用户完整输入结果",
@@ -186,7 +185,6 @@
"user_form_input_name": "标题", "user_form_input_name": "标题",
"user_question": "用户问题", "user_question": "用户问题",
"user_question_tool_desc": "用户输入的问题(问题需要完善)", "user_question_tool_desc": "用户输入的问题(问题需要完善)",
"value_type": "数据类型",
"variable_description": "变量描述", "variable_description": "变量描述",
"variable_picker_tips": "可输入节点名或变量名搜索", "variable_picker_tips": "可输入节点名或变量名搜索",
"variable_update": "变量更新", "variable_update": "变量更新",

View File

@@ -126,29 +126,11 @@ const FolderSlideCard = ({
<MyDivider my={6} /> <MyDivider my={6} />
<Box> <Box>
<FormLabel>{t('common:support.permission.Permission')}</FormLabel>
{!isInheritPermission && ( {!isInheritPermission && (
<Box mt={2}> <Box mt={2}>
<ResumeInherit onResume={() => resumeInheritPermission?.().then(refetchResource)} /> <ResumeInherit onResume={() => resumeInheritPermission?.().then(refetchResource)} />
</Box> </Box>
)} )}
{managePer.permission.hasManagePer && !!defaultPer && (
<Box mt={5}>
<Box fontSize={'sm'} color={'myGray.500'}>
{t('common:permission.Default permission')}
</Box>
<DefaultPermissionList
mt="1"
per={defaultPer.value}
defaultPer={defaultPer.defaultValue}
isInheritPermission={isInheritPermission}
onChange={(v) => defaultPer.onChange(v)}
hasParent={hasParent}
/>
</Box>
)}
<Box mt={6}> <Box mt={6}>
<CollaboratorContextProvider <CollaboratorContextProvider
{...managePer} {...managePer}
@@ -190,8 +172,8 @@ const FolderSlideCard = ({
<MemberListCard <MemberListCard
mt={2} mt={2}
tagStyle={{ tagStyle={{
type: 'borderSolid', type: 'fill',
colorSchema: 'gray' colorSchema: 'white'
}} }}
/> />
</> </>

View File

@@ -114,6 +114,35 @@ const ChatInput = ({
renderAudioGraph, renderAudioGraph,
stream stream
} = useSpeech({ appId, ...outLinkAuthData }); } = useSpeech({ appId, ...outLinkAuthData });
const onWhisperRecord = useCallback(() => {
const finishWhisperTranscription = (text: string) => {
if (!text) return;
if (whisperConfig?.autoSend) {
onSendMessage({
text,
files: fileList,
autoTTSResponse
});
replaceFiles([]);
} else {
resetInputVal({ text });
}
};
if (isSpeaking) {
return stopSpeak();
}
startSpeak(finishWhisperTranscription);
}, [
autoTTSResponse,
fileList,
isSpeaking,
onSendMessage,
replaceFiles,
resetInputVal,
startSpeak,
stopSpeak,
whisperConfig?.autoSend
]);
useEffect(() => { useEffect(() => {
if (!stream) { if (!stream) {
return; return;
@@ -131,28 +160,6 @@ const ChatInput = ({
}; };
renderCurve(); renderCurve();
}, [renderAudioGraph, stream]); }, [renderAudioGraph, stream]);
const finishWhisperTranscription = useCallback(
(text: string) => {
if (!text) return;
if (whisperConfig?.autoSend) {
onSendMessage({
text,
files: fileList,
autoTTSResponse
});
replaceFiles([]);
} else {
resetInputVal({ text });
}
},
[autoTTSResponse, fileList, onSendMessage, replaceFiles, resetInputVal, whisperConfig?.autoSend]
);
const onWhisperRecord = useCallback(() => {
if (isSpeaking) {
return stopSpeak();
}
startSpeak(finishWhisperTranscription);
}, [finishWhisperTranscription, isSpeaking, startSpeak, stopSpeak]);
const RenderTranslateLoading = useMemo( const RenderTranslateLoading = useMemo(
() => ( () => (

View File

@@ -95,23 +95,19 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
iconSrc="modal/AddClb" iconSrc="modal/AddClb"
title={t('user:team.add_collaborator')} title={t('user:team.add_collaborator')}
minW="800px" minW="800px"
h={'100%'}
isCentered isCentered
isLoading={loadingMembersAndGroups} isLoading={loadingMembersAndGroups}
> >
<ModalBody> <ModalBody flex={'1'}>
<Grid <Grid
border="1px solid" border="1px solid"
borderColor="myGray.200" borderColor="myGray.200"
borderRadius="0.5rem" borderRadius="0.5rem"
gridTemplateColumns="1fr 1fr" gridTemplateColumns="1fr 1fr"
h={'100%'}
> >
<Flex <Flex flexDirection="column" borderRight="1px solid" borderColor="myGray.200" p="4">
flexDirection="column"
borderRight="1px solid"
borderColor="myGray.200"
p="4"
minH="200px"
>
<SearchInput <SearchInput
placeholder={t('user:search_user')} placeholder={t('user:search_user')}
bgColor="myGray.50" bgColor="myGray.50"

View File

@@ -325,6 +325,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
</Box> </Box>
); );
}; };
const PlanUsage = () => { const PlanUsage = () => {
const router = useRouter(); const router = useRouter();
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -527,15 +527,14 @@ const authHeaderRequest = async ({
teamId, teamId,
tmbId, tmbId,
authType, authType,
apikey, apikey
canWrite: apiKeyCanWrite
} = await authCert({ } = await authCert({
req, req,
authToken: true, authToken: true,
authApiKey: true authApiKey: true
}); });
const { app, canWrite } = await (async () => { const { app } = await (async () => {
if (authType === AuthUserTypeEnum.apikey) { if (authType === AuthUserTypeEnum.apikey) {
if (!apiKeyAppId) { if (!apiKeyAppId) {
return Promise.reject( return Promise.reject(
@@ -551,16 +550,14 @@ const authHeaderRequest = async ({
appId = String(app._id); appId = String(app._id);
return { return {
app, app
canWrite: apiKeyCanWrite
}; };
} else { } else {
// token_auth // token_auth
if (!appId) { if (!appId) {
return Promise.reject('appId is empty'); return Promise.reject('appId is empty');
} }
const { app, permission } = await authApp({ const { app } = await authApp({
req, req,
authToken: true, authToken: true,
appId, appId,
@@ -568,8 +565,7 @@ const authHeaderRequest = async ({
}); });
return { return {
app, app
canWrite: permission.hasReadPer
}; };
} }
})(); })();
@@ -579,7 +575,12 @@ const authHeaderRequest = async ({
MongoChat.findOne({ appId, chatId }).lean() MongoChat.findOne({ appId, chatId }).lean()
]); ]);
if (chat && (String(chat.teamId) !== teamId || String(chat.tmbId) !== tmbId)) { if (
chat &&
(String(chat.teamId) !== teamId ||
// There's no need to distinguish who created it if it's apiKey auth
(authType === AuthUserTypeEnum.token && String(chat.tmbId) !== tmbId))
) {
return Promise.reject(ChatErrEnum.unAuthChat); return Promise.reject(ChatErrEnum.unAuthChat);
} }
@@ -591,7 +592,7 @@ const authHeaderRequest = async ({
responseDetail: true, responseDetail: true,
apikey, apikey,
authType, authType,
canWrite canWrite: true
}; };
}; };

View File

@@ -386,7 +386,7 @@ const Render = (props: Props) => {
return ( return (
<> <>
<NextHead title={props.appName} desc={props.appIntro} icon={props.appAvatar} /> <NextHead title={props.appName || 'AI'} desc={props.appIntro} icon={props.appAvatar} />
{systemLoaded && ( {systemLoaded && (
<ChatContextProvider params={contextParams}> <ChatContextProvider params={contextParams}>
<OutLink {...props} outLinkUid={contextParams.outLinkUid} />; <OutLink {...props} outLinkUid={contextParams.outLinkUid} />;

View File

@@ -15,11 +15,11 @@ function MemberManager({ managePer }: { managePer: MemberManagerInputPropsType }
return ( return (
<> <>
<Flex alignItems="center" flexDirection="row" justifyContent="space-between" w="full"> <Flex alignItems="center" flexDirection="row" justifyContent="space-between" w="full">
<Box> <Box color={'myGray.900'} fontSize={'mini'} fontWeight={'bold'}>
<FormLabel fontSize={'mini'}>{t('common:permission.Collaborator')}</FormLabel> {t('common:permission.Collaborator')}
</Box> </Box>
<Flex gap={0.5}> <Flex gap={2}>
<Box p={1}> <Box>
<MyIcon <MyIcon
onClick={onOpenManageModal} onClick={onOpenManageModal}
name="common/setting" name="common/setting"
@@ -30,7 +30,7 @@ function MemberManager({ managePer }: { managePer: MemberManagerInputPropsType }
_hover={{ color: 'primary.500' }} _hover={{ color: 'primary.500' }}
/> />
</Box> </Box>
<Box p={1}> <Box>
<MyIcon <MyIcon
cursor={'pointer'} cursor={'pointer'}
onClick={onOpenAddMember} onClick={onOpenAddMember}

View File

@@ -182,7 +182,7 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
showChunkInput: false, showChunkInput: false,
showPromptInput: false, showPromptInput: false,
charsPointsPrice: agentModel.charsPointsPrice, charsPointsPrice: agentModel.charsPointsPrice,
priceTip: t('common:core.dataset.import.Auto mode Estimated Price Tips', { priceTip: t('dataset:import.Auto mode Estimated Price Tips', {
price: agentModel.charsPointsPrice price: agentModel.charsPointsPrice
}), }),
uploadRate: 100 uploadRate: 100
@@ -197,7 +197,7 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
showChunkInput: true, showChunkInput: true,
showPromptInput: false, showPromptInput: false,
charsPointsPrice: vectorModel.charsPointsPrice, charsPointsPrice: vectorModel.charsPointsPrice,
priceTip: t('common:core.dataset.import.Embedding Estimated Price Tips', { priceTip: t('dataset:import.Embedding Estimated Price Tips', {
price: vectorModel.charsPointsPrice price: vectorModel.charsPointsPrice
}), }),
uploadRate: 150 uploadRate: 150
@@ -212,8 +212,8 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
showChunkInput: true, showChunkInput: true,
showPromptInput: true, showPromptInput: true,
charsPointsPrice: agentModel.charsPointsPrice, charsPointsPrice: agentModel.charsPointsPrice,
priceTip: t('common:core.dataset.import.QA Estimated Price Tips', { priceTip: t('dataset:import.Auto mode Estimated Price Tips', {
price: agentModel?.charsPointsPrice price: agentModel.charsPointsPrice
}), }),
uploadRate: 30 uploadRate: 30
} }

View File

@@ -78,7 +78,7 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
<Box h={'100%'} display={['block', 'flex']} fontSize={'sm'}> <Box h={'100%'} display={['block', 'flex']} fontSize={'sm'}>
<Box <Box
flex={'1 0 0'} flex={'1 0 0'}
minW={['auto', '540px']} minW={['auto', '500px']}
maxW={'600px'} maxW={'600px'}
h={['auto', '100%']} h={['auto', '100%']}
overflow={'auto'} overflow={'auto'}
@@ -86,11 +86,11 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
> >
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<MyIcon name={'common/settingLight'} w={'20px'} /> <MyIcon name={'common/settingLight'} w={'20px'} />
<Box fontSize={'md'}>{t('common:core.dataset.import.Data process params')}</Box> <Box fontSize={'md'}>{t('dataset:data_process_setting')}</Box>
</Flex> </Flex>
<Box display={['block', 'flex']} mt={4} alignItems={'center'}> <Box display={['block', 'flex']} mt={4} alignItems={'center'}>
<FormLabel flex={'0 0 100px'}>{t('common:core.dataset.import.Training mode')}</FormLabel> <FormLabel flex={'0 0 100px'}>{t('dataset:training_mode')}</FormLabel>
<LeftRadio <LeftRadio
list={trainingModeList.map(([key, value]) => ({ list={trainingModeList.map(([key, value]) => ({
title: t(value.label as any), title: t(value.label as any),
@@ -107,8 +107,9 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
flexWrap={'wrap'} flexWrap={'wrap'}
/> />
</Box> </Box>
<Box display={['block', 'flex']} mt={5}> <Box display={['block', 'flex']} mt={5}>
<FormLabel flex={'0 0 100px'}>{t('common:core.dataset.import.Process way')}</FormLabel> <FormLabel flex={'0 0 100px'}>{t('dataset:data_process_params')}</FormLabel>
<LeftRadio <LeftRadio
list={[ list={[
{ {
@@ -117,18 +118,16 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
value: ImportProcessWayEnum.auto value: ImportProcessWayEnum.auto
}, },
{ {
title: t('common:core.dataset.import.Custom process'), title: t('dataset:custom_data_process_params'),
desc: t('common:core.dataset.import.Custom process desc'), desc: t('dataset:custom_data_process_params_desc'),
value: ImportProcessWayEnum.custom, value: ImportProcessWayEnum.custom,
children: way === ImportProcessWayEnum.custom && ( children: way === ImportProcessWayEnum.custom && (
<Box mt={5}> <Box mt={5}>
{showChunkInput && chunkSizeField && ( {showChunkInput && chunkSizeField && (
<Box> <Box>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box>{t('common:core.dataset.import.Ideal chunk length')}</Box> <Box>{t('dataset:ideal_chunk_length')}</Box>
<MyTooltip <MyTooltip label={t('dataset:ideal_chunk_length_tips')}>
label={t('common:core.dataset.import.Ideal chunk length Tips')}
>
<MyIcon <MyIcon
name={'common/questionLight'} name={'common/questionLight'}
ml={1} ml={1}
@@ -269,15 +268,15 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
}} }}
></LeftRadio> ></LeftRadio>
</Box> </Box>
<Box mt={5} pl={[0, '100px']} gap={3}>
{feConfigs?.show_pay && ( {feConfigs?.show_pay && (
<MyTooltip label={priceTip}> <Box mt={5} pl={[0, '100px']} gap={3}>
<MyTag colorSchema={'gray'} py={1.5} borderRadius={'md'} px={3} whiteSpace={'wrap'}> <MyTag colorSchema={'gray'} py={1.5} borderRadius={'md'} px={3} whiteSpace={'wrap'}>
{priceTip} {priceTip}
</MyTag> </MyTag>
</MyTooltip> </Box>
)} )}
</Box>
<Flex mt={5} gap={3} justifyContent={'flex-end'}> <Flex mt={5} gap={3} justifyContent={'flex-end'}>
<Button <Button
onClick={() => { onClick={() => {

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Box, Flex, IconButton } from '@chakra-ui/react'; import { Box, Flex, Grid, IconButton } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
@@ -19,68 +19,69 @@ const Preview = ({ showPreviewChunks }: { showPreviewChunks: boolean }) => {
const [previewChunkSource, setPreviewChunkSource] = useState<ImportSourceItemType>(); const [previewChunkSource, setPreviewChunkSource] = useState<ImportSourceItemType>();
return ( return (
<Box h={'100%'} display={['block', 'flex']} flexDirection={'column'}> <Box h={'100%'} w={'100%'} display={['block', 'flex']} flexDirection={'column'}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<MyIcon name={'core/dataset/fileCollection'} w={'20px'} /> <MyIcon name={'core/dataset/fileCollection'} w={'20px'} />
<Box fontSize={'md'}>{t('common:core.dataset.import.Sources list')}</Box> <Box fontSize={'md'}>{t('common:core.dataset.import.Sources list')}</Box>
</Flex> </Flex>
<Box mt={3} flex={'1 0 0'} width={'100%'} overflow={'auto'}> <Box mt={3} flex={'1 0 0'} width={'100%'} overflowY={'auto'}>
{sources.map((source) => ( <Grid w={'100%'} gap={3} gridTemplateColumns={['1fr', '1fr', '1fr', '1fr', '1fr 1fr']}>
<Flex {sources.map((source) => (
key={source.id} <Flex
bg={'white'} key={source.id}
p={4} bg={'white'}
borderRadius={'md'} p={4}
borderWidth={'1px'} borderRadius={'md'}
borderColor={'borderColor.low'} borderWidth={'1px'}
boxShadow={'2'} borderColor={'borderColor.low'}
mb={3} boxShadow={'2'}
alignItems={'center'} alignItems={'center'}
> >
<MyIcon name={source.icon as any} w={'16px'} /> <MyIcon name={source.icon as any} w={['1rem', '1.25rem']} />
<Box mx={1} flex={'1 0 0'} w={0} className="textEllipsis"> <Box mx={1} flex={'1 0 0'} wordBreak={'break-all'} fontSize={'sm'}>
{source.sourceName} {source.sourceName}
</Box>
{showPreviewChunks && (
<Box fontSize={'xs'} color={'myGray.600'}>
<MyMenu
Button={
<IconButton
icon={<MyIcon name={'common/viewLight'} w={'14px'} p={2} />}
aria-label={''}
size={'sm'}
variant={'whitePrimary'}
/>
}
menuList={[
{
children: [
{
label: (
<Flex alignItems={'center'}>
<MyIcon name={'core/dataset/fileCollection'} w={'14px'} mr={2} />
{t('common:core.dataset.import.Preview raw text')}
</Flex>
),
onClick: () => setPreviewRawTextSource(source)
},
{
label: (
<Flex alignItems={'center'}>
<MyIcon name={'core/dataset/splitLight'} w={'14px'} mr={2} />
{t('common:core.dataset.import.Preview chunks')}
</Flex>
),
onClick: () => setPreviewChunkSource(source)
}
]
}
]}
/>
</Box> </Box>
)} {showPreviewChunks && (
</Flex> <Box fontSize={'xs'} color={'myGray.600'}>
))} <MyMenu
Button={
<IconButton
icon={<MyIcon name={'common/viewLight'} w={'14px'} p={2} />}
aria-label={''}
size={'sm'}
variant={'whitePrimary'}
/>
}
menuList={[
{
children: [
{
label: (
<Flex alignItems={'center'}>
<MyIcon name={'core/dataset/fileCollection'} w={'14px'} mr={2} />
{t('common:core.dataset.import.Preview raw text')}
</Flex>
),
onClick: () => setPreviewRawTextSource(source)
},
{
label: (
<Flex alignItems={'center'}>
<MyIcon name={'core/dataset/splitLight'} w={'14px'} mr={2} />
{t('common:core.dataset.import.Preview chunks')}
</Flex>
),
onClick: () => setPreviewChunkSource(source)
}
]
}
]}
/>
</Box>
)}
</Flex>
))}
</Grid>
</Box> </Box>
{!!previewRawTextSource && ( {!!previewRawTextSource && (
<PreviewRawText <PreviewRawText

View File

@@ -33,8 +33,6 @@ import { EditResourceInfoFormType } from '@/components/common/Modal/EditResource
const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal')); const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal'));
const Info = ({ datasetId }: { datasetId: string }) => { const Info = ({ datasetId }: { datasetId: string }) => {
const [openBaseConfig, setOpenBaseConfig] = useState(true);
const [openPermissionConfig, setOpenPermissionConfig] = useState(true);
const { t } = useTranslation(); const { t } = useTranslation();
const { datasetDetail, loadDatasetDetail, updateDataset, rebuildingCount, trainingCount } = const { datasetDetail, loadDatasetDetail, updateDataset, rebuildingCount, trainingCount } =
useContextSelector(DatasetPageContext, (v) => v); useContextSelector(DatasetPageContext, (v) => v);
@@ -175,23 +173,13 @@ const Info = ({ datasetId }: { datasetId: string }) => {
<MyDivider my={4} h={'2px'} maxW={'500px'} /> <MyDivider my={4} h={'2px'} maxW={'500px'} />
<Box overflow={'hidden'} h={openBaseConfig ? 'auto' : '24px'}> <Box overflow={'hidden'}>
<Flex justify={'space-between'} alignItems={'center'} fontSize={'mini'} h={'24px'}> <Flex justify={'space-between'} alignItems={'center'} fontSize={'mini'} h={'24px'}>
<Box fontWeight={'500'} color={'myGray.900'} userSelect={'none'}> <Box fontWeight={'500'} color={'myGray.900'} userSelect={'none'}>
{t('common:common.base_config')} {t('common:common.base_config')}
</Box> </Box>
<MyIcon
w={'16px'}
_hover={{ color: 'primary.500', cursor: 'pointer' }}
color={'myGray.500'}
name={openBaseConfig ? 'core/chat/chevronUp' : 'core/chat/chevronDown'}
onClick={(e) => {
e.preventDefault();
setOpenBaseConfig(!openBaseConfig);
}}
/>
</Flex> </Flex>
<Flex mt={3} w={'100%'} flexDir={'column'} userSelect={'none'}> <Flex mt={3} w={'100%'} flexDir={'column'}>
<FormLabel fontSize={'mini'} fontWeight={'500'}> <FormLabel fontSize={'mini'} fontWeight={'500'}>
{t('common:core.dataset.Dataset ID')} {t('common:core.dataset.Dataset ID')}
</FormLabel> </FormLabel>
@@ -202,7 +190,7 @@ const Info = ({ datasetId }: { datasetId: string }) => {
<FormLabel fontSize={'mini'} fontWeight={'500'}> <FormLabel fontSize={'mini'} fontWeight={'500'}>
{t('common:core.ai.model.Vector Model')} {t('common:core.ai.model.Vector Model')}
</FormLabel> </FormLabel>
<Box pt={2} flex={[1, '0 0 320px']}> <Box pt={2}>
<AIModelSelector <AIModelSelector
w={'100%'} w={'100%'}
value={vectorModel.model} value={vectorModel.model}
@@ -231,12 +219,10 @@ const Info = ({ datasetId }: { datasetId: string }) => {
</Box> </Box>
<Flex mt={2} w={'100%'} alignItems={'center'}> <Flex mt={2} w={'100%'} alignItems={'center'}>
<FormLabel flex={['0 0 90px', '0 0 160px']} fontSize={'mini'} w={0} fontWeight={'500'}> <FormLabel flex={1} fontSize={'mini'} w={0} fontWeight={'500'}>
{t('common:core.Max Token')} {t('common:core.Max Token')}
</FormLabel> </FormLabel>
<Box flex={[1, '0 0 320px']} fontSize={'mini'}> <Box fontSize={'mini'}>{vectorModel.maxToken}</Box>
{vectorModel.maxToken}
</Box>
</Flex> </Flex>
<Box pt={5}> <Box pt={5}>
@@ -286,54 +272,33 @@ const Info = ({ datasetId }: { datasetId: string }) => {
{datasetDetail.permission.hasManagePer && ( {datasetDetail.permission.hasManagePer && (
<> <>
<MyDivider my={4} h={'2px'} maxW={'500px'} /> <MyDivider my={4} h={'2px'} maxW={'500px'} />
<Box overflow={'hidden'} h={openPermissionConfig ? 'auto' : '24px'}> <Box>
<Flex justify={'space-between'} alignItems={'center'} fontSize={'mini'} h={'24px'}> <MemberManager
<Box fontWeight={'500'} color={'myGray.900'} userSelect={'none'}> managePer={{
{t('common:permission.Permission config')} mode: 'all',
</Box> permission: datasetDetail.permission,
<MyIcon onGetCollaboratorList: () => getCollaboratorList(datasetId),
w={'16px'} permissionList: DatasetPermissionList,
_hover={{ color: 'primary.500', cursor: 'pointer' }} onUpdateCollaborators: (body) =>
color={'myGray.500'} postUpdateDatasetCollaborators({
name={openPermissionConfig ? 'core/chat/chevronUp' : 'core/chat/chevronDown'} ...body,
onClick={() => setOpenPermissionConfig(!openPermissionConfig)} datasetId
/> }),
</Flex> onDelOneCollaborator: async ({ groupId, tmbId }) => {
if (tmbId) {
<Box mt={3}> return deleteDatasetCollaborators({
<FormLabel fontWeight={'500'} fontSize={'mini'} pb={3} userSelect={'none'}> datasetId,
{t('common:permission.Default permission')} tmbId
</FormLabel> });
</Box> } else if (groupId) {
return deleteDatasetCollaborators({
<Box py={4}> datasetId,
<MemberManager groupId
managePer={{ });
mode: 'all',
permission: datasetDetail.permission,
onGetCollaboratorList: () => getCollaboratorList(datasetId),
permissionList: DatasetPermissionList,
onUpdateCollaborators: (body) =>
postUpdateDatasetCollaborators({
...body,
datasetId
}),
onDelOneCollaborator: async ({ groupId, tmbId }) => {
if (tmbId) {
return deleteDatasetCollaborators({
datasetId,
tmbId
});
} else if (groupId) {
return deleteDatasetCollaborators({
datasetId,
groupId
});
}
} }
}} }
/> }}
</Box> />
</Box> </Box>
</> </>
)} )}

View File

@@ -13,7 +13,6 @@ import { useTranslation } from 'next-i18next';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
@@ -272,31 +271,15 @@ const ExtraPlan = () => {
<MyIcon mr={2} name={'support/bill/shoppingCart'} w={'16px'} color={'primary.600'} /> <MyIcon mr={2} name={'support/bill/shoppingCart'} w={'16px'} color={'primary.600'} />
{t('common:support.wallet.buy_resource')} {t('common:support.wallet.buy_resource')}
</Flex> </Flex>
{/* <Flex mt={4} alignItems={'center'}> <Flex mt={4} alignItems={'center'}>
<Box flex={['0 0 100px', '1 0 0']}> <Box flex={['0 0 100px', '1 0 0']} color={'myGray.600'}>
{t('common:support.wallet.subscription.Month amount')} {t('common:support.wallet.subscription.Month amount')}
</Box> </Box>
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}> <Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
<NumberInput size={'sm'} flex={1} step={1} min={1} max={12} position={'relative'}> <Box>1</Box>
<NumberInputField <Box color={'myGray.600'}>{t('common:common.month')}</Box>
pr={'30px'}
{...registerExtraPoints('month', {
required: true,
min: 1,
max: 12,
valueAsNumber: true
})}
/>
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
<Box position={'absolute'} right={'20px'} color={'myGray.500'} fontSize={'xs'}>
{t('common:common.month')}
</Box>
</Flex> </Flex>
</Flex> */} </Flex>
<Flex mt={4} alignItems={'center'}> <Flex mt={4} alignItems={'center'}>
<Box flex={['0 0 100px', '1 0 0']} color={'myGray.600'}> <Box flex={['0 0 100px', '1 0 0']} color={'myGray.600'}>
{t('common:support.wallet.subscription.Update extra ai points')} {t('common:support.wallet.subscription.Update extra ai points')}

View File

@@ -84,21 +84,35 @@ export const useSpeech = (props?: OutLinkChatAuthProps & { appId?: string }) =>
mediaRecorder.current.onstop = async () => { mediaRecorder.current.onstop = async () => {
if (!cancelWhisperSignal.current) { if (!cancelWhisperSignal.current) {
const formData = new FormData(); const formData = new FormData();
let options = {}; const { options, filename } = (() => {
if (MediaRecorder.isTypeSupported('video/webm; codecs=vp9')) {
return {
options: { mimeType: 'video/webm; codecs=vp9' },
filename: 'recording.mp3'
};
}
if (MediaRecorder.isTypeSupported('video/webm')) {
return {
options: { type: 'video/webm' },
filename: 'recording.mp3'
};
}
if (MediaRecorder.isTypeSupported('video/mp4')) {
return {
options: { mimeType: 'video/mp4', videoBitsPerSecond: 100000 },
filename: 'recording.mp4'
};
}
return {
options: { type: 'video/webm' },
filename: 'recording.mp3'
};
})();
if (MediaRecorder.isTypeSupported('video/webm; codecs=vp9')) {
options = { mimeType: 'video/webm; codecs=vp9' };
} else if (MediaRecorder.isTypeSupported('video/webm')) {
options = { type: 'video/webm' };
} else if (MediaRecorder.isTypeSupported('video/mp4')) {
options = { mimeType: 'video/mp4', videoBitsPerSecond: 100000 };
} else {
console.error('no suitable mimetype found for this device');
}
const blob = new Blob(chunks, options); const blob = new Blob(chunks, options);
const duration = Math.round((Date.now() - startTimestamp.current) / 1000); const duration = Math.round((Date.now() - startTimestamp.current) / 1000);
console.log(options, filename, '=-=-');
formData.append('file', blob, 'recording.mp3'); formData.append('file', blob, filename);
formData.append( formData.append(
'data', 'data',
JSON.stringify({ JSON.stringify({