update input action and subscription page

This commit is contained in:
Zhang Minghan 2023-11-16 22:40:27 +08:00
parent 12a150662d
commit 2939a53058
14 changed files with 190 additions and 84 deletions

View File

@ -62,7 +62,7 @@
scrollbar-width: thin;
height: max-content;
transition: .1s;
min-height: 20vh !important;
min-height: 50vh !important;
color: hsl(var(--text));
font-size: 16px !important;
padding: 14px 12px !important;
@ -80,7 +80,7 @@
border: 1px solid hsl(var(--border));
scrollbar-width: thin;
transition: 0.1s;
min-height: 20vh !important;
min-height: 50vh !important;
font-size: 16px;
}

View File

@ -591,6 +591,68 @@
overflow: hidden;
padding: 6px 24px;
.input-action {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-bottom: 6px;
.action {
display: inline-flex;
flex-direction: row;
align-items: center;
overflow: hidden;
margin-right: 4px;
width: 36px;
height: 24px;
border-radius: 12px;
padding: 4px 10px;
transition: 0.5s;
cursor: pointer;
border: 1px solid hsl(var(--border));
color: hsl(var(--text-secondary));
user-select: none;
transition-delay: 0.3s;
svg {
flex-shrink: 0;
width: 16px;
height: 16px;
padding: 1px;
}
.text {
font-size: 12px;
white-space: nowrap;
transform: translateY(-1px);
opacity: 0;
transition: 0.3s;
transition-delay: 0.3s;
margin-left: 4px;
transition-property: opacity;
}
&:hover {
border-color: hsl(var(--border-hover));
width: calc(42px + var(--width));
.text {
opacity: 1;
}
}
&.active {
border-color: hsl(var(--border-active));
color: hsl(var(--text));
}
&:last-child {
margin-right: 0;
}
}
}
.input-wrapper {
position: relative;
display: flex;
@ -610,7 +672,7 @@
width: 100%;
color: hsl(var(--text));
white-space: pre-wrap;
padding: 0 2.5rem;
padding-right: 0.25rem;
&.align {
text-align: center;
@ -718,6 +780,7 @@
}
p {
font: var(--font-family-normal);
transform: translateY(-1px);
}
}

View File

@ -35,5 +35,13 @@
user-select: none;
margin-top: 0.5rem;
transform: translateY(1rem);
& > * {
margin-bottom: 0.5rem;
&:last-child {
margin-bottom: 0;
}
}
}
}

View File

@ -15,6 +15,7 @@ import { useEffect, useRef, useState } from "react";
import { Toggle } from "./ui/toggle.tsx";
import { mobile } from "@/utils/device.ts";
import { Button } from "./ui/button.tsx";
import {ChatAction} from "@/components/home/assemblies/ChatAction.tsx";
type RichEditorProps = {
value: string;
@ -134,9 +135,9 @@ function EditorProvider(props: RichEditorProps) {
<>
<Dialog>
<DialogTrigger asChild>
<div className={`editor-action active editor`}>
<Maximize className={`h-3.5 w-3.5`} />
</div>
<ChatAction text={t("editor")}>
<Maximize className={`h-4 w-4`} />
</ChatAction>
</DialogTrigger>
<DialogContent className={`editor-dialog flex-dialog`}>
<DialogHeader>

View File

@ -25,7 +25,8 @@ import { FileObject, FileArray, blobParser } from "@/conversation/file.ts";
import { Button } from "@/components/ui/button.tsx";
import { useSelector } from "react-redux";
import { largeContextModels } from "@/conf.ts";
import { selectModel } from "@/store/chat.ts";
import {selectModel} from "@/store/chat.ts";
import {ChatAction} from "@/components/home/assemblies/ChatAction.tsx";
const MaxFileSize = 1024 * 1024 * 25; // 25MB File Size Limit
const MaxPromptSize = 5000; // 5000 Prompt Size Limit (to avoid token overflow)
@ -65,9 +66,9 @@ function FileProvider({ value, onChange }: FileProviderProps) {
<>
<Dialog>
<DialogTrigger asChild>
<div className={`file-action`}>
<Plus className={`h-3.5 w-3.5`} />
</div>
<ChatAction text={t("file.upload")}>
<Plus className={`h-4 w-4`} />
</ChatAction>
</DialogTrigger>
<DialogContent className={`file-dialog flex-dialog`}>
<DialogHeader>

View File

@ -1,20 +0,0 @@
import { openDialog } from "@/store/settings.ts";
import { version } from "@/conf.ts";
import { useDispatch } from "react-redux";
import {Settings} from "lucide-react";
function ChatFooter() {
const dispatch = useDispatch();
return (
<div className={`version`}>
<Settings
className={`app w-4 h-4`}
onClick={() => dispatch(openDialog())}
/>
chatnio v{version}
</div>
);
}
export default ChatFooter;

View File

@ -1,13 +1,13 @@
import { useTranslation } from "react-i18next";
import { useEffect, useRef, useState } from "react";
import FileProvider from "@/components/FileProvider.tsx";
import FileAction from "@/components/FileProvider.tsx";
import { useDispatch, useSelector } from "react-redux";
import { selectAuthenticated, selectInit } from "@/store/auth.ts";
import {selectCurrent, selectMessages, selectModel, selectWeb} from "@/store/chat.ts";
import { manager } from "@/conversation/manager.ts";
import { formatMessage } from "@/utils/processor.ts";
import ChatInterface from "@/components/home/ChatInterface.tsx";
import EditorProvider from "@/components/EditorProvider.tsx";
import EditorAction from "@/components/EditorProvider.tsx";
import ModelFinder from "./ModelFinder.tsx";
import { clearHistoryState, getQueryParam } from "@/utils/path.ts";
import { forgetMemory, popMemory } from "@/utils/memory.ts";
@ -15,9 +15,8 @@ import { useToast } from "@/components/ui/use-toast.ts";
import { ToastAction } from "@/components/ui/toast.tsx";
import { alignSelector, contextSelector } from "@/store/settings.ts";
import { FileArray } from "@/conversation/file.ts";
import WebToggle from "@/components/home/assemblies/WebToggle.tsx";
import {MarketAction, SettingsAction, WebAction} from "@/components/home/assemblies/ChatAction.tsx";
import ChatSpace from "@/components/home/ChatSpace.tsx";
import ChatFooter from "@/components/home/ChatFooter.tsx";
import ActionButton from "@/components/home/assemblies/ActionButton.tsx";
import ChatInput from "@/components/home/assemblies/ChatInput.tsx";
import ScrollAction from "@/components/home/assemblies/ScrollAction.tsx";
@ -141,10 +140,15 @@ function ChatWrapper() {
<Interface setTarget={setInstance} setWorking={setWorking} />
<ScrollAction target={instance} />
<div className={`chat-input`}>
<div className={`input-action`}>
<WebAction />
<FileAction value={files} onChange={setFiles} />
<EditorAction value={input} onChange={setInput} />
<SettingsAction />
<MarketAction />
</div>
<div className={`input-wrapper`}>
<WebToggle />
<div className={`chat-box`}>
<FileProvider value={files} onChange={setFiles} />
<ChatInput
className={align ? "align" : ""}
target={target}
@ -152,7 +156,6 @@ function ChatWrapper() {
onValueChange={setInput}
onEnterPressed={async () => await handleSend(auth, model, web)}
/>
<EditorProvider value={input} onChange={setInput} />
</div>
<ActionButton working={working} onClick={() => (
working ? handleCancel() : handleSend(auth, model, web)
@ -161,7 +164,6 @@ function ChatWrapper() {
<div className={`input-options`}>
<ModelFinder side={`bottom`} />
</div>
<ChatFooter />
</div>
</div>
</div>

View File

@ -98,7 +98,7 @@ function ModelFinder(props: ModelSelectorProps) {
<SelectGroup
current={models.find((item) => item.name === model) as SelectItemProps}
list={models}
maxElements={5}
maxElements={3}
side={props.side}
classNameMobile={`model-select-group`}
onChange={(value: string) => {

View File

@ -0,0 +1,68 @@
import {openMarket, selectWeb, toggleWeb} from "@/store/chat.ts";
import {Blocks, Globe, Settings} from "lucide-react";
import { useDispatch, useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import React, {useEffect, useRef} from "react";
import {openDialog} from "@/store/settings.ts";
type ChatActionProps = {
className?: string;
text?: string;
children?: React.ReactNode;
onClick?: () => void;
}
export const ChatAction = React.forwardRef<HTMLDivElement, ChatActionProps>((props, ref) => {
const { className, text, children, onClick } = props;
const label = useRef(null);
const [labelWidth, setLabelWidth] = React.useState(0);
useEffect(() => {
if (!label.current) return;
const target = label.current as HTMLDivElement;
const width = target.clientWidth || target.offsetWidth;
setLabelWidth(width);
}, [text, label]);
return ( // @ts-ignore
<div className={`action ${className}`} onClick={onClick} ref={ref} style={{ "--width": `${labelWidth}px` }}>
{children}
<div className="text" ref={label}>{text}</div>
</div>
);
});
export function WebAction() {
const { t } = useTranslation();
const dispatch = useDispatch();
const web = useSelector(selectWeb);
return (
<ChatAction className={web ? 'active': ''} text={t("chat.web")} onClick={() => dispatch(toggleWeb())}>
<Globe className={`h-4 w-4 web ${web ? "enable" : ""}`} />
</ChatAction>
);
}
export function SettingsAction() {
const { t } = useTranslation();
const dispatch = useDispatch();
return (
<ChatAction text={t("settings.description")} onClick={() => dispatch(openDialog())}>
<Settings className={`h-4 w-4`} />
</ChatAction>
)
}
export function MarketAction() {
const { t } = useTranslation();
const dispatch = useDispatch();
return (
<ChatAction text={t("market.title")} onClick={() => dispatch(openMarket())}>
<Blocks className={`h-4 w-4`} />
</ChatAction>
)
}

View File

@ -1,41 +0,0 @@
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip.tsx";
import { Toggle } from "@/components/ui/toggle.tsx";
import { selectWeb, setWeb } from "@/store/chat.ts";
import { Globe } from "lucide-react";
import { useDispatch, useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
function WebToggle() {
const { t } = useTranslation();
const dispatch = useDispatch();
const web = useSelector(selectWeb);
return (
<>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Toggle
aria-label={t("chat.web-aria")}
defaultPressed={false}
onPressedChange={(state: boolean) => dispatch(setWeb(state))}
variant={`outline`}
>
<Globe className={`h-4 w-4 web ${web ? "enable" : ""}`} />
</Toggle>
</TooltipTrigger>
<TooltipContent>
<p className={`tooltip`}>{t("chat.web")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</>
);
}
export default WebToggle;

View File

@ -8,7 +8,7 @@ import {
} from "@/utils/env.ts";
import { getMemory } from "@/utils/memory.ts";
export const version = "3.6.25";
export const version = "3.6.26";
export const dev: boolean = getDev();
export const deploy: boolean = true;
export let rest_api: string = getRestApi(deploy);

View File

@ -19,6 +19,7 @@ import {
import { Checkbox } from "@/components/ui/checkbox.tsx";
import {useEffect, useState} from "react";
import {getMemoryPerformance} from "@/utils/app.ts";
import {version} from "@/conf.ts";
function Settings() {
const { t } = useTranslation();
@ -77,6 +78,9 @@ function Settings() {
: t('unknown')
}
</p>
<p>
chatnio v{version}
</p>
</div>
</div>
</div>

View File

@ -27,6 +27,7 @@ const resources = {
"request-failed": "请求失败,请检查您的网络并重试。",
close: "关闭",
edit: "编辑",
editor: "文本编辑",
pricing: "更多计费详情参见模型定价表",
true: "是",
false: "否",
@ -46,6 +47,7 @@ const resources = {
"english-model": "英文模型",
},
market: {
title: "模型市场",
model: "探索更多模型",
explore: "探索",
search: "搜索模型名称或者简介",
@ -69,7 +71,7 @@ const resources = {
"delete-failed-prompt": "删除对话失败,请检查您的网络并重试。",
},
chat: {
web: "联网搜索功能",
web: "联网搜索",
"web-aria": "切换网络搜索功能",
placeholder: "写点什么...",
recall: "历史复原",
@ -250,6 +252,7 @@ const resources = {
},
settings: {
title: "设置",
description: "偏好设置",
context: "保留上下文",
align: "聊天框居中",
memory: "内存占用",
@ -340,6 +343,7 @@ const resources = {
"Request failed. Please check your network and try again.",
close: "Close",
edit: "Edit",
editor: "Text Editor",
pricing: "See model pricing for more details",
true: "Yes",
false: "No",
@ -359,6 +363,7 @@ const resources = {
"english-model": "English Model",
},
market: {
title: "Model Market",
model: "Explore more models",
explore: "Explore",
search: "Search model name or description",
@ -385,7 +390,7 @@ const resources = {
"Failed to delete conversation. Please check your network and try again.",
},
chat: {
web: "web searching feature",
web: "Web Searching",
"web-aria": "Toggle web searching feature",
placeholder: "Write something...",
recall: "History Recall",
@ -574,6 +579,7 @@ const resources = {
},
settings: {
title: "Settings",
description: "Preference Settings",
context: "Keep Context",
align: "Chatbox Centered",
memory: "Memory Usage",
@ -668,6 +674,7 @@ const resources = {
"Ошибка запроса. Пожалуйста, проверьте свою сеть и попробуйте еще раз.",
close: "Закрыть",
edit: "Редактировать",
editor: "Текстовый редактор",
pricing:
"См. ценообразование моделей для получения дополнительной информации",
true: "Да",
@ -687,6 +694,12 @@ const resources = {
fast: "Быстрый",
"english-model": "Английская модель",
},
market: {
title: "Рынок моделей",
model: "Исследуйте больше моделей",
explore: "Исследовать",
search: "Поиск по имени модели или описанию",
},
conversation: {
title: "Разговор",
empty: "Пусто",
@ -709,7 +722,7 @@ const resources = {
"Не удалось удалить разговор. Пожалуйста, проверьте свою сеть и попробуйте еще раз.",
},
chat: {
web: "веб-поиск",
web: "Веб-поиск",
"web-aria": "Переключить веб-поиск",
placeholder: "Напишите что-нибудь...",
recall: "История",
@ -898,6 +911,7 @@ const resources = {
},
settings: {
title: "Настройки",
description: "Настройки предпочтений",
context: "Сохранить контекст",
align: "Выравнивание чата по центру",
memory: "Использование памяти",

View File

@ -74,6 +74,11 @@ const chatSlice = createSlice({
setMemory("web", action.payload ? "true" : "false");
state.web = action.payload as boolean;
},
toggleWeb: (state) => {
const web = !state.web;
setMemory("web", web ? "true" : "false");
state.web = web;
},
setCurrent: (state, action) => {
state.current = action.payload as number;
},
@ -121,6 +126,7 @@ export const {
setMessages,
setModel,
setWeb,
toggleWeb,
addMessage,
setMessage,
setModelList,