cap nhat giao dien
1
Chebichat.md
Normal file
@ -0,0 +1 @@
|
||||
app/store/config.ts
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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";
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -518,8 +518,8 @@ export function ImagePreviewer(props: {
|
||||
<NextImage
|
||||
src={ChatGptIcon.src}
|
||||
alt="logo"
|
||||
width={50}
|
||||
height={50}
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -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> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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)} />
|
||||
|
@ -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={
|
||||
|
@ -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
After Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 97 KiB |
@ -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>
|
||||
);
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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
@ -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
|
BIN
public/android-chrome-192x192 copy.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
public/apple-touch-icon copy.png
Normal file
After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
BIN
public/favicon-16x16 copy.png
Normal file
After Width: | Height: | Size: 941 B |
BIN
public/favicon-32x32 copy.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
public/favicon-chebi.png
Normal file
After Width: | Height: | Size: 315 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
1
public/site copy.webmanifest
Normal 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"}
|