mirror of
https://github.com/coaidev/coai.git
synced 2025-05-20 05:20:15 +09:00
feat: customize send key
This commit is contained in:
parent
e9845f88c1
commit
e79c29e24a
@ -91,6 +91,7 @@
|
||||
|
||||
|
||||
## 📦 部署 | Deploy
|
||||
**当前安装需要额外安装 Deeptrain 统一账户管理,all in one功能正在开发中**
|
||||
```shell
|
||||
git clone https://github.com/Deeptrain-Community/chatnio.git
|
||||
cd chatnio
|
||||
|
@ -16,6 +16,7 @@ export type ChatProps = {
|
||||
message: string;
|
||||
model: string;
|
||||
web?: boolean;
|
||||
context?: number;
|
||||
ignore_context?: boolean;
|
||||
};
|
||||
|
||||
|
@ -75,7 +75,8 @@
|
||||
padding: 0.25rem 0.75rem !important;
|
||||
|
||||
span {
|
||||
margin-right: 0.5rem;
|
||||
font-size: 0.8rem !important;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "./ui/dropdown-menu.tsx";
|
||||
import {langs, setLanguage} from "@/i18n.ts";
|
||||
import { langs, setLanguage } from "@/i18n.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function I18nProvider() {
|
||||
@ -20,17 +20,15 @@ function I18nProvider() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{
|
||||
Object.entries(langs).map(([key, value]) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={key}
|
||||
checked={i18n.language === key}
|
||||
onClick={() => setLanguage(i18n, key)}
|
||||
>
|
||||
{value}
|
||||
</DropdownMenuCheckboxItem>
|
||||
))
|
||||
}
|
||||
{Object.entries(langs).map(([key, value]) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={key}
|
||||
checked={i18n.language === key}
|
||||
onClick={() => setLanguage(i18n, key)}
|
||||
>
|
||||
{value}
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
@ -13,7 +13,6 @@ import { login, tokenField } from "@/conf.ts";
|
||||
import { toggleMenu } from "@/store/menu.ts";
|
||||
import ProjectLink from "@/components/ProjectLink.tsx";
|
||||
import ModeToggle from "@/components/ThemeProvider.tsx";
|
||||
import I18nProvider from "@/components/I18nProvider.tsx";
|
||||
import router from "@/router.tsx";
|
||||
import MenuBar from "./MenuBar.tsx";
|
||||
import { getMemory } from "@/utils/memory.ts";
|
||||
@ -59,7 +58,6 @@ function NavBar() {
|
||||
<div className={`grow`} />
|
||||
<ProjectLink />
|
||||
<ModeToggle />
|
||||
<I18nProvider />
|
||||
{auth ? (
|
||||
<NavMenu />
|
||||
) : (
|
||||
|
@ -18,7 +18,11 @@ import { clearHistoryState, getQueryParam } from "@/utils/path.ts";
|
||||
import { forgetMemory, popMemory } from "@/utils/memory.ts";
|
||||
import { useToast } from "@/components/ui/use-toast.ts";
|
||||
import { ToastAction } from "@/components/ui/toast.tsx";
|
||||
import { alignSelector, contextSelector } from "@/store/settings.ts";
|
||||
import {
|
||||
alignSelector,
|
||||
contextSelector,
|
||||
historySelector,
|
||||
} from "@/store/settings.ts";
|
||||
import { FileArray } from "@/api/file.ts";
|
||||
import {
|
||||
MarketAction,
|
||||
@ -56,6 +60,7 @@ function ChatWrapper() {
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
const model = useSelector(selectModel);
|
||||
const web = useSelector(selectWeb);
|
||||
const history = useSelector(historySelector);
|
||||
const target = useRef(null);
|
||||
const context = useSelector(contextSelector);
|
||||
const align = useSelector(alignSelector);
|
||||
@ -68,13 +73,7 @@ function ChatWrapper() {
|
||||
setFiles([]);
|
||||
}
|
||||
|
||||
async function processSend(
|
||||
data: string,
|
||||
auth: boolean,
|
||||
model: string,
|
||||
web: boolean,
|
||||
context: boolean,
|
||||
): Promise<boolean> {
|
||||
async function processSend(data: string): Promise<boolean> {
|
||||
const message: string = formatMessage(files, data);
|
||||
if (message.length > 0 && data.trim().length > 0) {
|
||||
if (
|
||||
@ -83,6 +82,7 @@ function ChatWrapper() {
|
||||
message,
|
||||
web,
|
||||
model,
|
||||
context: history,
|
||||
ignore_context: !context,
|
||||
})
|
||||
) {
|
||||
@ -94,9 +94,9 @@ function ChatWrapper() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function handleSend(auth: boolean, model: string, web: boolean) {
|
||||
async function handleSend() {
|
||||
// because of the function wrapper, we need to update the selector state using props.
|
||||
if (await processSend(input, auth, model, web, context)) {
|
||||
if (await processSend(input)) {
|
||||
setInput("");
|
||||
}
|
||||
}
|
||||
@ -118,7 +118,7 @@ function ChatWrapper() {
|
||||
useEffect(() => {
|
||||
if (!init) return;
|
||||
const query = getQueryParam("q").trim();
|
||||
if (query.length > 0) processSend(query, auth, model, web, context).then();
|
||||
if (query.length > 0) processSend(query).then();
|
||||
clearHistoryState();
|
||||
}, [init]);
|
||||
|
||||
@ -168,14 +168,12 @@ function ChatWrapper() {
|
||||
target={target}
|
||||
value={input}
|
||||
onValueChange={setInput}
|
||||
onEnterPressed={async () => await handleSend(auth, model, web)}
|
||||
onEnterPressed={handleSend}
|
||||
/>
|
||||
</div>
|
||||
<ActionButton
|
||||
working={working}
|
||||
onClick={() =>
|
||||
working ? handleCancel() : handleSend(auth, model, web)
|
||||
}
|
||||
onClick={() => (working ? handleCancel() : handleSend())}
|
||||
/>
|
||||
</div>
|
||||
<div className={`input-options`}>
|
||||
|
@ -2,6 +2,8 @@ import React from "react";
|
||||
import { setMemory } from "@/utils/memory.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Textarea } from "@/components/ui/textarea.tsx";
|
||||
import { useSelector } from "react-redux";
|
||||
import { senderSelector } from "@/store/settings.ts";
|
||||
|
||||
type ChatInputProps = {
|
||||
className?: string;
|
||||
@ -20,6 +22,7 @@ function ChatInput({
|
||||
}: ChatInputProps) {
|
||||
const { t } = useTranslation();
|
||||
const [pressed, setPressed] = React.useState(false);
|
||||
const sender = useSelector(senderSelector);
|
||||
|
||||
return (
|
||||
<Textarea
|
||||
@ -37,7 +40,7 @@ function ChatInput({
|
||||
if (e.key === "Control") {
|
||||
setPressed(true);
|
||||
} else if (e.key === "Enter" && !e.shiftKey) {
|
||||
if (pressed) {
|
||||
if (sender || pressed) {
|
||||
e.preventDefault();
|
||||
onEnterPressed();
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import { Button } from "@/components/ui/button.tsx";
|
||||
import { expiredSelector, refreshSubscription } from "@/store/subscription.ts";
|
||||
import { Plus } from "lucide-react";
|
||||
import { subscriptionPrize } from "@/conf.ts";
|
||||
import { ToastAction } from "@/components/ui/toast.tsx";
|
||||
|
||||
function countPrize(base: number, month: number): number {
|
||||
const prize = subscriptionPrize[base] * month;
|
||||
@ -70,6 +71,14 @@ async function callBuyAction(
|
||||
toast({
|
||||
title: t("sub.failed"),
|
||||
description: t("sub.failed-prompt"),
|
||||
action: (
|
||||
<ToastAction
|
||||
altText={t("buy.go")}
|
||||
onClick={() => (location.href = "https://deeptrain.net/home/wallet")}
|
||||
>
|
||||
{t("buy.go")}
|
||||
</ToastAction>
|
||||
),
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.open("https://deeptrain.net/home/wallet");
|
||||
|
@ -1,10 +1,9 @@
|
||||
import * as React from "react";
|
||||
import {Input, InputProps} from "@/components/ui/input.tsx";
|
||||
import {getNumber} from "@/utils/base.ts";
|
||||
import {useEffect, useState} from "react";
|
||||
import { Input, InputProps } from "@/components/ui/input.tsx";
|
||||
import { getNumber } from "@/utils/base.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export interface NumberInputProps
|
||||
extends InputProps {
|
||||
export interface NumberInputProps extends InputProps {
|
||||
value: number;
|
||||
max?: number;
|
||||
min?: number;
|
||||
@ -31,7 +30,8 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
||||
let value = parseFloat(raw);
|
||||
if (isNaN(value) && !props.acceptNaN) value = 0;
|
||||
if (props.max !== undefined && value > props.max) value = props.max;
|
||||
else if (props.min !== undefined && value < props.min) value = props.min;
|
||||
else if (props.min !== undefined && value < props.min)
|
||||
value = props.min;
|
||||
props.onValueChange(value);
|
||||
}}
|
||||
/>
|
||||
|
@ -242,7 +242,13 @@ function QuotaDialog() {
|
||||
amount,
|
||||
}),
|
||||
action: (
|
||||
<ToastAction altText={t("buy.go")}>
|
||||
<ToastAction
|
||||
altText={t("buy.go")}
|
||||
onClick={() =>
|
||||
(location.href =
|
||||
"https://deeptrain.net/home/wallet")
|
||||
}
|
||||
>
|
||||
{t("buy.go")}
|
||||
</ToastAction>
|
||||
),
|
||||
|
@ -6,9 +6,13 @@ import {
|
||||
contextSelector,
|
||||
dialogSelector,
|
||||
historySelector,
|
||||
senderSelector,
|
||||
sendKeys,
|
||||
setAlign,
|
||||
setContext,
|
||||
setDialog, setHistory,
|
||||
setDialog,
|
||||
setHistory,
|
||||
setSender,
|
||||
} from "@/store/settings.ts";
|
||||
import {
|
||||
Dialog,
|
||||
@ -18,13 +22,18 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog.tsx";
|
||||
import { Checkbox } from "@/components/ui/checkbox.tsx";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getMemoryPerformance } from "@/utils/app.ts";
|
||||
import { version } from "@/conf.ts";
|
||||
import {NumberInput} from "@/components/ui/number-input.tsx";
|
||||
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select.tsx";
|
||||
import {SelectItemProps} from "@/components/SelectGroup.tsx";
|
||||
import {langs, setLanguage} from "@/i18n.ts";
|
||||
import { NumberInput } from "@/components/ui/number-input.tsx";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select.tsx";
|
||||
import { langs, setLanguage } from "@/i18n.ts";
|
||||
|
||||
function SettingsDialog() {
|
||||
const { t, i18n } = useTranslation();
|
||||
@ -33,6 +42,7 @@ function SettingsDialog() {
|
||||
|
||||
const align = useSelector(alignSelector);
|
||||
const context = useSelector(contextSelector);
|
||||
const sender = useSelector(senderSelector);
|
||||
const history = useSelector(historySelector);
|
||||
const [memorySize, setMemorySize] = useState(getMemoryPerformance());
|
||||
|
||||
@ -56,9 +66,7 @@ function SettingsDialog() {
|
||||
<div className={`item`}>
|
||||
<div className={`name`}>{t("settings.version")}</div>
|
||||
<div className={`grow`} />
|
||||
<div className={`value`}>
|
||||
v{version}
|
||||
</div>
|
||||
<div className={`value`}>v{version}</div>
|
||||
</div>
|
||||
<div className={`item`}>
|
||||
<div className={`name`}>{t("settings.language")}</div>
|
||||
@ -66,7 +74,9 @@ function SettingsDialog() {
|
||||
<div className={`value`}>
|
||||
<Select
|
||||
value={i18n.language}
|
||||
onValueChange={(value: string) => setLanguage(i18n, value)}
|
||||
onValueChange={(value: string) =>
|
||||
setLanguage(i18n, value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className={`select`}>
|
||||
<SelectValue placeholder={langs[i18n.language]} />
|
||||
@ -83,6 +93,26 @@ function SettingsDialog() {
|
||||
</div>
|
||||
</div>
|
||||
<div className={`settings-segment`}>
|
||||
<div className={`item`}>
|
||||
<div className={`name`}>{t("settings.sender")}</div>
|
||||
<div className={`grow`} />
|
||||
<div className={`value`}>
|
||||
<Select
|
||||
value={sender ? "true" : "false"}
|
||||
onValueChange={(value: string) =>
|
||||
dispatch(setSender(value === "true"))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className={`select`}>
|
||||
<SelectValue placeholder={sendKeys[sender ? 1 : 0]} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={"false"}>{sendKeys[0]}</SelectItem>
|
||||
<SelectItem value={"true"}>{sendKeys[1]}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`item`}>
|
||||
<div className={`name`}>{t("settings.align")}</div>
|
||||
<div className={`grow`} />
|
||||
@ -105,24 +135,22 @@ function SettingsDialog() {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
context && (
|
||||
<div className={`item`}>
|
||||
<div className={`name`}>{t("settings.history")}</div>
|
||||
<div className={`grow`} />
|
||||
<NumberInput
|
||||
className={`value`}
|
||||
value={history}
|
||||
acceptNaN={false}
|
||||
min={0}
|
||||
max={100}
|
||||
onValueChange={(value: number) => {
|
||||
dispatch(setHistory(value));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{context && (
|
||||
<div className={`item`}>
|
||||
<div className={`name`}>{t("settings.history")}</div>
|
||||
<div className={`grow`} />
|
||||
<NumberInput
|
||||
className={`value`}
|
||||
value={history}
|
||||
acceptNaN={false}
|
||||
min={0}
|
||||
max={999}
|
||||
onValueChange={(value: number) => {
|
||||
dispatch(setHistory(value));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`grow`} />
|
||||
|
@ -118,7 +118,7 @@ const resources = {
|
||||
"success-prompt": "您已成功购买 {{amount}} 点数。",
|
||||
failed: "购买失败",
|
||||
"failed-prompt":
|
||||
"购买点数失败。请确保您有足够的余额,您即将跳转到 deeptrain 钱包支付余额。",
|
||||
"购买点数失败。请确保您有足够的余额,您即将跳转到钱包支付余额。",
|
||||
"gpt4-tip": "提示:web 联网版功能可能会带来更多的输入点数消耗",
|
||||
go: "前往",
|
||||
},
|
||||
@ -194,7 +194,7 @@ const resources = {
|
||||
"migrate-success-prompt": "您已成功变更订阅计划。",
|
||||
failed: "订阅失败",
|
||||
"failed-prompt":
|
||||
"订阅失败,请确保您有足够的余额,您即将跳转到 deeptrain 钱包支付余额。",
|
||||
"订阅失败,请确保您有足够的余额,您即将跳转到钱包支付余额。",
|
||||
"migrate-failed": "变更失败",
|
||||
"migrate-failed-prompt": "您的订阅变更失败。",
|
||||
},
|
||||
@ -284,6 +284,7 @@ const resources = {
|
||||
description: "偏好设置",
|
||||
version: "当前版本",
|
||||
language: "显示语言",
|
||||
sender: "发送键",
|
||||
context: "保留上下文",
|
||||
history: "最大历史会话数",
|
||||
align: "聊天框居中",
|
||||
@ -480,7 +481,7 @@ const resources = {
|
||||
"success-prompt": "You have successfully purchased {{amount}} points.",
|
||||
failed: "Purchase failed",
|
||||
"failed-prompt":
|
||||
"Failed to purchase points. Please make sure you have enough balance, you will soon jump to deeptrain wallet to pay balance.",
|
||||
"Failed to purchase points. Please make sure you have enough balance, you will soon jump to wallet to pay balance.",
|
||||
"gpt4-tip": "Tip: web searching feature may consume more input points",
|
||||
go: "Go",
|
||||
},
|
||||
@ -559,7 +560,7 @@ const resources = {
|
||||
"You have successfully migrated subscription.",
|
||||
failed: "Subscribe failed",
|
||||
"failed-prompt":
|
||||
"Failed to subscribe, please make sure you have enough balance, you will soon jump to deeptrain wallet to pay balance.",
|
||||
"Failed to subscribe, please make sure you have enough balance, you will soon jump to wallet to pay balance.",
|
||||
"migrate-failed": "Migrate failed",
|
||||
"migrate-failed-prompt": "Your subscription migration failed.",
|
||||
},
|
||||
@ -654,6 +655,7 @@ const resources = {
|
||||
description: "Preference Settings",
|
||||
version: "Current Version",
|
||||
language: "Display Language",
|
||||
sender: "Send Key",
|
||||
context: "Keep Context",
|
||||
history: "Max History Conversations",
|
||||
align: "Chatbox Centered",
|
||||
@ -855,7 +857,7 @@ const resources = {
|
||||
"success-prompt": "Вы успешно приобрели {{amount}} очков.",
|
||||
failed: "Покупка не удалась",
|
||||
"failed-prompt":
|
||||
"Не удалось приобрести очки. Пожалуйста, убедитесь, что у вас достаточно баланса, вы скоро перейдете в кошелек deeptrain для оплаты баланса.",
|
||||
"Не удалось приобрести очки. Пожалуйста, убедитесь, что у вас достаточно баланса, вы скоро перейдете в кошелек для оплаты баланса.",
|
||||
"gpt4-tip":
|
||||
"Совет: функция веб-поиска может потреблять больше входных очков",
|
||||
go: "Перейти к",
|
||||
@ -933,7 +935,7 @@ const resources = {
|
||||
"migrate-success-prompt": "Вы успешно перенесли подписку.",
|
||||
failed: "Подписка не удалась",
|
||||
"failed-prompt":
|
||||
"Не удалось подписаться, пожалуйста, убедитесь, что у вас достаточно баланса, вы скоро перейдете в кошелек deeptrain для оплаты баланса.",
|
||||
"Не удалось подписаться, пожалуйста, убедитесь, что у вас достаточно баланса, вы скоро перейдете в кошелек для оплаты баланса.",
|
||||
"migrate-failed": "Перенос подписки не удался",
|
||||
"migrate-failed-prompt": "Ваша подписка не удалась.",
|
||||
},
|
||||
@ -1028,6 +1030,7 @@ const resources = {
|
||||
description: "Настройки предпочтений",
|
||||
version: "Текущая версия",
|
||||
language: "Язык отображения",
|
||||
sender: "Отправить ключ",
|
||||
context: "Сохранить контекст",
|
||||
history: "Максимальное количество исторических разговоров",
|
||||
align: "Выравнивание чата по центру",
|
||||
|
@ -4,7 +4,7 @@ import { Message } from "@/api/types.ts";
|
||||
import { insertStart } from "@/utils/base.ts";
|
||||
import { RootState } from "./index.ts";
|
||||
import { defaultModels, planModels, supportModels } from "@/conf.ts";
|
||||
import {getBooleanMemory, getMemory, setMemory} from "@/utils/memory.ts";
|
||||
import { getBooleanMemory, getMemory, setMemory } from "@/utils/memory.ts";
|
||||
|
||||
type initialStateType = {
|
||||
history: ConversationInstance[];
|
||||
|
@ -1,14 +1,21 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import {getBooleanMemory, getNumberMemory, setBooleanMemory, setNumberMemory} from "@/utils/memory.ts";
|
||||
import {
|
||||
getBooleanMemory,
|
||||
getNumberMemory,
|
||||
setBooleanMemory,
|
||||
setNumberMemory,
|
||||
} from "@/utils/memory.ts";
|
||||
import { RootState } from "@/store/index.ts";
|
||||
|
||||
export const sendKeys = ["Ctrl + Enter", "Enter"];
|
||||
export const settingsSlice = createSlice({
|
||||
name: "settings",
|
||||
initialState: {
|
||||
dialog: false,
|
||||
context: getBooleanMemory("context", true),
|
||||
align: getBooleanMemory("align", false),
|
||||
history: getNumberMemory("history", 8),
|
||||
history: getNumberMemory("history_context", 8),
|
||||
sender: getBooleanMemory("sender", false),
|
||||
},
|
||||
reducers: {
|
||||
toggleDialog: (state) => {
|
||||
@ -33,8 +40,12 @@ export const settingsSlice = createSlice({
|
||||
},
|
||||
setHistory: (state, action) => {
|
||||
state.history = action.payload as number;
|
||||
setNumberMemory("history", action.payload);
|
||||
}
|
||||
setNumberMemory("history_context", action.payload);
|
||||
},
|
||||
setSender: (state, action) => {
|
||||
state.sender = action.payload as boolean;
|
||||
setBooleanMemory("sender", action.payload);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -46,6 +57,7 @@ export const {
|
||||
setContext,
|
||||
setAlign,
|
||||
setHistory,
|
||||
setSender,
|
||||
} = settingsSlice.actions;
|
||||
export default settingsSlice.reducer;
|
||||
|
||||
@ -57,3 +69,5 @@ export const alignSelector = (state: RootState): boolean =>
|
||||
state.settings.align;
|
||||
export const historySelector = (state: RootState): number =>
|
||||
state.settings.history;
|
||||
export const senderSelector = (state: RootState): boolean =>
|
||||
state.settings.sender;
|
||||
|
Loading…
Reference in New Issue
Block a user