diff --git a/README.md b/README.md index 160a822..33c9bc3 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,7 @@ SQL Chat is built by [Next.js](https://nextjs.org/), it supports the following d ## Data Privacy -- All database connection configs are stored locally in your browser. You can also visit settings to clear the data. - -- Only the database schema will be sent to the OpenAI API. No table data will be sent there. - -- If you use [sqlchat.ai](https://sqlchat.ai), it will record the anonymised conversations. +See [SQL Chat Privacy Policy](https://sqlchat.ai/privacy). ## IP Whitelisting diff --git a/public/privacy.md b/public/privacy.md new file mode 100644 index 0000000..6b7ed96 --- /dev/null +++ b/public/privacy.md @@ -0,0 +1,46 @@ +# SQL Chat Privacy Policy + +*Last Updated: May 21, 2023* + +We respect your privacy and are committed to protecting your personal information. This Privacy Policy explains how we collect, use, and disclose your personal information when you use our application that calls LLM provider's API. + +## Collection of Application Data + +* All database connection configs are stored locally in your browser. You can also visit settings to clear the data. +* We send the database schema to the LLM provider. No table data will be sent there. +* We collect the conversation message to improve the product. + +## Collection of Personal Information + +We may collect certain personal information from you when you use our application, such as your name and email address. We collect this information in order to communicate with you about our services and to provide you with technical support. + +## Use of Personal Information + +We may use your personal information to: + +* Communicate with you about our services +* Provide you with technical support +* Improve our application and services +* Comply with legal obligations + +## Disclosure of Personal Information + +We do not sell, trade, or otherwise transfer your personal information to third parties. However, we may disclose your personal information in the following circumstances: + +* To our service providers who assist us in providing our services to you +* To comply with legal obligations or to protect our rights or the rights of others + +## Security of Personal Information + +We take reasonable measures to protect your personal information from unauthorized access, disclosure, or destruction. However, no method of transmission over the internet or electronic storage is completely secure, and we cannot guarantee absolute security. + + +## Changes to this Privacy Policy + +We may update this Privacy Policy from time to time by posting a new versionon our website. We encourage you to review this Privacy Policy periodically for any changes. Your continued use of our application and services after any modifications to this Privacy Policy will constitute your acceptance of such modifications. + +For more information about our privacy and data protection practices, including how to exercise your privacy rights, please visit the [Bytebase Privacy Policy](https://bytebase.com/privacy). + +## Contact Us + +Please contact us at support@bytebase.com if you have questions about this Privacy Policy. \ No newline at end of file diff --git a/public/terms.md b/public/terms.md new file mode 100644 index 0000000..39c9383 --- /dev/null +++ b/public/terms.md @@ -0,0 +1,12 @@ +# Terms of Service + +*Last Updated: May 21, 2023* + +## Refund Policy + +All fees and charges are earned upon receipt by us and are nonrefundable. + + +## Contact Us + +Please contact us at support@bytebase.com if you have questions about this Terms of Service. \ No newline at end of file diff --git a/src/components/ConversationView/Header.tsx b/src/components/ConversationView/Header.tsx index 8b190f5..8145bc2 100644 --- a/src/components/ConversationView/Header.tsx +++ b/src/components/ConversationView/Header.tsx @@ -49,9 +49,11 @@ const Header = (props: Props) => { > - + {hasFeature("debug") && ( + + )} diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index e2e5e9d..39e6979 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -217,6 +217,9 @@ const ConversationView = () => { if (settingStore.setting.openAIApiConfig?.endpoint) { requestHeaders["x-openai-endpoint"] = settingStore.setting.openAIApiConfig?.endpoint; } + if (settingStore.setting.openAIApiConfig?.model) { + requestHeaders["x-openai-model"] = settingStore.setting.openAIApiConfig?.model; + } const rawRes = await fetch("/api/chat", { method: "POST", body: JSON.stringify({ @@ -311,7 +314,7 @@ const ConversationView = () => { messages: usageMessageList, }, { - headers: session?.user.id ? { Authorization: `Bearer ${session?.user.id}` } : undefined, + headers: requestHeaders, } ) .catch(() => { diff --git a/src/components/CreateConnectionModal.tsx b/src/components/CreateConnectionModal.tsx index 2d61872..4c6d515 100644 --- a/src/components/CreateConnectionModal.tsx +++ b/src/components/CreateConnectionModal.tsx @@ -4,27 +4,28 @@ import { toast } from "react-hot-toast"; import TextareaAutosize from "react-textarea-autosize"; import { useConnectionStore } from "@/store"; import { Connection, Engine, ResponseObject, SSLOptions } from "@/types"; -import Select from "./kit/Select"; +import Radio from "./kit/Radio"; import TextField from "./kit/TextField"; import Modal from "./kit/Modal"; import Icon from "./Icon"; import DataStorageBanner from "./DataStorageBanner"; import ActionConfirmModal from "./ActionConfirmModal"; import { useTranslation } from "react-i18next"; +import EngineIcon from "./EngineIcon"; interface Props { connection?: Connection; close: () => void; } -type SSLType = "none" | "ca-only" | "full"; +type SSLType = "preferred" | "ca-only" | "full"; type SSLFieldType = "ca" | "cert" | "key"; const SSLTypeOptions = [ { - label: "None", - value: "none", + label: "Preferred", + value: "preferred", }, { label: "CA Only", @@ -36,19 +37,35 @@ const SSLTypeOptions = [ }, ]; -const defaultPort = { - [Engine.MySQL]: "3306", - [Engine.PostgreSQL]: "5432", - [Engine.MSSQL]: "1433", - [Engine.TiDBServerless]: "4000", -}; +const engines = [ + { + type: Engine.MySQL, + name: "MySQL", + defaultPort: "3306", + }, + { + type: Engine.PostgreSQL, + name: "PostgreSQL", + defaultPort: "5432", + }, + { + type: Engine.MSSQL, + name: "SQL Server", + defaultPort: "1433", + }, + { + type: Engine.TiDBServerless, + name: "TiDB Serverless", + defaultPort: "4000", + }, +]; const defaultConnection: Connection = { id: "", title: "", engineType: Engine.MySQL, host: "", - port: defaultPort[Engine.MySQL], + port: "3306", username: "", password: "", }; @@ -59,7 +76,7 @@ const CreateConnectionModal = (props: Props) => { const connectionStore = useConnectionStore(); const [connection, setConnection] = useState(defaultConnection); const [showDeleteConnectionModal, setShowDeleteConnectionModal] = useState(false); - const [sslType, setSSLType] = useState("none"); + const [sslType, setSSLType] = useState("preferred"); const [selectedSSLField, setSelectedSSLField] = useState("ca"); const [isRequesting, setIsRequesting] = useState(false); const showDatabaseField = connection.engineType === Engine.PostgreSQL; @@ -102,7 +119,6 @@ const CreateConnectionModal = (props: Props) => { setConnection({ ...connection, ...state, - port: defaultPort[state.engineType || Engine.MySQL], }); }; @@ -216,17 +232,24 @@ const CreateConnectionModal = (props: Props) => {
- setSSLType(e.target.value as SSLType)} + onChange={(value) => setSSLType(value as SSLType)} /> {option.label} ))}
- {sslType !== "none" && ( + {sslType !== "preferred" && ( <>
{
{isEditing && ( )}
diff --git a/src/components/MarkdownRenderer.tsx b/src/components/MarkdownRenderer.tsx new file mode 100644 index 0000000..7e2445d --- /dev/null +++ b/src/components/MarkdownRenderer.tsx @@ -0,0 +1,33 @@ +import React, { useState, useEffect } from "react"; +import ReactMarkdown from "react-markdown"; + +interface MarkdownRendererProps { + url: string; +} + +function MarkdownRenderer(props: MarkdownRendererProps) { + const [markdown, setMarkdown] = useState(""); + + useEffect(() => { + const fetchData = async () => { + const response = await fetch(props.url); + const data = await response.text(); + setMarkdown(data); + }; + fetchData(); + }, [props.url]); + + return ( +

{children}

, + li: ({ children }) =>
  • {children}
  • , + }} + > + {markdown} +
    + ); +} + +export default MarkdownRenderer; diff --git a/src/components/OpenAIApiConfigView.tsx b/src/components/OpenAIApiConfigView.tsx index 9c875a9..b45fac7 100644 --- a/src/components/OpenAIApiConfigView.tsx +++ b/src/components/OpenAIApiConfigView.tsx @@ -3,14 +3,31 @@ import { useTranslation } from "react-i18next"; import { useDebounce } from "react-use"; import { useSettingStore } from "@/store"; import { OpenAIApiConfig } from "@/types"; +import Radio from "./kit/Radio"; import TextField from "./kit/TextField"; - +import Tooltip from "./kit/Tooltip"; const OpenAIApiConfigView = () => { const { t } = useTranslation(); const settingStore = useSettingStore(); const [openAIApiConfig, setOpenAIApiConfig] = useState(settingStore.setting.openAIApiConfig); const [maskKey, setMaskKey] = useState(true); + const models = [ + { + id: "gpt-3.5-turbo", + title: `GPT-3.5 (${t("setting.openai-api-configuration.quota-per-ask", { count: 1 })})`, + disabled: false, + tooltip: "", + }, + // Disable GPT-4 if user doesn't provide key (because SQL Chat own key hasn't been whitelisted yet). + { + id: "gpt-4", + title: `GPT-4 (${t("setting.openai-api-configuration.quota-per-ask", { count: 10 })})`, + disabled: !settingStore.setting.openAIApiConfig.key, + tooltip: t("setting.openai-api-configuration.provide-gpt4-key"), + }, + ]; + const maskedKey = (str: string) => { if (str.length < 7) { return str; @@ -37,21 +54,58 @@ const OpenAIApiConfigView = () => { setMaskKey(false); }; + const modelRadio = (model: any) => { + return ( +
    + handleSetOpenAIApiConfig({ model: value })} + /> + +
    + ); + }; + return ( <>
    -
    - +
    + +

    {t("setting.openai-api-configuration.model-description")}

    +
    +
    + {models.map((model) => + model.disabled ? ( + + {modelRadio(model)} + + ) : ( + modelRadio(model) + ) + )} +
    +
    +
    +
    + +

    {t("setting.openai-api-configuration.key-description")}

    handleSetOpenAIApiConfig({ key: value })} />
    -
    - +
    + +

    {t("setting.openai-api-configuration.endpoint-description")}

    handleSetOpenAIApiConfig({ endpoint: value })} /> diff --git a/src/components/QuotaView.tsx b/src/components/QuotaView.tsx index 25bf409..abd7a20 100644 --- a/src/components/QuotaView.tsx +++ b/src/components/QuotaView.tsx @@ -58,7 +58,7 @@ const QuotaView = (props: Props) => {
    {t("common.quota")}
    -
    = quota.limit ? "text-red-600" : "text-black"}> +
    = quota.limit ? "text-red-600" : "text-black dark:text-gray-300"}> {quota.current}/{quota.limit}
    diff --git a/src/components/SettingView.tsx b/src/components/SettingGeneralView.tsx similarity index 81% rename from src/components/SettingView.tsx rename to src/components/SettingGeneralView.tsx index e8e73cf..6679450 100644 --- a/src/components/SettingView.tsx +++ b/src/components/SettingGeneralView.tsx @@ -12,12 +12,12 @@ import LocaleSelector from "./LocaleSelector"; import ThemeSelector from "./ThemeSelector"; import OpenAIApiConfigView from "./OpenAIApiConfigView"; -const SettingView = () => { +const SettingGeneralView = () => { const { t } = useTranslation(); const { data: session } = useSession(); return ( -
    + + +
    {t("setting.data.clear-all-data")} @@ -71,4 +84,4 @@ const SettingView = () => { ); }; -export default SettingView; +export default SettingGeneralView; diff --git a/src/components/SubscriptionHistoryTable.tsx b/src/components/SubscriptionHistoryTable.tsx index eceaf1e..60ecbce 100644 --- a/src/components/SubscriptionHistoryTable.tsx +++ b/src/components/SubscriptionHistoryTable.tsx @@ -65,7 +65,7 @@ const SubscriptionHistoryTable = () => { - {t("setting.billing.view-receipt")} + {t("setting.subscription.view-receipt")} diff --git a/src/components/kit/Radio.tsx b/src/components/kit/Radio.tsx new file mode 100644 index 0000000..694c887 --- /dev/null +++ b/src/components/kit/Radio.tsx @@ -0,0 +1,39 @@ +import { HTMLInputTypeAttribute } from "react"; + +interface Props { + value: string; + onChange?: (value: string) => void; + type?: HTMLInputTypeAttribute; + className?: string; + disabled?: boolean; + checked?: boolean; +} + +const getDefaultProps = () => ({ + value: "", + onChange: () => {}, + type: "radio", + className: "", + disabled: false, + checked: false, +}); + +const Radio = (props: Props) => { + const { value, disabled, className, type, checked, onChange } = { + ...getDefaultProps(), + ...props, + }; + + return ( + onChange(e.target.value)} + /> + ); +}; + +export default Radio; diff --git a/src/lib/connectors/mysql/index.ts b/src/lib/connectors/mysql/index.ts index 91ec737..f8d93f6 100644 --- a/src/lib/connectors/mysql/index.ts +++ b/src/lib/connectors/mysql/index.ts @@ -19,6 +19,11 @@ const getMySQLConnection = async (connection: Connection): Promise { +const systemSchemas = + "'information_schema', 'pg_catalog', 'pg_toast', '_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_internal', '_timescaledb_config', 'timescaledb_information', 'timescaledb_experimental'"; + +const systemTables = "'_prisma_migrations'"; + +const newPostgresClient = async (connection: Connection) => { const clientConfig: ClientConfig = { host: connection.host, port: Number(connection.port), user: connection.username, password: connection.password, database: connection.database, + application_name: "sqlchat", }; if (connection.ssl) { clientConfig.ssl = { @@ -16,21 +22,27 @@ const newPostgresClient = (connection: Connection) => { cert: connection.ssl?.cert, key: connection.ssl?.key, }; + } else { + // rejectUnauthorized=false to infer sslmode=prefer since hosted PG venders have SSL enabled. + clientConfig.ssl = { + rejectUnauthorized: false, + }; } - return new Client(clientConfig); + + let client = new Client(clientConfig); + await client.connect(); + return client; }; const testConnection = async (connection: Connection): Promise => { - const client = newPostgresClient(connection); - await client.connect(); + const client = await newPostgresClient(connection); await client.end(); return true; }; const execute = async (connection: Connection, databaseName: string, statement: string): Promise => { connection.database = databaseName; - const client = newPostgresClient(connection); - await client.connect(); + const client = await newPostgresClient(connection); const { rows, rowCount } = await client.query(statement); await client.end(); @@ -46,8 +58,7 @@ const execute = async (connection: Connection, databaseName: string, statement: }; const getDatabases = async (connection: Connection): Promise => { - const client = newPostgresClient(connection); - await client.connect(); + const client = await newPostgresClient(connection); if (connection.database) { await client.end(); return [connection.database]; @@ -67,16 +78,19 @@ const getDatabases = async (connection: Connection): Promise => { const getTables = async (connection: Connection, databaseName: string): Promise => { connection.database = databaseName; - const client = newPostgresClient(connection); - await client.connect(); + const client = await newPostgresClient(connection); const { rows } = await client.query( - `SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE' AND table_catalog=$1;`, + `SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema NOT IN (${systemSchemas}) AND table_name NOT IN (${systemTables}) AND table_type='BASE TABLE' AND table_catalog=$1;`, [databaseName] ); await client.end(); const tableList = []; for (const row of rows) { if (row["table_name"]) { + if (row["table_schema"] !== "public") { + tableList.push(`${row["table_schema"]}.${row["table_name"]}`); + continue; + } tableList.push(row["table_name"]); } } @@ -90,10 +104,9 @@ const getTableStructure = async ( structureFetched: (tableName: string, structure: string) => void ): Promise => { connection.database = databaseName; - const client = newPostgresClient(connection); - await client.connect(); + const client = await newPostgresClient(connection); const { rows } = await client.query( - `SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema='public' AND table_name=$1;`, + `SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema NOT IN (${systemSchemas}) AND table_name=$1;`, [tableName] ); await client.end(); @@ -119,12 +132,11 @@ const getTableStructureBatch = async ( structureFetched: (tableName: string, structure: string) => void ): Promise => { connection.database = databaseName; - const client = newPostgresClient(connection); - await client.connect(); + const client = await newPostgresClient(connection); await Promise.all( tableNameList.map(async (tableName) => { const { rows } = await client.query( - `SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema='public' AND table_name=$1;`, + `SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema NOT IN (${systemSchemas}) AND table_name=$1;`, [tableName] ); const columnList = []; diff --git a/src/locales/en.json b/src/locales/en.json index 8a5d334..e9e397a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -68,11 +68,11 @@ "upgrade": "Upgrade", "renew": "Renew", "expired": "Expired", - "n-question-per-month": "{{count}} questions / month", + "n-question-per-month": "{{count}} quota / month", "early-bird-checkout": "Early bird discount, 50% off for 1 year" }, - "billing": { - "self": "Billing", + "subscription": { + "self": "Subscription", "view-receipt": "View receipt" }, "account": { @@ -89,7 +89,13 @@ "dark": "Dark" }, "openai-api-configuration": { - "self": "OpenAI API configuration" + "self": "OpenAI API configuration", + "model": "Model", + "model-description": "Quota won't be consumed if you provide your own key below.", + "quota-per-ask": "{{count}} quata per ask", + "provide-gpt4-key": "Require your own GPT-4 enabled API key", + "key-description": "Bring your own key to waive quota requirement.", + "endpoint-description": "Optional endpoint pointing to your own compatible server or gateway." }, "data": { "self": "Data", diff --git a/src/locales/es.json b/src/locales/es.json index d15cd9e..7e1970e 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -66,11 +66,11 @@ "upgrade": "Mejora", "renew": "Renovar", "expired": "Expirado", - "n-question-per-month": "{{count}} preguntas / mes", + "n-question-per-month": "{{count}} Cuota / mes", "early-bird-checkout": "Descuento por reserva anticipada, 50 % de descuento durante 1 año" }, - "billing": { - "self": "Facturación", + "subscription": { + "self": "Suscripción", "view-receipt": "Ver recibo" }, "account": { @@ -87,7 +87,13 @@ "dark": "Oscuro" }, "openai-api-configuration": { - "self": "Configuración del API de OpenAI" + "self": "Configuración del API de OpenAI", + "model": "Modelo", + "model-description": "La cuota no se consumirá si proporciona su propia clave a continuación.", + "quota-per-ask": "{{count}} cuotas por pedido", + "provide-gpt4-key": "Requerir su propia clave API habilitada para GPT-4", + "key-description": "Traiga su propia llave para renunciar al requisito de cuota.", + "endpoint-description": "Punto final opcional que apunta a su propio servidor o puerta de enlace compatible." }, "data": { "self": "Datos", diff --git a/src/locales/zh.json b/src/locales/zh.json index df87cc5..033c85c 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -68,11 +68,11 @@ "upgrade": "升级", "renew": "续费", "expired": "已过期", - "n-question-per-month": "{{count}} 次提问 / 月", + "n-question-per-month": "{{count}} 点额度 / 月", "early-bird-checkout": "早鸟优惠,5 折购买 1 年" }, - "billing": { - "self": "计费", + "subscription": { + "self": "订阅", "view-receipt": "查看收据" }, "account": { @@ -89,7 +89,13 @@ "dark": "深色" }, "openai-api-configuration": { - "self": "OpenAI API 配置" + "self": "OpenAI API 配置", + "model": "模型", + "model-description": "如果您提供自己的 key,额度是不会消耗的。", + "quota-per-ask": "每一个提问消耗 {{count}} 点额度", + "provide-gpt4-key": "需提供您自己的,可以使用 GPT-4 的 key", + "key-description": "一旦您提供了自己的 key,额度就不受限制了。", + "endpoint-description": "可选的 endpoint 指向接口兼容的服务器或者网关。" }, "data": { "self": "数据", diff --git a/src/pages/api/chat.ts b/src/pages/api/chat.ts index 3decfd0..976a647 100644 --- a/src/pages/api/chat.ts +++ b/src/pages/api/chat.ts @@ -1,6 +1,6 @@ import { createParser, ParsedEvent, ReconnectInterval } from "eventsource-parser"; import { NextRequest } from "next/server"; -import { openAIApiEndpoint, openAIApiKey, gpt35, hasFeature } from "@/utils"; +import { openAIApiEndpoint, openAIApiKey, hasFeature, getModel } from "@/utils"; // Needs Edge for streaming response. export const config = { @@ -90,6 +90,7 @@ const handler = async (req: NextRequest) => { } const apiEndpoint = getApiEndpoint(req.headers.get("x-openai-endpoint") || openAIApiEndpoint); + const model = getModel(req.headers.get("x-openai-model") || ""); const remoteRes = await fetch(apiEndpoint, { headers: { "Content-Type": "application/json", @@ -97,13 +98,14 @@ const handler = async (req: NextRequest) => { }, method: "POST", body: JSON.stringify({ - model: gpt35.name, + model: model.name, messages: reqBody.messages, - temperature: gpt35.temperature, - frequency_penalty: gpt35.frequency_penalty, - presence_penalty: gpt35.presence_penalty, + temperature: model.temperature, + frequency_penalty: model.frequency_penalty, + presence_penalty: model.presence_penalty, stream: true, - // Send end-user ID to help OpenAI monitor and detect abuse. + // Send end-user IP to help OpenAI monitor and detect abuse. + // It's intentionally not using email to avoid leaking user's email to OpenAI. user: req.ip, }), }); diff --git a/src/pages/api/checkout_sessions/index.ts b/src/pages/api/checkout_sessions/index.ts index 7cde7d2..c4920b3 100644 --- a/src/pages/api/checkout_sessions/index.ts +++ b/src/pages/api/checkout_sessions/index.ts @@ -30,15 +30,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const params: Stripe.Checkout.SessionCreateParams = { submit_type: "pay", mode: "payment", - payment_method_types: [ - "affirm", - // "alipay", - "card", - "cashapp", - "klarna", - "link", - // "wechat_pay", - ], + payment_method_types: ["affirm", "alipay", "card", "cashapp", "klarna", "link", "wechat_pay"], line_items: [ { price: process.env.STRIPE_PRICE_ID_PRO_1_YEAR_SUBSCRIPTION, diff --git a/src/pages/api/collect.ts b/src/pages/api/collect.ts index f0d5647..1e96795 100644 --- a/src/pages/api/collect.ts +++ b/src/pages/api/collect.ts @@ -1,7 +1,7 @@ import { PrismaClient } from "@prisma/client"; import { NextApiRequest, NextApiResponse } from "next"; import { Conversation, Message } from "@/types"; -import { gpt35 } from "@/utils"; +import { getModel, gpt35 } from "@/utils"; import { getEndUser } from "./auth/end-user"; const prisma = new PrismaClient(); @@ -36,7 +36,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) data: { id: conversation.id, createdAt: new Date(conversation.createdAt), - model: gpt35, + model: getModel((req.headers["x-openai-model"] as string) || ""), ctx: {}, messages: { create: messages.map((message) => ({ diff --git a/src/pages/privacy/index.tsx b/src/pages/privacy/index.tsx new file mode 100644 index 0000000..9da2dfd --- /dev/null +++ b/src/pages/privacy/index.tsx @@ -0,0 +1,8 @@ +import MarkdownRenderer from "@/components/MarkdownRenderer"; +import { NextPage } from "next"; + +const PrivacyPage: NextPage = () => { + return ; +}; + +export default PrivacyPage; diff --git a/src/pages/setting/index.tsx b/src/pages/setting/index.tsx index 2efb8ce..b6e00c4 100644 --- a/src/pages/setting/index.tsx +++ b/src/pages/setting/index.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"; import Link from "next/link"; import { Dialog, Transition } from "@headlessui/react"; import { ArrowUturnLeftIcon, Bars3Icon, Cog6ToothIcon, XMarkIcon, CreditCardIcon } from "@heroicons/react/24/outline"; -import SettingView from "../../components/SettingView"; +import SettingGeneralView from "../../components/SettingGeneralView"; import StripeCheckPaymentBanner from "../../components/StripeCheckPaymentBanner"; function classNames(...classes: string[]) { @@ -174,8 +174,8 @@ const SettingPage: NextPage = () => {
    {router.query.session_id && } -
    - +
    +
    diff --git a/src/pages/terms/index.tsx b/src/pages/terms/index.tsx new file mode 100644 index 0000000..8eaec0c --- /dev/null +++ b/src/pages/terms/index.tsx @@ -0,0 +1,8 @@ +import MarkdownRenderer from "@/components/MarkdownRenderer"; +import { NextPage } from "next"; + +const PrivacyPage: NextPage = () => { + return ; +}; + +export default PrivacyPage; diff --git a/src/store/setting.ts b/src/store/setting.ts index 7086e4c..b51abaa 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -10,6 +10,7 @@ const getDefaultSetting = (): Setting => { openAIApiConfig: { key: "", endpoint: "", + model: "gpt-3.5-turbo", }, }; }; diff --git a/src/types/setting.ts b/src/types/setting.ts index 636a85f..886dbf2 100644 --- a/src/types/setting.ts +++ b/src/types/setting.ts @@ -5,6 +5,7 @@ export type Theme = "light" | "dark" | "system"; export interface OpenAIApiConfig { key: string; endpoint: string; + model: string; } export interface Setting { diff --git a/src/utils/model.ts b/src/utils/model.ts index af555a4..0764f11 100644 --- a/src/utils/model.ts +++ b/src/utils/model.ts @@ -1,6 +1,24 @@ -export const gpt35 = { +const gpt35 = { name: "gpt-3.5-turbo", temperature: 0, frequency_penalty: 0.0, presence_penalty: 0.0, }; + +const gpt4 = { + name: "gpt-4", + temperature: 0, + frequency_penalty: 0.0, + presence_penalty: 0.0, +}; + +export const models = [gpt35, gpt4]; + +export const getModel = (name: string) => { + for (const model of models) { + if (model.name === name) { + return model; + } + } + return gpt35; +};