diff --git a/Chebichat.md b/Chebichat.md new file mode 100644 index 000000000..f19ef98cc --- /dev/null +++ b/Chebichat.md @@ -0,0 +1 @@ +app/store/config.ts \ No newline at end of file diff --git a/app/SyncOnFirstLoad.tsx b/app/SyncOnFirstLoad.tsx index 1cf466c5f..e503cdd27 100644 --- a/app/SyncOnFirstLoad.tsx +++ b/app/SyncOnFirstLoad.tsx @@ -6,9 +6,16 @@ export default function SyncOnFirstLoad() { const syncStore = useSyncStore(); useEffect(() => { - if (syncStore.lastSyncTime === 0) { - syncStore.sync(); - } + // if (syncStore.lastSyncTime === 0) { + // // If this is the first time syncing, call sync() + // alert("[SyncOnFirstLoad] Dong bo hoa du lieu lan dau tien"); + + console.log("[SyncOnFirstLoad] Dong bo hoa du lieu lan dau tien"); + + console.log("Thoi gian dong bo lan cuoi: ", syncStore.lastSyncTime); + + syncStore.sync(); + // } }, []); return null; diff --git a/app/api/common.ts b/app/api/common.ts index f814a344e..42d9f4aa7 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -6,14 +6,20 @@ import { getModelProvider, isModelNotavailableInServer } from "../utils/model"; const serverConfig = getServerSideConfig(); +// Hàm proxy request từ client tới OpenAI hoặc Azure OpenAI, xử lý xác thực, cấu hình endpoint, kiểm tra model, và trả về response phù hợp. export async function requestOpenai(req: NextRequest) { + // Tạo controller để có thể hủy request khi timeout const controller = new AbortController(); + // Kiểm tra xem request có phải tới Azure OpenAI không const isAzure = req.nextUrl.pathname.includes("azure/deployments"); + // Biến lưu giá trị xác thực và tên header xác thực var authValue, authHeaderName = ""; + if (isAzure) { + // Nếu là Azure, lấy api-key từ header Authorization authValue = req.headers .get("Authorization") @@ -23,28 +29,35 @@ export async function requestOpenai(req: NextRequest) { authHeaderName = "api-key"; } else { + // Nếu là OpenAI thường, giữ nguyên header Authorization authValue = req.headers.get("Authorization") ?? ""; authHeaderName = "Authorization"; } + // Xử lý lại đường dẫn endpoint cho phù hợp với OpenAI/Azure let path = `${req.nextUrl.pathname}`.replaceAll("/api/openai/", ""); + console.log("[Proxy] mac dinh ", path); + + // Lấy baseUrl từ config, ưu tiên Azure nếu là request Azure let baseUrl = (isAzure ? serverConfig.azureUrl : serverConfig.baseUrl) || OPENAI_BASE_URL; - // console.log("[Base Url]", baseUrl); - + // Đảm bảo baseUrl có tiền tố https if (!baseUrl.startsWith("http")) { baseUrl = `https://${baseUrl}`; } + // Loại bỏ dấu "/" ở cuối baseUrl nếu có if (baseUrl.endsWith("/")) { baseUrl = baseUrl.slice(0, -1); } + // In ra log để debug đường dẫn và baseUrl console.log("[Proxy] ", path); console.log("[Base Url]", baseUrl); + // Thiết lập timeout cho request (10 phút), nếu quá sẽ abort const timeoutId = setTimeout( () => { controller.abort(); @@ -52,6 +65,7 @@ export async function requestOpenai(req: NextRequest) { 10 * 60 * 1000, ); + // Nếu là Azure, xử lý lại path và api-version cho đúng chuẩn Azure if (isAzure) { const azureApiVersion = req?.nextUrl?.searchParams?.get("api-version") || @@ -62,9 +76,7 @@ export async function requestOpenai(req: NextRequest) { "", )}?api-version=${azureApiVersion}`; - // Forward compatibility: - // if display_name(deployment_name) not set, and '{deploy-id}' in AZURE_URL - // then using default '{deploy-id}' + // Nếu có customModels và azureUrl, kiểm tra và thay thế deployment id nếu cần if (serverConfig.customModels && serverConfig.azureUrl) { const modelName = path.split("/")[1]; let realDeployName = ""; @@ -90,8 +102,12 @@ export async function requestOpenai(req: NextRequest) { } } + // Tạo url cuối cùng để fetch, có thể qua Cloudflare Gateway nếu cấu hình const fetchUrl = cloudflareAIGatewayUrl(`${baseUrl}/${path}`); + console.log("fetchUrl", fetchUrl); + + // Thiết lập các option cho fetch, bao gồm headers, method, body, v.v. const fetchOptions: RequestInit = { headers: { "Content-Type": "application/json", @@ -103,14 +119,14 @@ export async function requestOpenai(req: NextRequest) { }, method: req.method, body: req.body, - // to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body + // Fix lỗi body chỉ dùng được 1 lần trên Cloudflare Worker redirect: "manual", // @ts-ignore duplex: "half", signal: controller.signal, }; - // #1815 try to refuse gpt4 request + // Kiểm tra model có bị cấm sử dụng không (ví dụ: cấm GPT-4) if (serverConfig.customModels && req.body) { try { const clonedBody = await req.text(); @@ -118,7 +134,7 @@ export async function requestOpenai(req: NextRequest) { const jsonBody = JSON.parse(clonedBody) as { model?: string }; - // not undefined and is false + // Nếu model không được phép sử dụng, trả về lỗi 403 if ( isModelNotavailableInServer( serverConfig.customModels, @@ -126,7 +142,7 @@ export async function requestOpenai(req: NextRequest) { [ ServiceProvider.OpenAI, ServiceProvider.Azure, - jsonBody?.model as string, // support provider-unspecified model + jsonBody?.model as string, // hỗ trợ model không rõ provider ], ) ) { @@ -146,43 +162,40 @@ export async function requestOpenai(req: NextRequest) { } try { + // Gửi request tới OpenAI/Azure và nhận response const res = await fetch(fetchUrl, fetchOptions); - // Extract the OpenAI-Organization header from the response + // Lấy header OpenAI-Organization từ response (nếu có) const openaiOrganizationHeader = res.headers.get("OpenAI-Organization"); - // Check if serverConfig.openaiOrgId is defined and not an empty string + // Nếu đã cấu hình openaiOrgId, log giá trị header này if (serverConfig.openaiOrgId && serverConfig.openaiOrgId.trim() !== "") { - // If openaiOrganizationHeader is present, log it; otherwise, log that the header is not present console.log("[Org ID]", openaiOrganizationHeader); } else { console.log("[Org ID] is not set up."); } - // to prevent browser prompt for credentials + // Xử lý lại headers trả về cho client const newHeaders = new Headers(res.headers); - newHeaders.delete("www-authenticate"); - // to disable nginx buffering - newHeaders.set("X-Accel-Buffering", "no"); + newHeaders.delete("www-authenticate"); // Xóa header yêu cầu xác thực + newHeaders.set("X-Accel-Buffering", "no"); // Tắt buffer của nginx - // Conditionally delete the OpenAI-Organization header from the response if [Org ID] is undefined or empty (not setup in ENV) - // Also, this is to prevent the header from being sent to the client + // Nếu chưa cấu hình Org ID, xóa header này khỏi response if (!serverConfig.openaiOrgId || serverConfig.openaiOrgId.trim() === "") { newHeaders.delete("OpenAI-Organization"); } - // The latest version of the OpenAI API forced the content-encoding to be "br" in json response - // So if the streaming is disabled, we need to remove the content-encoding header - // Because Vercel uses gzip to compress the response, if we don't remove the content-encoding header - // The browser will try to decode the response with brotli and fail + // Xóa header content-encoding để tránh lỗi giải nén trên trình duyệt newHeaders.delete("content-encoding"); + // Trả về response cho client với các header đã xử lý return new Response(res.body, { status: res.status, statusText: res.statusText, headers: newHeaders, }); } finally { + // Dù thành công hay lỗi đều clear timeout clearTimeout(timeoutId); } } diff --git a/app/api/openai.ts b/app/api/openai.ts index 5bb3a5dcb..a4b8596f9 100644 --- a/app/api/openai.ts +++ b/app/api/openai.ts @@ -59,6 +59,8 @@ export async function handle( } try { + console.log("mac dinh su dung OpenAI API"); + console.log("[OpenAI Route] ", subpath); const response = await requestOpenai(req); // list models diff --git a/app/chebichatConstant.ts b/app/chebichatConstant.ts new file mode 100644 index 000000000..69593d7d4 --- /dev/null +++ b/app/chebichatConstant.ts @@ -0,0 +1,7 @@ +export const ALIBABA_BASE_URL = "https://dashscope-intl.aliyuncs.com"; +export const ALIBABA_PATH = `compatible-mode/v1/chat/completions`; +// The key used to store the last chat ID in local storage +export const UPSTASH_ENDPOINT = "https://fine-baboon-52580.upstash.io"; +export const UPSTASH_APIKEY = + "Ac1kAAIjcDE2YjM4YmY3OGI4YzA0MTU2YjZhNmQyNzc5Yzc3NzEwYnAxMA"; +export const STORAGE_KEY = "chebichat-backup"; diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 97c6d8071..f1925cc85 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -510,7 +510,7 @@ export function ChatActions(props: { const pluginStore = usePluginStore(); const session = chatStore.currentSession(); - // switch themes + // Chuyển đổi giữa các chế độ giao diện sáng/tối const theme = config.theme; function nextTheme() { @@ -521,11 +521,11 @@ export function ChatActions(props: { config.update((config) => (config.theme = nextTheme)); } - // stop all responses + // Dừng tất cả các phản hồi đang chạy const couldStop = ChatControllerPool.hasPending(); const stopAll = () => ChatControllerPool.stopAll(); - // switch model + // Chuyển đổi giữa các mô hình AI const currentModel = session.mask.modelConfig.model; const currentProviderName = session.mask.modelConfig?.providerName || ServiceProvider.OpenAI; @@ -577,11 +577,11 @@ export function ChatActions(props: { props.setUploading(false); } - // if current model is not available - // switch to first available model + // nếu mô hình hiện tại không khả dụng + // chuyển sang mô hình khả dụng đầu tiên const isUnavailableModel = !models.some((m) => m.name === currentModel); if (isUnavailableModel && models.length > 0) { - // show next model to default model if exist + // hiển thị mô hình tiếp theo là mô hình mặc định nếu có let nextModel = models.find((model) => model.isDefault) || models[0]; chatStore.updateTargetSession(session, (session) => { session.mask.modelConfig.model = nextModel.name; @@ -1795,9 +1795,13 @@ function _Chat() { }} > {messages - // TODO // .filter((m) => !m.isMcpResponse) .map((message, i) => { + // Bypass rendering if message.role is "system" + if (message.role === "system") { + return null; + } + const isUser = message.role === "user"; const isContext = i < context.length; const showActions = @@ -1809,6 +1813,8 @@ function _Chat() { const shouldShowClearContextDivider = i === clearContextIndex - 1; + console.log(message.role); + return (
{ + // Gọi hàm onEmojiClick khi người dùng click vào emoji + // Truyền giá trị unified của emoji đã chọn props.onEmojiClick(e.unified); }} /> ); } -export function Avatar(props: { model?: ModelType; avatar?: string }) { - let LlmIcon = BotIconDefault; - - if (props.model) { - const modelName = props.model.toLowerCase(); - - if ( - modelName.startsWith("gpt") || - modelName.startsWith("chatgpt") || - modelName.startsWith("dall-e") || - modelName.startsWith("dalle") || - modelName.startsWith("o1") || - modelName.startsWith("o3") - ) { - LlmIcon = BotIconOpenAI; - } else if (modelName.startsWith("gemini")) { - LlmIcon = BotIconGemini; - } else if (modelName.startsWith("gemma")) { - LlmIcon = BotIconGemma; - } else if (modelName.startsWith("claude")) { - LlmIcon = BotIconClaude; - } else if (modelName.includes("llama")) { - LlmIcon = BotIconMeta; - } else if ( - modelName.startsWith("mixtral") || - modelName.startsWith("codestral") - ) { - LlmIcon = BotIconMistral; - } else if (modelName.includes("deepseek")) { - LlmIcon = BotIconDeepseek; - } else if (modelName.startsWith("moonshot")) { - LlmIcon = BotIconMoonshot; - } else if (modelName.startsWith("qwen")) { - LlmIcon = BotIconQwen; - } else if (modelName.startsWith("ernie")) { - LlmIcon = BotIconWenxin; - } else if (modelName.startsWith("grok")) { - LlmIcon = BotIconGrok; - } else if (modelName.startsWith("hunyuan")) { - LlmIcon = BotIconHunyuan; - } else if (modelName.startsWith("doubao") || modelName.startsWith("ep-")) { - LlmIcon = BotIconDoubao; - } else if ( - modelName.includes("glm") || - modelName.startsWith("cogview-") || - modelName.startsWith("cogvideox-") - ) { - LlmIcon = BotIconChatglm; - } - - return ( -
- -
- ); - } - +// Function to render chebichat PNG avatar +function chebichatAvatar() { return (
- {props.avatar && } + chebichat avatar
); } +//xu ly avatar cho Chebichat +// nếu không có avatar thì trả về avatar mặc định của Chebichat +// nếu có avatar thì trả về avatar tương ứng với tên avatar +export function Avatar(props: { model?: ModelType; avatar?: string }) { + // console.log("Avatar props", props); + + if (props.avatar === "chebi-user") { + //sau thay the bang avatar tu Chebichat Platform (Avatar Google,...) + + // Nếu avatar là "chebi-user", trả về avatar mặc định của Chebichat + return null; + } + + return chebichatAvatar(); + + // let LlmIcon = BotIconDefault; + + //phan biệt các loại model và gán icon tương ứng + // if (props.model) { + // const modelName = props.model.toLowerCase(); + + // // Xác định icon phù hợp dựa trên tên model + // if ( + // modelName.startsWith("gpt") || + // modelName.startsWith("chatgpt") || + // modelName.startsWith("dall-e") || + // modelName.startsWith("dalle") || + // modelName.startsWith("o1") || + // modelName.startsWith("o3") + // ) { + // LlmIcon = BotIconOpenAI; + // } else if (modelName.startsWith("gemini")) { + // LlmIcon = BotIconGemini; + // } else if (modelName.startsWith("gemma")) { + // LlmIcon = BotIconGemma; + // } else if (modelName.startsWith("claude")) { + // LlmIcon = BotIconClaude; + // } else if (modelName.includes("llama")) { + // LlmIcon = BotIconMeta; + // } else if ( + // modelName.startsWith("mixtral") || + // modelName.startsWith("codestral") + // ) { + // LlmIcon = BotIconMistral; + // } else if (modelName.includes("deepseek")) { + // LlmIcon = BotIconDeepseek; + // } else if (modelName.startsWith("moonshot")) { + // LlmIcon = BotIconMoonshot; + // } else if (modelName.startsWith("qwen")) { + // LlmIcon = BotIconQwen; + // } else if (modelName.startsWith("ernie")) { + // LlmIcon = BotIconWenxin; + // } else if (modelName.startsWith("grok")) { + // LlmIcon = BotIconGrok; + // } else if (modelName.startsWith("hunyuan")) { + // LlmIcon = BotIconHunyuan; + // } else if (modelName.startsWith("doubao") || modelName.startsWith("ep-")) { + // LlmIcon = BotIconDoubao; + // } else if ( + // modelName.includes("glm") || + // modelName.startsWith("cogview-") || + // modelName.startsWith("cogvideox-") + // ) { + // LlmIcon = BotIconChatglm; + // } + + // return chebichatAvatar(); + // } + + // return ( + // console.log("Avatar", props.avatar), + //
+ // {props.avatar && } + //
+ // ); +} + export function EmojiAvatar(props: { avatar: string; size?: number }) { return ( + // Hiển thị emoji dựa trên giá trị avatar được truyền vào
diff --git a/app/components/mask.tsx b/app/components/mask.tsx index fa9537cba..9a78c5ed4 100644 --- a/app/components/mask.tsx +++ b/app/components/mask.tsx @@ -45,7 +45,6 @@ import { readFromFile, } from "../utils"; import { Updater } from "../typing"; -import { ModelConfigList } from "./model-config"; import { FileName, Path } from "../constant"; import { BUILTIN_MASK_STORE } from "../masks"; import { @@ -246,13 +245,14 @@ export function MaskConfig(props: { ) : null} - + {/* CAU HINH MODEL */} + {/* {props.extraListItems} - + */} ); } diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 68ebcf084..3eb47ce9c 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -31,7 +31,6 @@ import { showConfirm, showToast, } from "./ui-lib"; -import { ModelConfigList } from "./model-config"; import { IconButton } from "./button"; import { @@ -1873,7 +1872,8 @@ export function Settings() { - + {/* CAU HINH MODEL CUSTOM */} + {/* { @@ -1882,7 +1882,7 @@ export function Settings() { config.update((config) => (config.modelConfig = modelConfig)); }} /> - + */} {shouldShowPromptModal && ( setShowPromptModal(false)} /> diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index 86b1991e5..2b09bee61 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -4,7 +4,6 @@ import styles from "./home.module.scss"; import { IconButton } from "./button"; import SettingsIcon from "../icons/settings.svg"; -import GithubIcon from "../icons/github.svg"; import ChatGptIcon from "../icons/chebichat.svg"; @@ -25,7 +24,6 @@ import { MIN_SIDEBAR_WIDTH, NARROW_SIDEBAR_WIDTH, Path, - REPO_URL, } from "../constant"; import { Link, useNavigate } from "react-router-dom"; @@ -36,8 +34,8 @@ import clsx from "clsx"; import { isMcpEnabled } from "../mcp/actions"; const DISCOVERY = [ - { name: Locale.Plugin.Name, path: Path.Plugins }, - { name: "Stable Diffusion", path: Path.Sd }, + // { name: Locale.Plugin.Name, path: Path.Plugins }, + // { name: "Stable Diffusion", path: Path.Sd }, { name: Locale.SearchChat.Page.Title, path: Path.SearchChat }, ]; @@ -252,17 +250,20 @@ export function SideBar(props: { className?: string }) { {...props} > } - shouldNarrow={shouldNarrow} + title="Chebi Chat" // Tiêu đề sidebar + subTitle="Trợ lý AI học tiếng Trung" // Phụ đề sidebar + logo={} // Logo hiển thị + shouldNarrow={shouldNarrow} // Trạng thái thu nhỏ sidebar > + {/* Thanh công cụ phía trên của sidebar */}
+ {/* Nút chuyển sang giao diện tạo chat mới hoặc danh sách mask */} } text={shouldNarrow ? undefined : Locale.Mask.Name} className={styles["sidebar-bar-button"]} onClick={() => { + // Nếu chưa tắt splash screen mask thì chuyển sang tạo chat mới, ngược lại chuyển sang danh sách mask if (config.dontShowMaskSplashScreen !== true) { navigate(Path.NewChat, { state: { fromHome: true } }); } else { @@ -271,17 +272,20 @@ export function SideBar(props: { className?: string }) { }} shadow /> + {/* Nếu tính năng MCP được bật thì hiển thị nút MCP */} {mcpEnabled && ( } text={shouldNarrow ? undefined : Locale.Mcp.Name} className={styles["sidebar-bar-button"]} onClick={() => { + // Chuyển sang giao diện MCP Market navigate(Path.McpMarket, { state: { fromHome: true } }); }} shadow /> )} + {/* Nút chuyển sang giao diện Discovery */} } text={shouldNarrow ? undefined : Locale.Discovery.Name} @@ -290,6 +294,7 @@ export function SideBar(props: { className?: string }) { shadow />
+ {/* Hiển thị selector khi người dùng bấm vào Discovery */} {showDiscoverySelector && ( setshowDiscoverySelector(false)} onSelection={(s) => { + // Điều hướng đến trang được chọn navigate(s[0], { state: { fromHome: true } }); }} /> @@ -338,7 +344,8 @@ export function SideBar(props: { className?: string }) { /> -
+ + {/*
-
+
*/} } secondaryAction={ diff --git a/app/constant.ts b/app/constant.ts index 1fbce621e..fc8fdc1ac 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -1,3 +1,7 @@ +import { ALIBABA_BASE_URL, ALIBABA_PATH } from "./chebichatConstant"; + +export * from "./chebichatConstant"; + export const OWNER = "ChatGPTNextWeb"; export const REPO = "ChatGPT-Next-Web"; export const REPO_URL = `https://github.com/${OWNER}/${REPO}`; @@ -21,8 +25,6 @@ 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 ALIBABA_BASE_URL = "https://dashscope-intl.aliyuncs.com"; - export const TENCENT_BASE_URL = "https://hunyuan.tencentcloudapi.com"; export const MOONSHOT_BASE_URL = "https://api.moonshot.cn"; @@ -60,7 +62,7 @@ export enum Path { export enum ApiPath { Cors = "", Azure = "/api/azure", - OpenAI = "/api/openai", + OpenAI = "/api/alibaba", // Use Alibaba path for OpenAI API Anthropic = "/api/anthropic", Google = "/api/google", Baidu = "/api/baidu", @@ -110,12 +112,6 @@ export const ACCESS_CODE_PREFIX = "nk-"; export const LAST_INPUT_KEY = "last-input"; export const UNFINISHED_INPUT = (id: string) => "unfinished-input-" + id; -// The key used to store the last chat ID in local storage -export const UPSTASH_ENDPOINT = "https://fine-baboon-52580.upstash.io"; -export const UPSTASH_APIKEY = - "Ac1kAAIjcDE2YjM4YmY3OGI4YzA0MTU2YjZhNmQyNzc5Yzc3NzEwYnAxMA"; -export const STORAGE_KEY = "chebichat-backup"; - export const REQUEST_TIMEOUT_MS = 60000; export const REQUEST_TIMEOUT_MS_FOR_THINKING = REQUEST_TIMEOUT_MS * 5; @@ -178,7 +174,8 @@ export const Anthropic = { }; export const OpenaiPath = { - ChatPath: "v1/chat/completions", + // ChatPath: "v1/chat/completions", + ChatPath: ALIBABA_PATH, SpeechPath: "v1/audio/speech", ImagePath: "v1/images/generations", UsagePath: "dashboard/billing/usage", @@ -236,7 +233,7 @@ export const Alibaba = { // https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions - const URL = "compatible-mode/v1/chat/completions"; + const URL = ALIBABA_PATH; // if (modelName.includes("vl") || modelName.includes("omni")) { // return "v1/services/aigc/multimodal-generation/generation"; diff --git a/app/icons/chebichat-big.svg b/app/icons/chebichat-big.svg new file mode 100644 index 000000000..d086c297b --- /dev/null +++ b/app/icons/chebichat-big.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/icons/chebichat.svg b/app/icons/chebichat.svg index d086c297b..f54ca959b 100644 --- a/app/icons/chebichat.svg +++ b/app/icons/chebichat.svg @@ -1,149 +1,151 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/layout.tsx b/app/layout.tsx index 3cff3db7f..8f3d66c8c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -7,12 +7,14 @@ import type { Metadata, Viewport } from "next"; import { SpeedInsights } from "@vercel/speed-insights/next"; import { GoogleTagManager, GoogleAnalytics } from "@next/third-parties/google"; import { getServerSideConfig } from "./config/server"; +import SyncOnFirstLoad from "./SyncOnFirstLoad"; +const TITLE = "Chebi Chat - Trợ lý AI học tiếng Trung"; export const metadata: Metadata = { - title: "Chebi Chat", - description: "Trợ lý AI học tiếng Trung", + title: TITLE, + description: TITLE, appleWebApp: { - title: "Chebi Chat", + title: TITLE, statusBarStyle: "default", }, }; @@ -34,6 +36,10 @@ export default function RootLayout({ }) { const serverConfig = getServerSideConfig(); + // Log server configuration for debugging + console.log("Server Configuration:"); + // console.log(serverConfig); + return ( @@ -51,6 +57,7 @@ export default function RootLayout({ {children} + {serverConfig?.isVercel && ( <> @@ -66,6 +73,8 @@ export default function RootLayout({ )} + {/* DONG BO VOI CLOUD SYNC UPSTASH */} + ); diff --git a/app/locales/vi.ts b/app/locales/vi.ts index 44b51314f..4369c83da 100644 --- a/app/locales/vi.ts +++ b/app/locales/vi.ts @@ -471,6 +471,9 @@ const vi: PartialLocaleType = { "Tóm tắt nội dung cuộc trò chuyện một cách ngắn gọn, dùng làm gợi ý ngữ cảnh cho các lần sau, giữ trong vòng 200 từ", }, }, + Discovery: { + Name: "Tìm kiêm", + }, Copy: { Success: "Đã sao chép vào clipboard", Failed: "Sao chép thất bại, vui lòng cấp quyền clipboard", diff --git a/app/masks/cn.ts b/app/masks/cn.ts index 6c8357c23..227477fce 100644 --- a/app/masks/cn.ts +++ b/app/masks/cn.ts @@ -18,13 +18,13 @@ export const CN_MASKS: BuiltinMask[] = [ content: "mẫu câu chào hỏi tiếng trung?", date: "", }, - { - id: "text-to-pic-2", - role: "assistant", - content: - "Dưới đây là một số mẫu câu chào hỏi bằng tiếng Trung (bao gồm chữ Hán và phiên âm Pinyin) mà bạn có thể sử dụng:\n\n1. **Xìnghé** (xin chào):\n - 您好 (nín hǎo)\n\n2. **Chào buổi sáng**:\n - 早上好 (zǎo shàng hǎo)\n\n3. **Chào buổi chiều**:\n - 下午好 (xià wǔ hǎo)\n\n4. **Chào buổi tối**:\n - 晚上好 (wǎn shàng hǎo)\n\n5. **Tạm biệt**:\n - 再见 (zài jiàn)\n\n6. **Bạn khỏe không?** (Bạn đang khỏe chứ?):\n - 你好吗?(nǐ hǎo ma?)\n\n7. **Tôi khỏe, cảm ơn!**:\n - 我很好,谢谢!(wǒ hěn hǎo, xièxiè!)\n\n8. **Rất vui được gặp bạn**:\n - 很高兴见到你 (hěn gāoxìng jiàndào nǐ)\n\nHy vọng những câu chào hỏi này sẽ giúp ích cho bạn khi học tiếng Trung!", - date: "", - }, + // { + // id: "text-to-pic-2", + // role: "assistant", + // content: + // "Dưới đây là một số mẫu câu chào hỏi bằng tiếng Trung (bao gồm chữ Hán và phiên âm Pinyin) mà bạn có thể sử dụng:\n\n1. **Xìnghé** (xin chào):\n - 您好 (nín hǎo)\n\n2. **Chào buổi sáng**:\n - 早上好 (zǎo shàng hǎo)\n\n3. **Chào buổi chiều**:\n - 下午好 (xià wǔ hǎo)\n\n4. **Chào buổi tối**:\n - 晚上好 (wǎn shàng hǎo)\n\n5. **Tạm biệt**:\n - 再见 (zài jiàn)\n\n6. **Bạn khỏe không?** (Bạn đang khỏe chứ?):\n - 你好吗?(nǐ hǎo ma?)\n\n7. **Tôi khỏe, cảm ơn!**:\n - 我很好,谢谢!(wǒ hěn hǎo, xièxiè!)\n\n8. **Rất vui được gặp bạn**:\n - 很高兴见到你 (hěn gāoxìng jiàndào nǐ)\n\nHy vọng những câu chào hỏi này sẽ giúp ích cho bạn khi học tiếng Trung!", + // date: "", + // }, ], modelConfig: { model: "qwen-turbo", @@ -77,13 +77,13 @@ export const CN_MASKS: BuiltinMask[] = [ ], date: "", }, - { - id: "text-to-pic-2", - role: "assistant", - content: - "Tôi giúp bạn phân tích và giải thích ý nghĩa của bức ảnh về Đền Thiên Cung (Temple of Heaven) bằng tiếng Trung có pinyin.\n\nBức ảnh này cho thấy Đền Thiên Cung ở Bắc Kinh, Trung Quốc. Đây là một di sản văn hóa thế giới được UNESCO công nhận. Dưới đây là một số điểm chính về bức ảnh:\n\n1. **Tên địa điểm**: Đền Thiên Cung (天宮殿 - Tiān Gōng Diàn)\n - Tiếng Việt: Đền Thiên Cung\n - Pinyin: Tiān Gōng Diàn\n\n2. **Ý nghĩa**: Đền Thiên Cung là một kiến trúc quan trọng trong lịch sử Trung Quốc, đặc biệt là thời kỳ triều đại nhà Minh và nhà Thanh. Nó được xây dựng để tế lễ thần Thiên (tiếng Trung: 天 - Tiān), biểu tượng cho trời đất và sự nghiệp của vua chúa.\n\n3. **Kiến trúc**: \n - **Đỉnh mái**: Đỉnh mái của đền có dạng hình lục giác,象征着天地的和谐统一 (biểu thị sự hòa hợp giữa trời đất).\n - **Màu sắc**: Màu đỏ thường được sử dụng trong kiến trúc cổ Trung Quốc, tượng trưng cho quyền lực và may mắn.\n - **Cầu thang**: Các bậc cầu thang dẫn lên đền tượng trưng cho việc tiến trình từ hạ giới lên thiên giới.\n\n4. **Vị trí**: Đền Thiên Cung nằm ở phía nam thành phố Bắc Kinh, gần quảng trường Thiên An Môn. Đây là một địa điểm du lịch nổi tiếng và cũng là nơi diễn ra nhiều nghi lễ quan trọng trong lịch sử.\n\n5. **Giá trị văn hóa**: Đền Thiên Cung không chỉ là một công trình kiến trúc tuyệt đẹp mà còn là một biểu tượng của văn hóa và lịch sử Trung Quốc. Nó phản ánh tư duy tâm linh và triết lý về mối quan hệ giữa con người và thiên nhiên của người Trung Quốc.\n\nHy vọng những thông tin trên sẽ hữu ích cho bạn! Nếu bạn cần thêm thông tin chi tiết hoặc có câu hỏi khác, hãy cho tôi biết nhé.", - date: "", - }, + // { + // id: "text-to-pic-2", + // role: "assistant", + // content: + // "Tôi giúp bạn phân tích và giải thích ý nghĩa của bức ảnh về Đền Thiên Cung (Temple of Heaven) bằng tiếng Trung có pinyin.\n\nBức ảnh này cho thấy Đền Thiên Cung ở Bắc Kinh, Trung Quốc. Đây là một di sản văn hóa thế giới được UNESCO công nhận. Dưới đây là một số điểm chính về bức ảnh:\n\n1. **Tên địa điểm**: Đền Thiên Cung (天宮殿 - Tiān Gōng Diàn)\n - Tiếng Việt: Đền Thiên Cung\n - Pinyin: Tiān Gōng Diàn\n\n2. **Ý nghĩa**: Đền Thiên Cung là một kiến trúc quan trọng trong lịch sử Trung Quốc, đặc biệt là thời kỳ triều đại nhà Minh và nhà Thanh. Nó được xây dựng để tế lễ thần Thiên (tiếng Trung: 天 - Tiān), biểu tượng cho trời đất và sự nghiệp của vua chúa.\n\n3. **Kiến trúc**: \n - **Đỉnh mái**: Đỉnh mái của đền có dạng hình lục giác,象征着天地的和谐统一 (biểu thị sự hòa hợp giữa trời đất).\n - **Màu sắc**: Màu đỏ thường được sử dụng trong kiến trúc cổ Trung Quốc, tượng trưng cho quyền lực và may mắn.\n - **Cầu thang**: Các bậc cầu thang dẫn lên đền tượng trưng cho việc tiến trình từ hạ giới lên thiên giới.\n\n4. **Vị trí**: Đền Thiên Cung nằm ở phía nam thành phố Bắc Kinh, gần quảng trường Thiên An Môn. Đây là một địa điểm du lịch nổi tiếng và cũng là nơi diễn ra nhiều nghi lễ quan trọng trong lịch sử.\n\n5. **Giá trị văn hóa**: Đền Thiên Cung không chỉ là một công trình kiến trúc tuyệt đẹp mà còn là một biểu tượng của văn hóa và lịch sử Trung Quốc. Nó phản ánh tư duy tâm linh và triết lý về mối quan hệ giữa con người và thiên nhiên của người Trung Quốc.\n\nHy vọng những thông tin trên sẽ hữu ích cho bạn! Nếu bạn cần thêm thông tin chi tiết hoặc có câu hỏi khác, hãy cho tôi biết nhé.", + // date: "", + // }, ], modelConfig: { model: "qwen-vl-plus", diff --git a/app/store/config.ts b/app/store/config.ts index 5f434d7e8..a90936af4 100644 --- a/app/store/config.ts +++ b/app/store/config.ts @@ -42,8 +42,8 @@ export const DEFAULT_CONFIG = { lastUpdate: Date.now(), // timestamp, to merge state submitKey: SubmitKey.Enter, - avatar: "1f603", - fontSize: 14, + avatar: "chebi-user", + fontSize: 16, fontFamily: "", theme: Theme.Auto as Theme, tightBorder: !!config?.isApp, diff --git a/app/store/sync.ts b/app/store/sync.ts index 1799887aa..e59328057 100644 --- a/app/store/sync.ts +++ b/app/store/sync.ts @@ -131,6 +131,9 @@ export const useSyncStore = createPersistStore( ) as AppState; mergeAppState(localState, parsedRemoteState); setLocalAppState(localState); + + console.log("[Sync] Merged remote state with local state"); + console.log("Dong bo thanh cong", provider, config.username); } } catch (e) { console.log("[Sync] failed to get remote state", e); diff --git a/git.sh b/git.sh index 3cd96a94b..ab7970f8f 100644 --- a/git.sh +++ b/git.sh @@ -2,7 +2,7 @@ # git config --global user.name "quangdn-ght" git add . -git commit -m "thay doi ui chebichat" +git commit -m "cap nhat giao dien" git push # mdZddHXcuzsB0Akk \ No newline at end of file diff --git a/public/android-chrome-192x192 copy.png b/public/android-chrome-192x192 copy.png new file mode 100644 index 000000000..b2df36711 Binary files /dev/null and b/public/android-chrome-192x192 copy.png differ diff --git a/public/apple-touch-icon copy.png b/public/apple-touch-icon copy.png new file mode 100644 index 000000000..4123aba87 Binary files /dev/null and b/public/apple-touch-icon copy.png differ diff --git a/app/icons/llm-icons/chebichat.png b/public/chebichat.png similarity index 100% rename from app/icons/llm-icons/chebichat.png rename to public/chebichat.png diff --git a/public/favicon-16x16 copy.png b/public/favicon-16x16 copy.png new file mode 100644 index 000000000..4dce3bcc9 Binary files /dev/null and b/public/favicon-16x16 copy.png differ diff --git a/public/favicon-32x32 copy.png b/public/favicon-32x32 copy.png new file mode 100644 index 000000000..feebe5e84 Binary files /dev/null and b/public/favicon-32x32 copy.png differ diff --git a/public/favicon-chebi.png b/public/favicon-chebi.png new file mode 100644 index 000000000..ef9c51951 Binary files /dev/null and b/public/favicon-chebi.png differ diff --git a/public/favicon.ico b/public/favicon.ico index b5e8234cd..6f11e5f58 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/site copy.webmanifest b/public/site copy.webmanifest new file mode 100644 index 000000000..45dc8a206 --- /dev/null +++ b/public/site copy.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file