Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Hk-Gosuto 2024-02-09 08:23:26 +08:00
commit b56d2e6b4f
17 changed files with 665 additions and 144 deletions

View File

@ -43,12 +43,9 @@ jobs:
- os: ubuntu-latest - os: ubuntu-latest
arch: x86_64 arch: x86_64
rust_target: x86_64-unknown-linux-gnu rust_target: x86_64-unknown-linux-gnu
- os: macos-latest
arch: x86_64
rust_target: x86_64-apple-darwin
- os: macos-latest - os: macos-latest
arch: aarch64 arch: aarch64
rust_target: aarch64-apple-darwin rust_target: x86_64-apple-darwin,aarch64-apple-darwin
- os: windows-latest - os: windows-latest
arch: x86_64 arch: x86_64
rust_target: x86_64-pc-windows-msvc rust_target: x86_64-pc-windows-msvc
@ -60,13 +57,14 @@ jobs:
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18
cache: 'yarn'
- name: install Rust stable - name: install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
targets: ${{ matrix.config.rust_target }} targets: ${{ matrix.config.rust_target }}
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: with:
key: ${{ matrix.config.rust_target }} key: ${{ matrix.config.os }}
- name: install dependencies (ubuntu only) - name: install dependencies (ubuntu only)
if: matrix.config.os == 'ubuntu-latest' if: matrix.config.os == 'ubuntu-latest'
run: | run: |
@ -79,8 +77,15 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with: with:
releaseId: ${{ needs.create-release.outputs.release_id }} releaseId: ${{ needs.create-release.outputs.release_id }}
args: ${{ matrix.config.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
publish-release: publish-release:
permissions: permissions:

View File

@ -5,6 +5,7 @@ on:
types: types:
- opened - opened
- synchronize - synchronize
- reopened
env: env:
VERCEL_TEAM: ${{ secrets.VERCEL_TEAM }} VERCEL_TEAM: ${{ secrets.VERCEL_TEAM }}

View File

@ -171,7 +171,7 @@ export function getHeaders(ignoreHeaders?: boolean) {
const accessStore = useAccessStore.getState(); const accessStore = useAccessStore.getState();
let headers: Record<string, string> = {}; let headers: Record<string, string> = {};
const modelConfig = useChatStore.getState().currentSession().mask.modelConfig; const modelConfig = useChatStore.getState().currentSession().mask.modelConfig;
const isGoogle = modelConfig.model === "gemini-pro"; const isGoogle = modelConfig.model.startsWith("gemini");
if (!ignoreHeaders && !isGoogle) { if (!ignoreHeaders && !isGoogle) {
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@ -13,15 +13,8 @@ import {
LLMUsage, LLMUsage,
} from "../api"; } from "../api";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store"; import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import {
EventStreamContentType,
fetchEventSource,
} from "@fortaine/fetch-event-source";
import { prettyObject } from "@/app/utils/format";
import { getClientConfig } from "@/app/config/client"; import { getClientConfig } from "@/app/config/client";
import Locale from "../../locales"; import { DEFAULT_API_HOST } from "@/app/constant";
import { getServerSideConfig } from "@/app/config/server";
import de from "@/app/locales/de";
export class GeminiProApi implements LLMApi { export class GeminiProApi implements LLMApi {
toolAgentChat(options: AgentChatOptions): Promise<void> { toolAgentChat(options: AgentChatOptions): Promise<void> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
@ -36,7 +29,7 @@ export class GeminiProApi implements LLMApi {
); );
} }
async chat(options: ChatOptions): Promise<void> { async chat(options: ChatOptions): Promise<void> {
const apiClient = this; // const apiClient = this;
const messages = options.messages.map((v) => ({ const messages = options.messages.map((v) => ({
role: v.role.replace("assistant", "model").replace("system", "user"), role: v.role.replace("assistant", "model").replace("system", "user"),
parts: [{ text: v.content }], parts: [{ text: v.content }],
@ -94,13 +87,27 @@ export class GeminiProApi implements LLMApi {
], ],
}; };
console.log("[Request] google payload: ", requestPayload); const accessStore = useAccessStore.getState();
let baseUrl = accessStore.googleUrl;
const isApp = !!getClientConfig()?.isApp;
const shouldStream = !!options.config.stream; let shouldStream = !!options.config.stream;
const controller = new AbortController(); const controller = new AbortController();
options.onController?.(controller); options.onController?.(controller);
try { try {
const chatPath = this.path(Google.ChatPath); let chatPath = this.path(Google.ChatPath);
// let baseUrl = accessStore.googleUrl;
if (!baseUrl) {
baseUrl = isApp
? DEFAULT_API_HOST + "/api/proxy/google/" + Google.ChatPath
: chatPath;
}
if (isApp) {
baseUrl += `?key=${accessStore.googleApiKey}`;
}
const chatPayload = { const chatPayload = {
method: "POST", method: "POST",
body: JSON.stringify(requestPayload), body: JSON.stringify(requestPayload),
@ -116,10 +123,6 @@ export class GeminiProApi implements LLMApi {
if (shouldStream) { if (shouldStream) {
let responseText = ""; let responseText = "";
let remainText = ""; let remainText = "";
let streamChatPath = chatPath.replace(
"generateContent",
"streamGenerateContent",
);
let finished = false; let finished = false;
let existingTexts: string[] = []; let existingTexts: string[] = [];
@ -149,7 +152,11 @@ export class GeminiProApi implements LLMApi {
// start animaion // start animaion
animateResponseText(); animateResponseText();
fetch(streamChatPath, chatPayload)
fetch(
baseUrl.replace("generateContent", "streamGenerateContent"),
chatPayload,
)
.then((response) => { .then((response) => {
const reader = response?.body?.getReader(); const reader = response?.body?.getReader();
const decoder = new TextDecoder(); const decoder = new TextDecoder();
@ -200,11 +207,9 @@ export class GeminiProApi implements LLMApi {
console.error("Error:", error); console.error("Error:", error);
}); });
} else { } else {
const res = await fetch(chatPath, chatPayload); const res = await fetch(baseUrl, chatPayload);
clearTimeout(requestTimeoutId); clearTimeout(requestTimeoutId);
const resJson = await res.json(); const resJson = await res.json();
if (resJson?.promptFeedback?.blockReason) { if (resJson?.promptFeedback?.blockReason) {
// being blocked // being blocked
options.onError?.( options.onError?.(

View File

@ -1,3 +1,4 @@
"use client";
import { import {
ApiPath, ApiPath,
DEFAULT_API_HOST, DEFAULT_API_HOST,
@ -53,7 +54,9 @@ export class ChatGPTApi implements LLMApi {
if (baseUrl.length === 0) { if (baseUrl.length === 0) {
const isApp = !!getClientConfig()?.isApp; const isApp = !!getClientConfig()?.isApp;
baseUrl = isApp ? DEFAULT_API_HOST : ApiPath.OpenAI; baseUrl = isApp
? DEFAULT_API_HOST + "/proxy" + ApiPath.OpenAI
: ApiPath.OpenAI;
} }
if (baseUrl.endsWith("/")) { if (baseUrl.endsWith("/")) {
@ -68,6 +71,8 @@ export class ChatGPTApi implements LLMApi {
return [baseUrl, model, path].join("/"); return [baseUrl, model, path].join("/");
} }
console.log("[Proxy Endpoint] ", baseUrl, path);
return [baseUrl, path].join("/"); return [baseUrl, path].join("/");
} }

View File

@ -307,7 +307,7 @@ export function PreviewActions(props: {
setShouldExport(false); setShouldExport(false);
var api: ClientApi; var api: ClientApi;
if (config.modelConfig.model === "gemini-pro") { if (config.modelConfig.model.startsWith("gemini")) {
api = new ClientApi(ModelProvider.GeminiPro); api = new ClientApi(ModelProvider.GeminiPro);
} else { } else {
api = new ClientApi(ModelProvider.GPT); api = new ClientApi(ModelProvider.GPT);

View File

@ -178,7 +178,7 @@ export function useLoadData() {
const config = useAppConfig(); const config = useAppConfig();
var api: ClientApi; var api: ClientApi;
if (config.modelConfig.model === "gemini-pro") { if (config.modelConfig.model.startsWith("gemini")) {
api = new ClientApi(ModelProvider.GeminiPro); api = new ClientApi(ModelProvider.GeminiPro);
} else { } else {
api = new ClientApi(ModelProvider.GPT); api = new ClientApi(ModelProvider.GPT);

View File

@ -92,7 +92,7 @@ export function ModelConfigList(props: {
></input> ></input>
</ListItem> </ListItem>
{props.modelConfig.model === "gemini-pro" ? null : ( {props.modelConfig.model.startsWith("gemini") ? null : (
<> <>
<ListItem <ListItem
title={Locale.Settings.PresencePenalty.Title} title={Locale.Settings.PresencePenalty.Title}

View File

@ -8,8 +8,7 @@ export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/c
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`; export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
export const RUNTIME_CONFIG_DOM = "danger-runtime-config"; export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
export const DEFAULT_CORS_HOST = "https://a.nextweb.fun"; export const DEFAULT_API_HOST = "https://api.nextchat.dev";
export const DEFAULT_API_HOST = `${DEFAULT_CORS_HOST}/api/proxy`;
export const OPENAI_BASE_URL = "https://api.openai.com"; export const OPENAI_BASE_URL = "https://api.openai.com";
export const GOOGLE_BASE_URL = "https://generativelanguage.googleapis.com"; export const GOOGLE_BASE_URL = "https://generativelanguage.googleapis.com";

View File

@ -12,7 +12,9 @@ import { ensure } from "../utils/clone";
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
const DEFAULT_OPENAI_URL = const DEFAULT_OPENAI_URL =
getClientConfig()?.buildMode === "export" ? DEFAULT_API_HOST : ApiPath.OpenAI; getClientConfig()?.buildMode === "export"
? DEFAULT_API_HOST + "/api/proxy/openai"
: ApiPath.OpenAI;
const DEFAULT_ACCESS_STATE = { const DEFAULT_ACCESS_STATE = {
accessCode: "", accessCode: "",

View File

@ -100,14 +100,18 @@ function countMessages(msgs: ChatMessage[]) {
} }
function fillTemplateWith(input: string, modelConfig: ModelConfig) { function fillTemplateWith(input: string, modelConfig: ModelConfig) {
const cutoff = KnowledgeCutOffDate[modelConfig.model] ?? KnowledgeCutOffDate.default; const cutoff =
KnowledgeCutOffDate[modelConfig.model] ?? KnowledgeCutOffDate.default;
// Find the model in the DEFAULT_MODELS array that matches the modelConfig.model // Find the model in the DEFAULT_MODELS array that matches the modelConfig.model
const modelInfo = DEFAULT_MODELS.find(m => m.name === modelConfig.model); const modelInfo = DEFAULT_MODELS.find((m) => m.name === modelConfig.model);
if (!modelInfo) {
throw new Error(`Model ${modelConfig.model} not found in DEFAULT_MODELS array.`); var serviceProvider = "OpenAI";
if (modelInfo) {
// TODO: auto detect the providerName from the modelConfig.model
// Directly use the providerName from the modelInfo
serviceProvider = modelInfo.provider.providerName;
} }
// Directly use the providerName from the modelInfo
const serviceProvider = modelInfo.provider.providerName;
const vars = { const vars = {
ServiceProvider: serviceProvider, ServiceProvider: serviceProvider,
@ -127,7 +131,7 @@ function fillTemplateWith(input: string, modelConfig: ModelConfig) {
} }
Object.entries(vars).forEach(([name, value]) => { Object.entries(vars).forEach(([name, value]) => {
const regex = new RegExp(`{{${name}}}`, 'g'); const regex = new RegExp(`{{${name}}}`, "g");
output = output.replace(regex, value.toString()); // Ensure value is a string output = output.replace(regex, value.toString()); // Ensure value is a string
}); });
@ -404,7 +408,7 @@ export const useChatStore = createPersistStore(
}, },
}); });
} else { } else {
if (modelConfig.model === "gemini-pro") { if (modelConfig.model.startsWith("gemini")) {
api = new ClientApi(ModelProvider.GeminiPro); api = new ClientApi(ModelProvider.GeminiPro);
} }
// make request // make request
@ -587,7 +591,7 @@ export const useChatStore = createPersistStore(
const modelConfig = session.mask.modelConfig; const modelConfig = session.mask.modelConfig;
var api: ClientApi; var api: ClientApi;
if (modelConfig.model === "gemini-pro") { if (modelConfig.model.startsWith("gemini")) {
api = new ClientApi(ModelProvider.GeminiPro); api = new ClientApi(ModelProvider.GeminiPro);
} else { } else {
api = new ClientApi(ModelProvider.GPT); api = new ClientApi(ModelProvider.GPT);

View File

@ -1,8 +1,8 @@
import { getClientConfig } from "../config/client"; import { getClientConfig } from "../config/client";
import { ApiPath, DEFAULT_CORS_HOST } from "../constant"; import { ApiPath, DEFAULT_API_HOST } from "../constant";
export function corsPath(path: string) { export function corsPath(path: string) {
const baseUrl = getClientConfig()?.isApp ? `${DEFAULT_CORS_HOST}` : ""; const baseUrl = getClientConfig()?.isApp ? `${DEFAULT_API_HOST}` : "";
if (!path.startsWith("/")) { if (!path.startsWith("/")) {
path = "/" + path; path = "/" + path;

View File

@ -64,8 +64,17 @@ if (mode !== "export") {
nextConfig.rewrites = async () => { nextConfig.rewrites = async () => {
const ret = [ const ret = [
// adjust for previous verison directly using "/api/proxy/" as proxy base route
{ {
source: "/api/proxy/:path*", source: "/api/proxy/v1/:path*",
destination: "https://api.openai.com/v1/:path*",
},
{
source: "/api/proxy/google/:path*",
destination: "https://generativelanguage.googleapis.com/:path*",
},
{
source: "/api/proxy/openai/:path*",
destination: "https://api.openai.com/:path*", destination: "https://api.openai.com/:path*",
}, },
{ {

View File

@ -1,5 +1,5 @@
{ {
"name": "chatgpt-next-web", "name": "nextchat",
"private": false, "private": false,
"license": "mit", "license": "mit",
"scripts": { "scripts": {
@ -80,4 +80,4 @@
"resolutions": { "resolutions": {
"lint-staged/yaml": "^2.2.2" "lint-staged/yaml": "^2.2.2"
} }
} }

652
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +1,45 @@
[package] [package]
name = "chatgpt-next-web" name = "nextchat"
version = "0.1.0" version = "0.1.0"
description = "A cross platform app for LLM ChatBot." description = "A cross platform app for LLM ChatBot."
authors = ["Yidadaa"] authors = ["Yidadaa"]
license = "mit" license = "mit"
repository = "" repository = ""
default-run = "chatgpt-next-web" default-run = "nextchat"
edition = "2021" edition = "2021"
rust-version = "1.60" rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies] [build-dependencies]
tauri-build = { version = "1.3.0", features = [] } tauri-build = { version = "1.5.1", features = [] }
[dependencies] [dependencies]
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.3.0", features = ["notification-all", "fs-all", "clipboard-all", "dialog-all", "shell-open", "updater", "window-close", "window-hide", "window-maximize", "window-minimize", "window-set-icon", "window-set-ignore-cursor-events", "window-set-resizable", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] } tauri = { version = "1.5.4", features = [
"notification-all",
"fs-all",
"clipboard-all",
"dialog-all",
"shell-open",
"updater",
"window-close",
"window-hide",
"window-maximize",
"window-minimize",
"window-set-icon",
"window-set-ignore-cursor-events",
"window-set-resizable",
"window-show",
"window-start-dragging",
"window-unmaximize",
"window-unminimize",
] }
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
[features] [features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes. # If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!! # DO NOT REMOVE!!
custom-protocol = [ "tauri/custom-protocol" ] custom-protocol = ["tauri/custom-protocol"]

View File

@ -9,7 +9,7 @@
}, },
"package": { "package": {
"productName": "NextChat", "productName": "NextChat",
"version": "2.10.1" "version": "2.10.3"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {
@ -86,7 +86,8 @@
} }
}, },
"security": { "security": {
"csp": null "csp": null,
"dangerousUseHttpScheme": true
}, },
"updater": { "updater": {
"active": true, "active": true,