thay doi alibaba module mac dinh - chebichat

This commit is contained in:
quangdn-ght 2025-06-24 09:18:27 +07:00
parent 673f907ea4
commit 861d854e16
13 changed files with 204 additions and 214 deletions

View File

@ -20,7 +20,11 @@ async function handle(
req: NextRequest, req: NextRequest,
{ params }: { params: { provider: string; path: string[] } }, { params }: { params: { provider: string; path: string[] } },
) { ) {
// Handle OPTIONS request for CORS preflight
// params.provider = MODEL_PROVIDER;
const apiPath = `/api/${params.provider}`; const apiPath = `/api/${params.provider}`;
console.log(`[${params.provider} Route] params `, params); console.log(`[${params.provider} Route] params `, params);
switch (apiPath) { switch (apiPath) {
case ApiPath.Azure: case ApiPath.Azure:

View File

@ -1,14 +1,8 @@
import { getServerSideConfig } from "@/app/config/server"; import { getServerSideConfig } from "@/app/config/server";
import { import { ALIBABA_BASE_URL, ApiPath, ModelProvider } from "@/app/constant";
ALIBABA_BASE_URL,
ApiPath,
ModelProvider,
ServiceProvider,
} from "@/app/constant";
import { prettyObject } from "@/app/utils/format"; import { prettyObject } from "@/app/utils/format";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/app/api/auth"; import { auth } from "@/app/api/auth";
import { isModelNotavailableInServer } from "@/app/utils/model";
const serverConfig = getServerSideConfig(); const serverConfig = getServerSideConfig();
@ -83,28 +77,36 @@ async function request(req: NextRequest) {
if (serverConfig.customModels && req.body) { if (serverConfig.customModels && req.body) {
try { try {
const clonedBody = await req.text(); const clonedBody = await req.text();
fetchOptions.body = clonedBody; let jsonBody: any = {};
const jsonBody = JSON.parse(clonedBody) as { model?: string }; try {
jsonBody = JSON.parse(clonedBody);
delete jsonBody.model; // Remove the model key
fetchOptions.body = JSON.stringify(jsonBody);
} catch (e) {
fetchOptions.body = clonedBody; // fallback if not JSON
}
console.log("[Alibaba] request body", fetchOptions.body);
// not undefined and is false // not undefined and is false
if ( // if (
isModelNotavailableInServer( // isModelNotavailableInServer(
serverConfig.customModels, // serverConfig.customModels,
jsonBody?.model as string, // jsonBody?.model as string,
ServiceProvider.Alibaba as string, // ServiceProvider.Alibaba as string,
) // )
) { // ) {
return NextResponse.json( // return NextResponse.json(
{ // {
error: true, // error: true,
message: `you are not allowed to use ${jsonBody?.model} model`, // message: `you are not allowed to use ${jsonBody?.model} model`,
}, // },
{ // {
status: 403, // status: 403,
}, // },
); // );
} // }
} catch (e) { } catch (e) {
console.error(`[Alibaba] filter`, e); console.error(`[Alibaba] filter`, e);
} }

View File

@ -18,7 +18,6 @@ import {
LLMModel, LLMModel,
SpeechOptions, SpeechOptions,
MultimodalContent, MultimodalContent,
MultimodalContentForAlibaba,
} from "../api"; } from "../api";
import { getClientConfig } from "@/app/config/client"; import { getClientConfig } from "@/app/config/client";
import { import {
@ -156,11 +155,13 @@ export class QwenApi implements LLMApi {
); );
if (shouldStream) { if (shouldStream) {
// Lấy danh sách các công cụ (tools) và hàm (funcs) từ plugin hiện tại của phiên chat
const [tools, funcs] = usePluginStore const [tools, funcs] = usePluginStore
.getState() .getState()
.getAsTools( .getAsTools(
useChatStore.getState().currentSession().mask?.plugin || [], useChatStore.getState().currentSession().mask?.plugin || [],
); );
// Gọi hàm streamWithThink để xử lý chat dạng stream (dòng sự kiện server-sent events)
return streamWithThink( return streamWithThink(
chatPath, chatPath,
requestPayload, requestPayload,
@ -168,44 +169,19 @@ export class QwenApi implements LLMApi {
tools as any, tools as any,
funcs, funcs,
controller, controller,
// parseSSE // Updated SSE parse callback for new JSON structure
(text: string, runTools: ChatMessageTool[]) => { (text: string, runTools: ChatMessageTool[]) => {
// console.log("parseSSE", text, runTools); // Parse the JSON response
const json = JSON.parse(text); const json = JSON.parse(text);
const choices = json.output.choices as Array<{
message: {
content: string | null | MultimodalContentForAlibaba[];
tool_calls: ChatMessageTool[];
reasoning_content: string | null;
};
}>;
if (!choices?.length) return { isThinking: false, content: "" }; // console.log("[Alibaba] SSE response", json);
const tool_calls = choices[0]?.message?.tool_calls; // Extract content from the new structure
if (tool_calls?.length > 0) { const output = json.output;
const index = tool_calls[0]?.index; const content = output?.text ?? "";
const id = tool_calls[0]?.id; const reasoning = output?.reasoning_content ?? ""; // If exists in your new structure
const args = tool_calls[0]?.function?.arguments;
if (id) {
runTools.push({
id,
type: tool_calls[0]?.type,
function: {
name: tool_calls[0]?.function?.name as string,
arguments: args,
},
});
} else {
// @ts-ignore
runTools[index]["function"]["arguments"] += args;
}
}
const reasoning = choices[0]?.message?.reasoning_content; // If both are empty, return default
const content = choices[0]?.message?.content;
// Skip if both content and reasoning_content are empty or null
if ( if (
(!reasoning || reasoning.length === 0) && (!reasoning || reasoning.length === 0) &&
(!content || content.length === 0) (!content || content.length === 0)
@ -216,31 +192,34 @@ export class QwenApi implements LLMApi {
}; };
} }
// If reasoning_content exists, treat as "thinking"
if (reasoning && reasoning.length > 0) { if (reasoning && reasoning.length > 0) {
return { return {
isThinking: true, isThinking: true,
content: reasoning, content: reasoning,
}; };
} else if (content && content.length > 0) { }
// Otherwise, return the main content
else if (content && content.length > 0) {
return { return {
isThinking: false, isThinking: false,
content: Array.isArray(content) content: content,
? content.map((item) => item.text).join(",")
: content,
}; };
} }
// Fallback
return { return {
isThinking: false, isThinking: false,
content: "", content: "",
}; };
}, },
// processToolMessage, include tool_calls message and tool call results // Hàm xử lý message liên quan đến tool_call và kết quả trả về từ tool_call
( (
requestPayload: RequestPayload, requestPayload: RequestPayload,
toolCallMessage: any, toolCallMessage: any,
toolCallResult: any[], toolCallResult: any[],
) => { ) => {
// Thêm message gọi tool và kết quả trả về vào cuối mảng messages trong payload gửi lên API
requestPayload?.input?.messages?.splice( requestPayload?.input?.messages?.splice(
requestPayload?.input?.messages?.length, requestPayload?.input?.messages?.length,
0, 0,
@ -248,7 +227,7 @@ export class QwenApi implements LLMApi {
...toolCallResult, ...toolCallResult,
); );
}, },
options, options, // Các tuỳ chọn khác cho hàm streamWithThink
); );
} else { } else {
const res = await fetch(chatPath, chatPayload); const res = await fetch(chatPath, chatPayload);

View File

@ -151,7 +151,8 @@ export class DeepSeekApi implements LLMApi {
controller, controller,
// parseSSE // parseSSE
(text: string, runTools: ChatMessageTool[]) => { (text: string, runTools: ChatMessageTool[]) => {
// console.log("parseSSE", text, runTools); console.log("parseSSE", text, runTools);
const json = JSON.parse(text); const json = JSON.parse(text);
const choices = json.choices as Array<{ const choices = json.choices as Array<{
delta: { delta: {

View File

@ -153,81 +153,35 @@ export class SiliconflowApi implements LLMApi {
tools as any, tools as any,
funcs, funcs,
controller, controller,
// parseSSE // parseSSE mới cho SiliconFlow response
(text: string, runTools: ChatMessageTool[]) => { (text: string, runTools: ChatMessageTool[]) => {
// console.log("parseSSE", text, runTools); // Parse chuỗi JSON trả về thành đối tượng
const json = JSON.parse(text); const json = JSON.parse(text);
const choices = json.choices as Array<{
delta: {
content: string | null;
tool_calls: ChatMessageTool[];
reasoning_content: string | null;
};
}>;
const tool_calls = choices[0]?.delta?.tool_calls;
if (tool_calls?.length > 0) {
const index = tool_calls[0]?.index;
const id = tool_calls[0]?.id;
const args = tool_calls[0]?.function?.arguments;
if (id) {
runTools.push({
id,
type: tool_calls[0]?.type,
function: {
name: tool_calls[0]?.function?.name as string,
arguments: args,
},
});
} else {
// @ts-ignore
runTools[index]["function"]["arguments"] += args;
}
}
const reasoning = choices[0]?.delta?.reasoning_content;
const content = choices[0]?.delta?.content;
// Skip if both content and reasoning_content are empty or null // Lấy nội dung trả lời từ output.text
if ( const content = json?.output?.text ?? "";
(!reasoning || reasoning.length === 0) &&
(!content || content.length === 0) // Nếu không có nội dung trả lời, trả về trạng thái không suy nghĩ và nội dung rỗng
) { if (!content || content.length === 0) {
return { return {
isThinking: false, isThinking: false,
content: "", content: "",
}; };
} }
if (reasoning && reasoning.length > 0) { // Trả về trạng thái không suy nghĩ và nội dung trả lời
return {
isThinking: true,
content: reasoning,
};
} else if (content && content.length > 0) {
return {
isThinking: false,
content: content,
};
}
return { return {
isThinking: false, isThinking: false,
content: "", content: content,
}; };
}, },
// processToolMessage, include tool_calls message and tool call results // processToolMessage: SiliconFlow không có tool_call nên giữ nguyên hoặc để rỗng
( (
requestPayload: RequestPayload, requestPayload: RequestPayload,
toolCallMessage: any, toolCallMessage: any,
toolCallResult: any[], toolCallResult: any[],
) => { ) => {
// @ts-ignore // Không cần xử lý tool_call, có thể để trống hoặc giữ nguyên nếu muốn tương thích
requestPayload?.messages?.splice(
// @ts-ignore
requestPayload?.messages?.length,
0,
toolCallMessage,
...toolCallResult,
);
}, },
options, options,
); );

View File

@ -58,6 +58,7 @@ declare global {
// alibaba only // alibaba only
ALIBABA_URL?: string; ALIBABA_URL?: string;
ALIBABA_API_KEY?: string; ALIBABA_API_KEY?: string;
ALIBABA_APP_ID?: string; // alibaba app id, used for some models
// tencent only // tencent only
TENCENT_URL?: string; TENCENT_URL?: string;
@ -210,6 +211,7 @@ export const getServerSideConfig = () => {
isAlibaba, isAlibaba,
alibabaUrl: process.env.ALIBABA_URL, alibabaUrl: process.env.ALIBABA_URL,
alibabaApiKey: getApiKey(process.env.ALIBABA_API_KEY), alibabaApiKey: getApiKey(process.env.ALIBABA_API_KEY),
alibabaAppId: process.env.ALIBABA_APP_ID,
isTencent, isTencent,
tencentUrl: process.env.TENCENT_URL, tencentUrl: process.env.TENCENT_URL,

View File

@ -21,7 +21,7 @@ export const BAIDU_OATUH_URL = `${BAIDU_BASE_URL}/oauth/2.0/token`;
export const BYTEDANCE_BASE_URL = "https://ark.cn-beijing.volces.com"; export const BYTEDANCE_BASE_URL = "https://ark.cn-beijing.volces.com";
export const ALIBABA_BASE_URL = "https://dashscope.aliyuncs.com/api/"; export const ALIBABA_BASE_URL = "https://dashscope-intl.aliyuncs.com";
export const TENCENT_BASE_URL = "https://hunyuan.tencentcloudapi.com"; export const TENCENT_BASE_URL = "https://hunyuan.tencentcloudapi.com";
@ -39,6 +39,9 @@ export const SILICONFLOW_BASE_URL = "https://api.siliconflow.cn";
export const CACHE_URL_PREFIX = "/api/cache"; export const CACHE_URL_PREFIX = "/api/cache";
export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`; export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`;
export const ALIBABA_APP_ID = "95072bcf71bf4469a25c45c31e76f37a"; // default alibaba app id, used for some models
export const MODEL_PROVIDER = "alibaba";
export enum Path { export enum Path {
Home = "/", Home = "/",
Chat = "/chat", Chat = "/chat",
@ -222,10 +225,13 @@ export const ByteDance = {
export const Alibaba = { export const Alibaba = {
ExampleEndpoint: ALIBABA_BASE_URL, ExampleEndpoint: ALIBABA_BASE_URL,
ChatPath: (modelName: string) => { ChatPath: (modelName: string) => {
const URL = `api/v1/apps/${ALIBABA_APP_ID}/completion`;
if (modelName.includes("vl") || modelName.includes("omni")) { if (modelName.includes("vl") || modelName.includes("omni")) {
return "v1/services/aigc/multimodal-generation/generation"; return "v1/services/aigc/multimodal-generation/generation";
} }
return `v1/services/aigc/text-generation/generation`; // return `v1/services/aigc/text-generation/generation`;
return URL;
}, },
}; };
@ -681,20 +687,21 @@ const siliconflowModels = [
let seq = 1000; // 内置的模型序号生成器从1000开始 let seq = 1000; // 内置的模型序号生成器从1000开始
export const DEFAULT_MODELS = [ export const DEFAULT_MODELS = [
...openaiModels.map((name) => ({ ...alibabaModes.map((name) => ({
name, name,
available: true, available: true, // 默认可用
sorted: seq++, // Global sequence sort(index) sorted: seq++,
provider: { provider: {
id: "openai", id: "alibaba",
providerName: "OpenAI", providerName: "Alibaba",
providerType: "openai", providerType: "alibaba",
sorted: 1, // 这里是固定的,确保顺序与之前内置的版本一致 sorted: 1,
}, },
})), })),
...openaiModels.map((name) => ({ ...openaiModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "azure", id: "azure",
@ -705,7 +712,7 @@ export const DEFAULT_MODELS = [
})), })),
...googleModels.map((name) => ({ ...googleModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "google", id: "google",
@ -716,7 +723,7 @@ export const DEFAULT_MODELS = [
})), })),
...anthropicModels.map((name) => ({ ...anthropicModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "anthropic", id: "anthropic",
@ -727,7 +734,7 @@ export const DEFAULT_MODELS = [
})), })),
...baiduModels.map((name) => ({ ...baiduModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "baidu", id: "baidu",
@ -738,7 +745,7 @@ export const DEFAULT_MODELS = [
})), })),
...bytedanceModels.map((name) => ({ ...bytedanceModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "bytedance", id: "bytedance",
@ -747,20 +754,22 @@ export const DEFAULT_MODELS = [
sorted: 6, sorted: 6,
}, },
})), })),
...alibabaModes.map((name) => ({
...openaiModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++, // Global sequence sort(index)
provider: { provider: {
id: "alibaba", id: "openai",
providerName: "Alibaba", providerName: "OpenAI",
providerType: "alibaba", providerType: "openai",
sorted: 7, sorted: 7, // 这里是固定的,确保顺序与之前内置的版本一致
}, },
})), })),
...tencentModels.map((name) => ({ ...tencentModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "tencent", id: "tencent",
@ -771,7 +780,7 @@ export const DEFAULT_MODELS = [
})), })),
...moonshotModes.map((name) => ({ ...moonshotModes.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "moonshot", id: "moonshot",
@ -782,7 +791,7 @@ export const DEFAULT_MODELS = [
})), })),
...iflytekModels.map((name) => ({ ...iflytekModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "iflytek", id: "iflytek",
@ -793,7 +802,7 @@ export const DEFAULT_MODELS = [
})), })),
...xAIModes.map((name) => ({ ...xAIModes.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "xai", id: "xai",
@ -804,7 +813,7 @@ export const DEFAULT_MODELS = [
})), })),
...chatglmModels.map((name) => ({ ...chatglmModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "chatglm", id: "chatglm",
@ -815,7 +824,7 @@ export const DEFAULT_MODELS = [
})), })),
...deepseekModels.map((name) => ({ ...deepseekModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "deepseek", id: "deepseek",
@ -826,7 +835,7 @@ export const DEFAULT_MODELS = [
})), })),
...siliconflowModels.map((name) => ({ ...siliconflowModels.map((name) => ({
name, name,
available: true, available: false,
sorted: seq++, sorted: seq++,
provider: { provider: {
id: "siliconflow", id: "siliconflow",

View File

@ -64,8 +64,8 @@ export const DEFAULT_CONFIG = {
models: DEFAULT_MODELS as any as LLMModel[], models: DEFAULT_MODELS as any as LLMModel[],
modelConfig: { modelConfig: {
model: "gpt-4o-mini" as ModelType, model: "qwen-turbo" as ModelType,
providerName: "OpenAI" as ServiceProvider, providerName: "Alibaba" as ServiceProvider,
temperature: 0.5, temperature: 0.5,
top_p: 1, top_p: 1,
max_tokens: 4000, max_tokens: 4000,

View File

@ -65,32 +65,40 @@ export const useMaskStore = createPersistStore(
return masks[id]; return masks[id];
}, },
// Hàm cập nhật một mask dựa trên id và một hàm updater
updateMask(id: string, updater: (mask: Mask) => void) { updateMask(id: string, updater: (mask: Mask) => void) {
const masks = get().masks; const masks = get().masks; // Lấy danh sách các mask hiện tại
const mask = masks[id]; const mask = masks[id]; // Lấy mask theo id
if (!mask) return; if (!mask) return; // Nếu không tìm thấy thì thoát
const updateMask = { ...mask }; const updateMask = { ...mask }; // Tạo bản sao mask để cập nhật
updater(updateMask); updater(updateMask); // Gọi hàm updater để chỉnh sửa mask
masks[id] = updateMask; masks[id] = updateMask; // Gán lại mask đã cập nhật vào danh sách
set(() => ({ masks })); set(() => ({ masks })); // Cập nhật lại state
get().markUpdate(); get().markUpdate(); // Đánh dấu đã cập nhật
}, },
// Hàm xóa một mask theo id
delete(id: string) { delete(id: string) {
const masks = get().masks; const masks = get().masks; // Lấy danh sách các mask hiện tại
delete masks[id]; delete masks[id]; // Xóa mask theo id
set(() => ({ masks })); set(() => ({ masks })); // Cập nhật lại state
get().markUpdate(); get().markUpdate(); // Đánh dấu đã cập nhật
}, },
// Hàm lấy một mask theo id (nếu không truyền id sẽ lấy id mặc định)
get(id?: string) { get(id?: string) {
return get().masks[id ?? 1145141919810]; return get().masks[id ?? 1145141919810];
}, },
// Hàm lấy tất cả các mask (bao gồm cả mask người dùng và mask mặc định)
getAll() { getAll() {
// Lấy danh sách mask của người dùng, sắp xếp theo thời gian tạo mới nhất
const userMasks = Object.values(get().masks).sort( const userMasks = Object.values(get().masks).sort(
(a, b) => b.createdAt - a.createdAt, (a, b) => b.createdAt - a.createdAt,
); );
const config = useAppConfig.getState(); const config = useAppConfig.getState(); // Lấy config hiện tại
if (config.hideBuiltinMasks) return userMasks; if (config.hideBuiltinMasks) return userMasks; // Nếu ẩn mask mặc định thì chỉ trả về mask người dùng
// Tạo danh sách mask mặc định (BUILTIN_MASKS) với cấu hình model hiện tại
const buildinMasks = BUILTIN_MASKS.map( const buildinMasks = BUILTIN_MASKS.map(
(m) => (m) =>
({ ({
@ -101,6 +109,7 @@ export const useMaskStore = createPersistStore(
}, },
}) as Mask, }) as Mask,
); );
// Trả về danh sách mask người dùng + mask mặc định
return userMasks.concat(buildinMasks); return userMasks.concat(buildinMasks);
}, },
search(text: string) { search(text: string) {

View File

@ -236,36 +236,38 @@ export const usePluginStore = createPersistStore(
return; return;
} }
fetch("./plugins.json") // fetch("./plugins.json")
.then((res) => res.json()) // .then((res) => res.json())
.then((res) => { // .then((res) => {
Promise.all( // Promise.all(
res.map((item: any) => // res.map((item: any) =>
// skip get schema // // skip get schema
state.get(item.id) // state.get(item.id)
? item // ? item
: fetch(item.schema) // : fetch(item.schema)
.then((res) => res.text()) // .then((res) => res.text())
.then((content) => ({ // .then((content) => ({
...item, // ...item,
content, // content,
})) // }))
.catch((e) => item), // .catch((e) => item),
), // ),
).then((builtinPlugins: any) => { // ).then((builtinPlugins: any) => {
builtinPlugins
.filter((item: any) => item?.content) // // builtinPlugins
.forEach((item: any) => { // // .filter((item: any) => item?.content)
const plugin = state.create(item); // // .forEach((item: any) => {
state.updatePlugin(plugin.id, (plugin) => { // // const plugin = state.create(item);
const tool = FunctionToolService.add(plugin, true); // // state.updatePlugin(plugin.id, (plugin) => {
plugin.title = tool.api.definition.info.title; // // const tool = FunctionToolService.add(plugin, true);
plugin.version = tool.api.definition.info.version; // // plugin.title = tool.api.definition.info.title;
plugin.builtin = true; // // plugin.version = tool.api.definition.info.version;
}); // // plugin.builtin = true;
}); // // });
}); // // });
}); // });
// });
}, },
}, },
); );

View File

@ -198,6 +198,7 @@ export function stream(
function animateResponseText() { function animateResponseText() {
if (finished || controller.signal.aborted) { if (finished || controller.signal.aborted) {
responseText += remainText; responseText += remainText;
console.log("[Response Animation] finished"); console.log("[Response Animation] finished");
if (responseText?.length === 0) { if (responseText?.length === 0) {
options.onError?.(new Error("empty response from server")); options.onError?.(new Error("empty response from server"));
@ -211,6 +212,12 @@ export function stream(
responseText += fetchText; responseText += fetchText;
remainText = remainText.slice(fetchCount); remainText = remainText.slice(fetchCount);
options.onUpdate?.(responseText, fetchText); options.onUpdate?.(responseText, fetchText);
console.log("[Response Animation] update", {
responseText,
fetchText,
remainText,
});
} }
requestAnimationFrame(animateResponseText); requestAnimationFrame(animateResponseText);

View File

@ -1,9 +1,10 @@
import { create } from "zustand"; import { create } from "zustand"; // Thư viện quản lý state cho React
import { combine, persist, createJSONStorage } from "zustand/middleware"; import { combine, persist, createJSONStorage } from "zustand/middleware"; // Các middleware hỗ trợ zustand
import { Updater } from "../typing"; import { Updater } from "../typing"; // Kiểu Updater tự định nghĩa
import { deepClone } from "./clone"; import { deepClone } from "./clone"; // Hàm deepClone để sao chép sâu object
import { indexedDBStorage } from "@/app/utils/indexedDB-storage"; import { indexedDBStorage } from "@/app/utils/indexedDB-storage"; // Lưu trữ dữ liệu bằng IndexedDB
// Lấy kiểu tham số thứ hai của một hàm
type SecondParam<T> = T extends ( type SecondParam<T> = T extends (
_f: infer _F, _f: infer _F,
_s: infer S, _s: infer S,
@ -12,52 +13,63 @@ type SecondParam<T> = T extends (
? S ? S
: never; : never;
// Định nghĩa các thuộc tính và phương thức bổ sung cho store
type MakeUpdater<T> = { type MakeUpdater<T> = {
lastUpdateTime: number; lastUpdateTime: number; // Thời gian cập nhật cuối cùng
_hasHydrated: boolean; _hasHydrated: boolean; // Đánh dấu đã hydrate (khôi phục dữ liệu từ storage)
markUpdate: () => void; markUpdate: () => void; // Đánh dấu cập nhật (cập nhật lastUpdateTime)
update: Updater<T>; update: Updater<T>; // Hàm cập nhật state bằng một updater
setHasHydrated: (state: boolean) => void; setHasHydrated: (state: boolean) => void; // Đặt trạng thái hydrate
}; };
// Kiểu hàm set state cho store
type SetStoreState<T> = ( type SetStoreState<T> = (
partial: T | Partial<T> | ((state: T) => T | Partial<T>), partial: T | Partial<T> | ((state: T) => T | Partial<T>),
replace?: boolean | undefined, replace?: boolean | undefined,
) => void; ) => void;
// Hàm tạo store có persist (lưu trữ lâu dài) với các phương thức bổ sung
export function createPersistStore<T extends object, M>( export function createPersistStore<T extends object, M>(
state: T, state: T, // State mặc định ban đầu
methods: ( methods: (
set: SetStoreState<T & MakeUpdater<T>>, set: SetStoreState<T & MakeUpdater<T>>,
get: () => T & MakeUpdater<T>, get: () => T & MakeUpdater<T>,
) => M, ) => M, // Các phương thức thao tác với store
persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>, persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>, // Tùy chọn lưu trữ
) { ) {
// Thiết lập storage sử dụng IndexedDB
persistOptions.storage = createJSONStorage(() => indexedDBStorage); persistOptions.storage = createJSONStorage(() => indexedDBStorage);
// Lưu lại hàm onRehydrateStorage cũ (nếu có)
const oldOonRehydrateStorage = persistOptions?.onRehydrateStorage; const oldOonRehydrateStorage = persistOptions?.onRehydrateStorage;
// Gán lại hàm onRehydrateStorage để đánh dấu đã hydrate khi khôi phục dữ liệu
persistOptions.onRehydrateStorage = (state) => { persistOptions.onRehydrateStorage = (state) => {
oldOonRehydrateStorage?.(state); oldOonRehydrateStorage?.(state);
return () => state.setHasHydrated(true); return () => state.setHasHydrated(true);
}; };
// Tạo store với zustand, kết hợp các middleware và phương thức bổ sung
return create( return create(
persist( persist(
combine( combine(
{ {
...state, ...state,
lastUpdateTime: 0, lastUpdateTime: 0, // Khởi tạo thời gian cập nhật cuối là 0
_hasHydrated: false, _hasHydrated: false, // Chưa hydrate
}, },
(set, get) => { (set, get) => {
return { return {
...methods(set, get as any), ...methods(set, get as any), // Thêm các phương thức custom
// Đánh dấu cập nhật (cập nhật lastUpdateTime)
markUpdate() { markUpdate() {
set({ lastUpdateTime: Date.now() } as Partial< set({ lastUpdateTime: Date.now() } as Partial<
T & M & MakeUpdater<T> T & M & MakeUpdater<T>
>); >);
}, },
// Hàm cập nhật state bằng một updater, đồng thời cập nhật lastUpdateTime
update(updater) { update(updater) {
const state = deepClone(get()); const state = deepClone(get());
updater(state); updater(state);
@ -66,6 +78,7 @@ export function createPersistStore<T extends object, M>(
lastUpdateTime: Date.now(), lastUpdateTime: Date.now(),
}); });
}, },
// Đặt trạng thái hydrate
setHasHydrated: (state: boolean) => { setHasHydrated: (state: boolean) => {
set({ _hasHydrated: state } as Partial<T & M & MakeUpdater<T>>); set({ _hasHydrated: state } as Partial<T & M & MakeUpdater<T>>);
}, },

8
git.sh Normal file
View File

@ -0,0 +1,8 @@
# git config --global user.email "quangdn@giahungtech.com.vn"
# git config --global user.name "quangdn-ght"
git add .
git commit -m "thay doi alibaba module mac dinh - chebichat"
git push
# mdZddHXcuzsB0Akk