mirror of
https://github.com/coaidev/coai.git
synced 2025-05-30 18:30:32 +09:00
update input action and subscription page
This commit is contained in:
parent
12a150662d
commit
2939a53058
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -35,5 +35,13 @@
|
||||
user-select: none;
|
||||
margin-top: 0.5rem;
|
||||
transform: translateY(1rem);
|
||||
|
||||
& > * {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
@ -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>
|
||||
|
@ -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) => {
|
||||
|
68
app/src/components/home/assemblies/ChatAction.tsx
Normal file
68
app/src/components/home/assemblies/ChatAction.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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;
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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: "Использование памяти",
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user