feat: customize send key

This commit is contained in:
Zhang Minghan 2023-11-28 21:57:09 +08:00
parent e9845f88c1
commit e79c29e24a
14 changed files with 137 additions and 77 deletions

View File

@ -91,6 +91,7 @@
## 📦 部署 | Deploy
**当前安装需要额外安装 Deeptrain 统一账户管理all in one功能正在开发中**
```shell
git clone https://github.com/Deeptrain-Community/chatnio.git
cd chatnio

View File

@ -16,6 +16,7 @@ export type ChatProps = {
message: string;
model: string;
web?: boolean;
context?: number;
ignore_context?: boolean;
};

View File

@ -75,7 +75,8 @@
padding: 0.25rem 0.75rem !important;
span {
margin-right: 0.5rem;
font-size: 0.8rem !important;
margin: 0 0.5rem;
}
}

View File

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

View File

@ -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 />
) : (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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`} />

View File

@ -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: "Выравнивание чата по центру",

View File

@ -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[];

View File

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