cap nhat giao dien

This commit is contained in:
quangdn-ght 2025-06-25 18:12:09 +07:00
parent cdeb27891b
commit b07760fbc9
28 changed files with 526 additions and 307 deletions

1
Chebichat.md Normal file
View File

@ -0,0 +1 @@
app/store/config.ts

View File

@ -6,9 +6,16 @@ export default function SyncOnFirstLoad() {
const syncStore = useSyncStore();
useEffect(() => {
if (syncStore.lastSyncTime === 0) {
// 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;

View File

@ -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);
}
}

View File

@ -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

7
app/chebichatConstant.ts Normal file
View File

@ -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";

View File

@ -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 (
<Fragment key={message.id}>
<div

View File

@ -8,30 +8,11 @@ import { ModelType } from "../store";
// import BotIconDefault from "../icons/llm-icons/chebichat.svg";
import BotIconDefault from "../icons/llm-icons/chebichat.svg";
import BotIconOpenAI from "../icons/llm-icons/chebichat.svg";
import BotIconGemini from "../icons/llm-icons/gemini.svg";
import BotIconGemma from "../icons/llm-icons/gemma.svg";
import BotIconClaude from "../icons/llm-icons/claude.svg";
import BotIconMeta from "../icons/llm-icons/meta.svg";
import BotIconMistral from "../icons/llm-icons/mistral.svg";
import BotIconDeepseek from "../icons/llm-icons/deepseek.svg";
import BotIconMoonshot from "../icons/llm-icons/moonshot.svg";
// thay bang chebichat
import BotIconQwen from "../icons/llm-icons/chebichat.svg";
import BotIconWenxin from "../icons/llm-icons/wenxin.svg";
import BotIconGrok from "../icons/llm-icons/grok.svg";
import BotIconHunyuan from "../icons/llm-icons/hunyuan.svg";
import BotIconDoubao from "../icons/llm-icons/doubao.svg";
import BotIconChatglm from "../icons/llm-icons/chatglm.svg";
export function getEmojiUrl(unified: string, style: EmojiStyle) {
// Whoever owns this Content Delivery Network (CDN), I am using your CDN to serve emojis
// Old CDN broken, so I had to switch to this one
// Author: https://github.com/H0llyW00dzZ
// Phương thức trả về đường dẫn URL của emoji dựa trên mã hóa unified và kiểu style
// CDN mới được sử dụng để phục vụ hình ảnh emoji
return `https://fastly.jsdelivr.net/npm/emoji-datasource-apple/img/${style}/64/${unified}.png`;
}
@ -45,78 +26,109 @@ export function AvatarPicker(props: {
theme={EmojiTheme.AUTO}
getEmojiUrl={getEmojiUrl}
onEmojiClick={(e) => {
// 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);
}}
/>
);
}
// Function to render chebichat PNG avatar
function chebichatAvatar() {
return (
<div className="user-avatar">
<img
src="/chebichat.png"
alt="chebichat avatar"
width={48}
height={48}
style={{ borderRadius: "50%" }}
/>
</div>
);
}
//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 }) {
let LlmIcon = BotIconDefault;
// console.log("Avatar props", props);
if (props.model) {
const modelName = props.model.toLowerCase();
if (props.avatar === "chebi-user") {
//sau thay the bang avatar tu Chebichat Platform (Avatar Google,...)
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;
// Nếu avatar là "chebi-user", trả về avatar mặc định của Chebichat
return null;
}
return (
<div className="user-avatar">
<LlmIcon className="user-avatar" size={48} width={48} height={48} />
</div>
);
}
return chebichatAvatar();
return (
<div className="user-avatar">
{props.avatar && <EmojiAvatar avatar={props.avatar} size={48} />}
</div>
);
// 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),
// <div className="user-avatar">
// {props.avatar && <EmojiAvatar avatar={props.avatar} size={48} />}
// </div>
// );
}
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
<Emoji
unified={props.avatar}
size={props.size ?? 18}

View File

@ -518,8 +518,8 @@ export function ImagePreviewer(props: {
<NextImage
src={ChatGptIcon.src}
alt="logo"
width={50}
height={50}
width={30}
height={30}
/>
</div>

View File

@ -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}
</List>
<List>
{/* CAU HINH MODEL */}
{/* <List>
<ModelConfigList
modelConfig={{ ...props.mask.modelConfig }}
updateConfig={updateConfig}
/>
{props.extraListItems}
</List>
</List> */}
</>
);
}

View File

@ -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() {
</ListItem>
</List>
<List>
{/* CAU HINH MODEL CUSTOM */}
{/* <List>
<ModelConfigList
modelConfig={config.modelConfig}
updateConfig={(updater) => {
@ -1882,7 +1882,7 @@ export function Settings() {
config.update((config) => (config.modelConfig = modelConfig));
}}
/>
</List>
</List> */}
{shouldShowPromptModal && (
<UserPromptModal onClose={() => setShowPromptModal(false)} />

View File

@ -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}
>
<SideBarHeader
title="Chebi Chat"
subTitle="Trợ lý AI học tiếng Trung"
logo={<ChatGptIcon />}
shouldNarrow={shouldNarrow}
title="Chebi Chat" // Tiêu đề sidebar
subTitle="Trợ lý AI học tiếng Trung" // Phụ đề sidebar
logo={<ChatGptIcon />} // Logo hiển thị
shouldNarrow={shouldNarrow} // Trạng thái thu nhỏ sidebar
>
{/* Thanh công cụ phía trên của sidebar */}
<div className={styles["sidebar-header-bar"]}>
{/* Nút chuyển sang giao diện tạo chat mới hoặc danh sách mask */}
<IconButton
icon={<MaskIcon />}
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 && (
<IconButton
icon={<McpIcon />}
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 */}
<IconButton
icon={<DiscoveryIcon />}
text={shouldNarrow ? undefined : Locale.Discovery.Name}
@ -290,6 +294,7 @@ export function SideBar(props: { className?: string }) {
shadow
/>
</div>
{/* Hiển thị selector khi người dùng bấm vào Discovery */}
{showDiscoverySelector && (
<Selector
items={[
@ -302,6 +307,7 @@ export function SideBar(props: { className?: string }) {
]}
onClose={() => 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 }) {
/>
</Link>
</div>
<div className={styles["sidebar-action"]}>
{/* <div className={styles["sidebar-action"]}>
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
<IconButton
aria={Locale.Export.MessageFromChatGPT}
@ -346,7 +353,7 @@ export function SideBar(props: { className?: string }) {
shadow
/>
</a>
</div>
</div> */}
</>
}
secondaryAction={

View File

@ -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";

149
app/icons/chebichat-big.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 85 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -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 (
<html lang="en">
<head>
@ -51,6 +57,7 @@ export default function RootLayout({
</head>
<body>
{children}
{serverConfig?.isVercel && (
<>
<SpeedInsights />
@ -66,6 +73,8 @@ export default function RootLayout({
<GoogleAnalytics gaId={serverConfig.gaId} />
</>
)}
{/* DONG BO VOI CLOUD SYNC UPSTASH */}
<SyncOnFirstLoad />
</body>
</html>
);

View File

@ -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",

View File

@ -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",

View File

@ -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,

View File

@ -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);

2
git.sh
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/favicon-chebi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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"}