v4.5.1 (#417)
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { formatPrice } from '@fastgpt/common/bill/index';
|
||||
import { formatPrice } from '@fastgpt/global/common/bill/tools';
|
||||
import type { BillSchema } from '@/types/common/bill';
|
||||
import type { UserBillType } from '@/types/user';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/core/ai/constant';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant';
|
||||
import { ChatRoleEnum } from '@/constants/chat';
|
||||
import type { MessageItemType } from '@/types/core/chat/type';
|
||||
import type { AppModuleItemType } from '@/types/app';
|
||||
@@ -83,7 +83,10 @@ export const appModule2FlowNode = ({
|
||||
// replace item data
|
||||
const moduleItem: FlowModuleItemType = {
|
||||
...template,
|
||||
...item,
|
||||
flowType: item.flowType,
|
||||
moduleId: item.moduleId,
|
||||
name: item.name,
|
||||
showStatus: item.showStatus,
|
||||
inputs: concatInputs.map((templateInput) => {
|
||||
// use latest inputs
|
||||
const itemInput = item.inputs.find((item) => item.key === templateInput.key) || templateInput;
|
||||
|
||||
@@ -1,573 +0,0 @@
|
||||
import type { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||
import { chatModelList } from '@/web/common/store/static';
|
||||
import {
|
||||
FlowInputItemTypeEnum,
|
||||
FlowModuleTypeEnum,
|
||||
FlowValueTypeEnum,
|
||||
SpecialInputKeyEnum
|
||||
} from '@/constants/flow';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import type { SelectedDatasetType } from '@/types/core/dataset';
|
||||
import type { FlowInputItemType } from '@/types/core/app/flow';
|
||||
import type { AIChatProps } from '@/types/core/aiChat';
|
||||
import { getGuideModule, splitGuideModule } from '@/components/ChatBox/utils';
|
||||
|
||||
export type EditFormType = {
|
||||
chatModel: AIChatProps;
|
||||
kb: {
|
||||
list: SelectedDatasetType;
|
||||
searchSimilarity: number;
|
||||
searchLimit: number;
|
||||
searchEmptyText: string;
|
||||
};
|
||||
guide: {
|
||||
welcome: {
|
||||
text: string;
|
||||
};
|
||||
};
|
||||
variables: VariableItemType[];
|
||||
questionGuide: boolean;
|
||||
};
|
||||
export const getDefaultAppForm = (): EditFormType => {
|
||||
const defaultChatModel = chatModelList[0];
|
||||
|
||||
return {
|
||||
chatModel: {
|
||||
model: defaultChatModel?.model,
|
||||
systemPrompt: '',
|
||||
temperature: 0,
|
||||
[SystemInputEnum.isResponseAnswerText]: true,
|
||||
quotePrompt: '',
|
||||
quoteTemplate: '',
|
||||
maxToken: defaultChatModel ? defaultChatModel.maxToken / 2 : 4000,
|
||||
frequency: 0.5,
|
||||
presence: -0.5
|
||||
},
|
||||
kb: {
|
||||
list: [],
|
||||
searchSimilarity: 0.4,
|
||||
searchLimit: 5,
|
||||
searchEmptyText: ''
|
||||
},
|
||||
guide: {
|
||||
welcome: {
|
||||
text: ''
|
||||
}
|
||||
},
|
||||
variables: [],
|
||||
questionGuide: false
|
||||
};
|
||||
};
|
||||
|
||||
export const appModules2Form = (modules: AppModuleItemType[]) => {
|
||||
const defaultAppForm = getDefaultAppForm();
|
||||
const updateVal = ({
|
||||
formKey,
|
||||
inputs,
|
||||
key
|
||||
}: {
|
||||
formKey: string;
|
||||
inputs: FlowInputItemType[];
|
||||
key: string;
|
||||
}) => {
|
||||
const propertyPath = formKey.split('.');
|
||||
let currentObj: any = defaultAppForm;
|
||||
for (let i = 0; i < propertyPath.length - 1; i++) {
|
||||
currentObj = currentObj[propertyPath[i]];
|
||||
}
|
||||
|
||||
const val =
|
||||
inputs.find((item) => item.key === key)?.value ||
|
||||
currentObj[propertyPath[propertyPath.length - 1]];
|
||||
|
||||
currentObj[propertyPath[propertyPath.length - 1]] = val;
|
||||
};
|
||||
|
||||
modules.forEach((module) => {
|
||||
if (module.flowType === FlowModuleTypeEnum.chatNode) {
|
||||
updateVal({
|
||||
formKey: 'chatModel.model',
|
||||
inputs: module.inputs,
|
||||
key: 'model'
|
||||
});
|
||||
updateVal({
|
||||
formKey: 'chatModel.temperature',
|
||||
inputs: module.inputs,
|
||||
key: 'temperature'
|
||||
});
|
||||
updateVal({
|
||||
formKey: 'chatModel.maxToken',
|
||||
inputs: module.inputs,
|
||||
key: 'maxToken'
|
||||
});
|
||||
updateVal({
|
||||
formKey: 'chatModel.systemPrompt',
|
||||
inputs: module.inputs,
|
||||
key: 'systemPrompt'
|
||||
});
|
||||
updateVal({
|
||||
formKey: 'chatModel.quoteTemplate',
|
||||
inputs: module.inputs,
|
||||
key: 'quoteTemplate'
|
||||
});
|
||||
updateVal({
|
||||
formKey: 'chatModel.quotePrompt',
|
||||
inputs: module.inputs,
|
||||
key: 'quotePrompt'
|
||||
});
|
||||
} else if (module.flowType === FlowModuleTypeEnum.kbSearchNode) {
|
||||
updateVal({
|
||||
formKey: 'kb.list',
|
||||
inputs: module.inputs,
|
||||
key: 'kbList'
|
||||
});
|
||||
updateVal({
|
||||
formKey: 'kb.searchSimilarity',
|
||||
inputs: module.inputs,
|
||||
key: 'similarity'
|
||||
});
|
||||
updateVal({
|
||||
formKey: 'kb.searchLimit',
|
||||
inputs: module.inputs,
|
||||
key: 'limit'
|
||||
});
|
||||
// empty text
|
||||
const emptyOutputs = module.outputs.find((item) => item.key === 'isEmpty')?.targets || [];
|
||||
const emptyOutput = emptyOutputs[0];
|
||||
if (emptyOutput) {
|
||||
const target = modules.find((item) => item.moduleId === emptyOutput.moduleId);
|
||||
defaultAppForm.kb.searchEmptyText =
|
||||
target?.inputs?.find((item) => item.key === SpecialInputKeyEnum.answerText)?.value || '';
|
||||
}
|
||||
} else if (module.flowType === FlowModuleTypeEnum.userGuide) {
|
||||
const { welcomeText, variableModules, questionGuide } = splitGuideModule(
|
||||
getGuideModule(modules)
|
||||
);
|
||||
if (welcomeText) {
|
||||
defaultAppForm.guide.welcome = {
|
||||
text: welcomeText
|
||||
};
|
||||
}
|
||||
|
||||
defaultAppForm.variables = variableModules;
|
||||
defaultAppForm.questionGuide = !!questionGuide;
|
||||
}
|
||||
});
|
||||
|
||||
return defaultAppForm;
|
||||
};
|
||||
|
||||
const chatModelInput = (formData: EditFormType): FlowInputItemType[] => [
|
||||
{
|
||||
key: 'model',
|
||||
value: formData.chatModel.model,
|
||||
type: 'custom',
|
||||
label: '对话模型',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
value: formData.chatModel.temperature,
|
||||
type: 'slider',
|
||||
label: '温度',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
value: formData.chatModel.maxToken,
|
||||
type: 'custom',
|
||||
label: '回复上限',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
value: formData.chatModel.systemPrompt || '',
|
||||
type: 'textarea',
|
||||
label: '系统提示词',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.isResponseAnswerText,
|
||||
value: true,
|
||||
type: 'hidden',
|
||||
label: '返回AI内容',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quoteTemplate',
|
||||
value: formData.chatModel.quoteTemplate || '',
|
||||
type: 'hidden',
|
||||
label: '引用内容模板',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
value: formData.chatModel.quotePrompt || '',
|
||||
type: 'hidden',
|
||||
label: '引用内容提示词',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
label: '触发器',
|
||||
connected: formData.kb.list.length > 0 && !!formData.kb.searchEmptyText
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
type: 'target',
|
||||
label: '引用内容',
|
||||
connected: formData.kb.list.length > 0
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: 'target',
|
||||
label: '用户问题',
|
||||
connected: true
|
||||
}
|
||||
];
|
||||
const userGuideTemplate = (formData: EditFormType): AppModuleItemType[] => [
|
||||
{
|
||||
name: '用户引导',
|
||||
flowType: FlowModuleTypeEnum.userGuide,
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.welcomeText,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
label: '开场白',
|
||||
value: formData.guide.welcome.text
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.variables,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
label: '对话框变量',
|
||||
value: formData.variables
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.questionGuide,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
label: '问题引导',
|
||||
value: formData.questionGuide
|
||||
}
|
||||
],
|
||||
outputs: [],
|
||||
position: {
|
||||
x: 447.98520778293346,
|
||||
y: 721.4016845336229
|
||||
},
|
||||
moduleId: 'userGuide'
|
||||
}
|
||||
];
|
||||
const simpleChatTemplate = (formData: EditFormType): AppModuleItemType[] => [
|
||||
{
|
||||
name: '用户问题(对话入口)',
|
||||
flowType: FlowModuleTypeEnum.questionInput,
|
||||
inputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
connected: true,
|
||||
label: '用户问题',
|
||||
type: 'target'
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 464.32198615344566,
|
||||
y: 1602.2698463081606
|
||||
},
|
||||
moduleId: 'userChatInput'
|
||||
},
|
||||
{
|
||||
name: '聊天记录',
|
||||
flowType: FlowModuleTypeEnum.historyNode,
|
||||
inputs: [
|
||||
{
|
||||
key: 'maxContext',
|
||||
value: 6,
|
||||
connected: true,
|
||||
type: 'numberInput',
|
||||
label: '最长记录数'
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'hidden',
|
||||
label: '聊天记录',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'history',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'history'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 452.5466249541586,
|
||||
y: 1276.3930310334215
|
||||
},
|
||||
moduleId: 'history'
|
||||
},
|
||||
{
|
||||
name: 'AI 对话',
|
||||
flowType: FlowModuleTypeEnum.chatNode,
|
||||
inputs: chatModelInput(formData),
|
||||
showStatus: true,
|
||||
outputs: [
|
||||
{
|
||||
key: 'answerText',
|
||||
label: 'AI回复',
|
||||
description: '直接响应,无需配置',
|
||||
type: 'hidden',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'finish',
|
||||
label: '回复结束',
|
||||
description: 'AI 回复完成后触发',
|
||||
valueType: 'boolean',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 981.9682828103937,
|
||||
y: 890.014595014464
|
||||
},
|
||||
moduleId: 'chatModule'
|
||||
}
|
||||
];
|
||||
const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
|
||||
{
|
||||
name: '用户问题(对话入口)',
|
||||
flowType: FlowModuleTypeEnum.questionInput,
|
||||
inputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
label: '用户问题',
|
||||
type: 'target',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'userChatInput'
|
||||
},
|
||||
{
|
||||
moduleId: 'kbSearch',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 464.32198615344566,
|
||||
y: 1602.2698463081606
|
||||
},
|
||||
moduleId: 'userChatInput'
|
||||
},
|
||||
{
|
||||
name: '聊天记录',
|
||||
flowType: FlowModuleTypeEnum.historyNode,
|
||||
inputs: [
|
||||
{
|
||||
key: 'maxContext',
|
||||
value: 6,
|
||||
connected: true,
|
||||
type: 'numberInput',
|
||||
label: '最长记录数'
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'hidden',
|
||||
label: '聊天记录',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'history',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'history'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 452.5466249541586,
|
||||
y: 1276.3930310334215
|
||||
},
|
||||
moduleId: 'history'
|
||||
},
|
||||
{
|
||||
name: '知识库搜索',
|
||||
flowType: FlowModuleTypeEnum.kbSearchNode,
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
{
|
||||
key: 'kbList',
|
||||
value: formData.kb.list,
|
||||
type: FlowInputItemTypeEnum.custom,
|
||||
label: '关联的知识库',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'similarity',
|
||||
value: formData.kb.searchSimilarity,
|
||||
type: FlowInputItemTypeEnum.slider,
|
||||
label: '相似度',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'limit',
|
||||
value: formData.kb.searchLimit,
|
||||
type: FlowInputItemTypeEnum.slider,
|
||||
label: '单次搜索上限',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
label: '触发器',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
label: '用户问题',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'isEmpty',
|
||||
targets: formData.kb.searchEmptyText
|
||||
? [
|
||||
{
|
||||
moduleId: 'emptyText',
|
||||
key: 'switch'
|
||||
}
|
||||
]
|
||||
: []
|
||||
},
|
||||
{
|
||||
key: 'unEmpty',
|
||||
targets: formData.kb.searchEmptyText
|
||||
? [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'switch'
|
||||
}
|
||||
]
|
||||
: []
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'quoteQA'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 956.0838440206068,
|
||||
y: 887.462827870246
|
||||
},
|
||||
moduleId: 'kbSearch'
|
||||
},
|
||||
...(formData.kb.searchEmptyText
|
||||
? [
|
||||
{
|
||||
name: '指定回复',
|
||||
flowType: FlowModuleTypeEnum.answerNode,
|
||||
inputs: [
|
||||
{
|
||||
key: 'switch',
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
label: '触发器',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: SpecialInputKeyEnum.answerText,
|
||||
value: formData.kb.searchEmptyText,
|
||||
type: FlowInputItemTypeEnum.textarea,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
label: '回复的内容',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [],
|
||||
position: {
|
||||
x: 1553.5815811529146,
|
||||
y: 637.8753731306779
|
||||
},
|
||||
moduleId: 'emptyText'
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: 'AI 对话',
|
||||
flowType: FlowModuleTypeEnum.chatNode,
|
||||
inputs: chatModelInput(formData),
|
||||
showStatus: true,
|
||||
outputs: [
|
||||
{
|
||||
key: 'answerText',
|
||||
label: 'AI回复',
|
||||
description: '直接响应,无需配置',
|
||||
type: 'hidden',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'finish',
|
||||
label: '回复结束',
|
||||
description: 'AI 回复完成后触发',
|
||||
valueType: 'boolean',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 1551.71405495818,
|
||||
y: 977.4911578918461
|
||||
},
|
||||
moduleId: 'chatModule'
|
||||
}
|
||||
];
|
||||
|
||||
export const appForm2Modules = (formData: EditFormType) => {
|
||||
const modules = [
|
||||
...userGuideTemplate(formData),
|
||||
...(formData.kb.list.length > 0 ? kbTemplate(formData) : simpleChatTemplate(formData))
|
||||
];
|
||||
|
||||
return modules as AppModuleItemType[];
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ChatItemType } from '@/types/chat';
|
||||
import { ChatRoleEnum } from '@/constants/chat';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/core/ai/constant';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant';
|
||||
import type { MessageItemType } from '@/types/core/chat/type';
|
||||
|
||||
const chat2Message = {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,92 +0,0 @@
|
||||
/* Only the token of gpt-3.5-turbo is used */
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { Tiktoken } from 'js-tiktoken/lite';
|
||||
import { adaptChat2GptMessages } from '../adapt/message';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/core/ai/constant';
|
||||
import encodingJson from './cl100k_base.json';
|
||||
|
||||
/* init tikToken obj */
|
||||
export function getTikTokenEnc() {
|
||||
if (typeof window !== 'undefined' && window.TikToken) {
|
||||
return window.TikToken;
|
||||
}
|
||||
if (typeof global !== 'undefined' && global.TikToken) {
|
||||
return global.TikToken;
|
||||
}
|
||||
|
||||
const enc = new Tiktoken(encodingJson);
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.TikToken = enc;
|
||||
}
|
||||
if (typeof global !== 'undefined') {
|
||||
global.TikToken = enc;
|
||||
}
|
||||
|
||||
return enc;
|
||||
}
|
||||
|
||||
/* count one prompt tokens */
|
||||
export function countPromptTokens(prompt = '', role: `${ChatCompletionRequestMessageRoleEnum}`) {
|
||||
const enc = getTikTokenEnc();
|
||||
const text = `${role}\n${prompt}`;
|
||||
try {
|
||||
const encodeText = enc.encode(text);
|
||||
return encodeText.length + 3; // 补充 role 估算值
|
||||
} catch (error) {
|
||||
return text.length;
|
||||
}
|
||||
}
|
||||
|
||||
/* count messages tokens */
|
||||
export function countMessagesTokens({ messages }: { messages: ChatItemType[] }) {
|
||||
const adaptMessages = adaptChat2GptMessages({ messages, reserveId: true });
|
||||
|
||||
let totalTokens = 0;
|
||||
for (let i = 0; i < adaptMessages.length; i++) {
|
||||
const item = adaptMessages[i];
|
||||
const tokens = countPromptTokens(item.content, item.role);
|
||||
totalTokens += tokens;
|
||||
}
|
||||
|
||||
return totalTokens;
|
||||
}
|
||||
|
||||
export function sliceTextByTokens({ text, length }: { text: string; length: number }) {
|
||||
const enc = getTikTokenEnc();
|
||||
|
||||
try {
|
||||
const encodeText = enc.encode(text);
|
||||
return enc.decode(encodeText.slice(0, length));
|
||||
} catch (error) {
|
||||
return text.slice(0, length);
|
||||
}
|
||||
}
|
||||
|
||||
/* slice messages from top to bottom by maxTokens */
|
||||
export function sliceMessagesTB({
|
||||
messages,
|
||||
maxTokens
|
||||
}: {
|
||||
messages: ChatItemType[];
|
||||
maxTokens: number;
|
||||
}) {
|
||||
const adaptMessages = adaptChat2GptMessages({ messages, reserveId: true });
|
||||
let reduceTokens = maxTokens;
|
||||
let result: ChatItemType[] = [];
|
||||
|
||||
for (let i = 0; i < adaptMessages.length; i++) {
|
||||
const item = adaptMessages[i];
|
||||
|
||||
const tokens = countPromptTokens(item.content, item.role);
|
||||
reduceTokens -= tokens;
|
||||
|
||||
if (reduceTokens > 0) {
|
||||
result.push(messages[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result.length === 0 && messages[0] ? [messages[0]] : result;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
replace {{variable}} to value
|
||||
*/
|
||||
export function replaceVariable(text: string, obj: Record<string, string | number>) {
|
||||
for (const key in obj) {
|
||||
const val = obj[key];
|
||||
if (typeof val !== 'string') continue;
|
||||
|
||||
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), val);
|
||||
}
|
||||
return text || '';
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import { getErrText } from './tools';
|
||||
import { countPromptTokens } from './common/tiktoken';
|
||||
|
||||
/**
|
||||
* text split into chunks
|
||||
* maxLen - one chunk len. max: 3500
|
||||
* overlapLen - The size of the before and after Text
|
||||
* maxLen > overlapLen
|
||||
*/
|
||||
export const splitText2Chunks = ({ text = '', maxLen }: { text: string; maxLen: number }) => {
|
||||
const overlapLen = Math.floor(maxLen * 0.2); // Overlap length
|
||||
const tempMarker = 'SPLIT_HERE_SPLIT_HERE';
|
||||
|
||||
const stepReg: Record<number, RegExp> = {
|
||||
0: /(\n\n)/g,
|
||||
1: /([\n])/g,
|
||||
2: /([。]|\.\s)/g,
|
||||
3: /([!?]|!\s|\?\s)/g,
|
||||
4: /([;]|;\s)/g,
|
||||
5: /([,]|,\s)/g
|
||||
};
|
||||
|
||||
const splitTextRecursively = ({ text = '', step }: { text: string; step: number }) => {
|
||||
if (text.length <= maxLen) {
|
||||
return [text];
|
||||
}
|
||||
const reg = stepReg[step];
|
||||
|
||||
if (!reg) {
|
||||
// use slice-maxLen to split text
|
||||
const chunks: string[] = [];
|
||||
let chunk = '';
|
||||
for (let i = 0; i < text.length; i += maxLen - overlapLen) {
|
||||
chunk = text.slice(i, i + maxLen);
|
||||
chunks.push(chunk);
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
// split text by delimiters
|
||||
const splitTexts = text
|
||||
.replace(reg, `$1${tempMarker}`)
|
||||
.split(`${tempMarker}`)
|
||||
.filter((part) => part);
|
||||
|
||||
let chunks: string[] = [];
|
||||
let preChunk = '';
|
||||
let chunk = '';
|
||||
for (let i = 0; i < splitTexts.length; i++) {
|
||||
let text = splitTexts[i];
|
||||
// chunk over size
|
||||
if (text.length > maxLen) {
|
||||
const innerChunks = splitTextRecursively({ text, step: step + 1 });
|
||||
if (innerChunks.length === 0) continue;
|
||||
// If the last chunk is too small, it is merged into the next chunk
|
||||
if (innerChunks[innerChunks.length - 1].length <= maxLen * 0.5) {
|
||||
text = innerChunks.pop() || '';
|
||||
chunks = chunks.concat(innerChunks);
|
||||
} else {
|
||||
chunks = chunks.concat(innerChunks);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
chunk += text;
|
||||
// size over lapLen, push it to next chunk
|
||||
if (chunk.length > maxLen - overlapLen) {
|
||||
preChunk += text;
|
||||
}
|
||||
if (chunk.length >= maxLen) {
|
||||
chunks.push(chunk);
|
||||
chunk = preChunk;
|
||||
preChunk = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk && !chunks[chunks.length - 1].endsWith(chunk)) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
|
||||
try {
|
||||
const chunks = splitTextRecursively({ text, step: 0 });
|
||||
|
||||
const tokens = chunks.reduce((sum, chunk) => sum + countPromptTokens(chunk, 'system'), 0);
|
||||
|
||||
return {
|
||||
chunks,
|
||||
tokens
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error(getErrText(err));
|
||||
}
|
||||
};
|
||||
@@ -80,22 +80,6 @@ export const formatTimeToChatTime = (time: Date) => {
|
||||
return target.format('YYYY/M/D');
|
||||
};
|
||||
|
||||
export const formatFileSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
export const getErrText = (err: any, def = '') => {
|
||||
const msg: string = typeof err === 'string' ? err : err?.message || def || '';
|
||||
msg && console.log('error =>', msg);
|
||||
return msg;
|
||||
};
|
||||
|
||||
export const delay = (ms: number) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { loginOut } from '@/web/support/api/user';
|
||||
import timezones from 'timezones-list';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
const tokenKey = 'token';
|
||||
export const clearToken = () => {
|
||||
try {
|
||||
loginOut();
|
||||
localStorage.removeItem(tokenKey);
|
||||
} catch (error) {
|
||||
error;
|
||||
}
|
||||
};
|
||||
|
||||
export const setToken = (token: string) => {
|
||||
localStorage.setItem(tokenKey, token);
|
||||
};
|
||||
export const getToken = () => {
|
||||
return localStorage.getItem(tokenKey) || '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the offset from UTC in hours for the current locale.
|
||||
* @param {string} timeZone Timezone to get offset for
|
||||
* @returns {number} The offset from UTC in hours.
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
export const getTimezoneOffset = (timeZone: string): number => {
|
||||
const now = new Date();
|
||||
const tzString = now.toLocaleString('en-US', {
|
||||
timeZone
|
||||
});
|
||||
const localString = now.toLocaleString('en-US');
|
||||
const diff = (Date.parse(localString) - Date.parse(tzString)) / 3600000;
|
||||
const offset = diff + now.getTimezoneOffset() / 60;
|
||||
return -offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a list of timezones sorted by their offset from UTC.
|
||||
* @returns {object[]} A list of the given timezones sorted by their offset from UTC.
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
export const timezoneList = () => {
|
||||
const result = timezones
|
||||
.map((timezone) => {
|
||||
try {
|
||||
let display = dayjs().tz(timezone.tzCode).format('Z');
|
||||
|
||||
return {
|
||||
name: `(UTC${display}) ${timezone.tzCode}`,
|
||||
value: timezone.tzCode,
|
||||
time: getTimezoneOffset(timezone.tzCode)
|
||||
};
|
||||
} catch (e) {}
|
||||
})
|
||||
.filter((item) => item);
|
||||
|
||||
result.sort((a, b) => {
|
||||
if (!a || !b) return 0;
|
||||
if (a.time > b.time) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (b.time > a.time) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'UTC',
|
||||
time: 0,
|
||||
value: 'UTC'
|
||||
},
|
||||
...result
|
||||
] as {
|
||||
name: string;
|
||||
value: string;
|
||||
time: number;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const getSystemTime = (timeZone: string) => {
|
||||
const timezoneDiff = getTimezoneOffset(timeZone);
|
||||
const now = Date.now();
|
||||
const targetTime = now + timezoneDiff * 60 * 60 * 1000;
|
||||
return dayjs(targetTime).format('YYYY-MM-DD HH:mm:ss');
|
||||
};
|
||||
Reference in New Issue
Block a user