mirror of
https://github.com/coaidev/coai.git
synced 2025-05-21 22:10:12 +09:00
update model selector
This commit is contained in:
parent
a5e18a1ee2
commit
e31d69ae18
@ -17,6 +17,9 @@
|
||||
}
|
||||
|
||||
.select-group-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.35rem 0.8rem;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 4px;
|
||||
@ -34,6 +37,27 @@
|
||||
background: hsl(var(--text));
|
||||
border-color: hsl(var(--border-hover));
|
||||
color: hsl(var(--background));
|
||||
|
||||
.badge {
|
||||
background: hsl(var(--background)) !important;
|
||||
color: hsl(var(--text));
|
||||
|
||||
&:hover {
|
||||
background: hsl(var(--background)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
user-select: none;
|
||||
transition: .2s;
|
||||
padding-left: 0.45rem;
|
||||
padding-right: 0.45rem;
|
||||
background: hsl(var(--primary)) !important;
|
||||
|
||||
&:hover {
|
||||
background: hsl(var(--primary)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import {
|
||||
Copy,
|
||||
File,
|
||||
Loader2,
|
||||
MousePointerSquare, Power, RotateCcw,
|
||||
MousePointerSquare,
|
||||
Power,
|
||||
RotateCcw,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
ContextMenu,
|
||||
@ -165,21 +167,21 @@ function MessageContent({ message, end, onEvent }: MessageProps) {
|
||||
<Loader2 className={`h-5 w-5 m-1 animate-spin`} />
|
||||
)}
|
||||
</div>
|
||||
{
|
||||
(message.role === "assistant" && end === true) && (
|
||||
<div className={`message-toolbar`}>
|
||||
{
|
||||
(message.end !== false) ?
|
||||
<RotateCcw className={`h-4 w-4 m-0.5`} onClick={() => (
|
||||
onEvent && onEvent("restart")
|
||||
)} /> :
|
||||
<Power className={`h-4 w-4 m-0.5`} onClick={() => (
|
||||
onEvent && onEvent("stop")
|
||||
)} />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{message.role === "assistant" && end === true && (
|
||||
<div className={`message-toolbar`}>
|
||||
{message.end !== false ? (
|
||||
<RotateCcw
|
||||
className={`h-4 w-4 m-0.5`}
|
||||
onClick={() => onEvent && onEvent("restart")}
|
||||
/>
|
||||
) : (
|
||||
<Power
|
||||
className={`h-4 w-4 m-0.5`}
|
||||
onClick={() => onEvent && onEvent("stop")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -7,13 +7,101 @@ import {
|
||||
} from "./ui/select";
|
||||
import { mobile } from "../utils.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Badge } from "./ui/badge.tsx";
|
||||
|
||||
export type SelectItemProps = {
|
||||
name: string;
|
||||
value: string;
|
||||
badge?: string;
|
||||
tag?: any;
|
||||
};
|
||||
|
||||
type SelectGroupProps = {
|
||||
current: string;
|
||||
list: string[];
|
||||
current: SelectItemProps;
|
||||
list: SelectItemProps[];
|
||||
onChange?: (select: string) => void;
|
||||
maxElements?: number;
|
||||
};
|
||||
|
||||
function GroupSelectItem(props: SelectItemProps) {
|
||||
return (
|
||||
<>
|
||||
{props.value}
|
||||
{props.badge && <Badge className="badge ml-1">{props.badge}</Badge>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectGroupDesktop(props: SelectGroupProps) {
|
||||
const max: number = props.maxElements || 5;
|
||||
const range = props.list.length > max ? max : props.list.length;
|
||||
const display = props.list.slice(0, range);
|
||||
const hidden = props.list.slice(range);
|
||||
|
||||
return (
|
||||
<div className={`select-group`}>
|
||||
{display.map((select: SelectItemProps, idx: number) => (
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => props.onChange?.(select.name)}
|
||||
className={`select-group-item ${
|
||||
select == props.current ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<GroupSelectItem {...select} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
{props.list.length > max && (
|
||||
<Select
|
||||
defaultValue={"..."}
|
||||
value={props.current?.name || "..."}
|
||||
onValueChange={(value: string) => props.onChange?.(value)}
|
||||
>
|
||||
<SelectTrigger
|
||||
className={`w-max gap-1 select-group-item ${
|
||||
hidden.includes(props.current) ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<SelectValue asChild>
|
||||
<span>
|
||||
{hidden.includes(props.current) ? props.current.value : "..."}
|
||||
</span>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{hidden.map((select: SelectItemProps, idx: number) => (
|
||||
<SelectItem key={idx} value={select.name}>
|
||||
<GroupSelectItem {...select} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectGroupMobile(props: SelectGroupProps) {
|
||||
return (
|
||||
<Select
|
||||
value={props.current.name}
|
||||
onValueChange={(value: string) => props.onChange?.(value)}
|
||||
>
|
||||
<SelectTrigger className="select-group mobile">
|
||||
<SelectValue placeholder={props.current.value} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{props.list.map((select: SelectItemProps, idx: number) => (
|
||||
<SelectItem key={idx} value={select.name}>
|
||||
<GroupSelectItem {...select} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectGroup(props: SelectGroupProps) {
|
||||
const [state, setState] = useState(mobile);
|
||||
useEffect(() => {
|
||||
@ -23,35 +111,9 @@ function SelectGroup(props: SelectGroupProps) {
|
||||
}, []);
|
||||
|
||||
return state ? (
|
||||
<Select
|
||||
value={props.current}
|
||||
onValueChange={(value: string) => props.onChange?.(value)}
|
||||
>
|
||||
<SelectTrigger className="select-group mobile">
|
||||
<SelectValue placeholder={props.current} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{props.list.map((select: string, idx: number) => (
|
||||
<SelectItem key={idx} value={select}>
|
||||
{select}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<SelectGroupMobile {...props} />
|
||||
) : (
|
||||
<div className={`select-group`}>
|
||||
{props.list.map((select: string, idx: number) => (
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => props.onChange?.(select)}
|
||||
className={`select-group-item ${
|
||||
select == props.current ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
{select}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<SelectGroupDesktop {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
|
56
app/src/components/home/ModelSelector.tsx
Normal file
56
app/src/components/home/ModelSelector.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import SelectGroup, { SelectItemProps } from "../SelectGroup.tsx";
|
||||
import { supportModels } from "../../conf.ts";
|
||||
import { selectModel, setModel } from "../../store/chat.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { selectAuthenticated } from "../../store/auth.ts";
|
||||
import { useToast } from "../ui/use-toast.ts";
|
||||
import { useEffect } from "react";
|
||||
import { Model } from "../../conversation/types.ts";
|
||||
|
||||
function GetModel(name: string): Model {
|
||||
return supportModels.find((model) => model.id === name) as Model;
|
||||
}
|
||||
|
||||
function ModelSelector() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const { toast } = useToast();
|
||||
|
||||
const model = useSelector(selectModel);
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
|
||||
useEffect(() => {
|
||||
if (auth && model === "GPT-3.5") dispatch(setModel("GPT-3.5-16k"));
|
||||
}, [auth]);
|
||||
|
||||
const list = supportModels.map(
|
||||
(model: Model): SelectItemProps => ({
|
||||
name: model.id,
|
||||
value: model.name,
|
||||
badge: model.free ? "free" : undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectGroup
|
||||
current={list.find((item) => item.name === model) as SelectItemProps}
|
||||
list={list}
|
||||
maxElements={6}
|
||||
onChange={(value: string) => {
|
||||
const model = GetModel(value);
|
||||
console.debug(`[model] select model: ${model.name} (id: ${model.id})`);
|
||||
|
||||
if (!auth && model.auth) {
|
||||
toast({
|
||||
title: t("login-require"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
dispatch(setModel(value));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelSelector;
|
@ -1,6 +1,7 @@
|
||||
import axios from "axios";
|
||||
import { Model } from "./conversation/types.ts";
|
||||
|
||||
export const version = "3.4.5";
|
||||
export const version = "3.4.6";
|
||||
export const deploy: boolean = true;
|
||||
export let rest_api: string = "http://localhost:8094";
|
||||
export let ws_api: string = "ws://localhost:8094";
|
||||
@ -11,19 +12,45 @@ if (deploy) {
|
||||
}
|
||||
|
||||
export const tokenField = deploy ? "token" : "token-dev";
|
||||
export const supportModels: string[] = [
|
||||
"GPT-3.5",
|
||||
"GPT-3.5-16k",
|
||||
"GPT-4",
|
||||
"GPT-4-32k",
|
||||
"Claude-2",
|
||||
"Claude-2-100k",
|
||||
"SparkDesk 讯飞星火",
|
||||
"Palm2",
|
||||
"New Bing",
|
||||
"智谱 ChatGLM Pro",
|
||||
"智谱 ChatGLM Std",
|
||||
"智谱 ChatGLM Lite",
|
||||
export const supportModels: Model[] = [
|
||||
// openai models
|
||||
{ id: "gpt-3.5-turbo", name: "GPT-3.5", free: true, auth: false },
|
||||
{ id: "gpt-3.5-turbo-16k", name: "GPT-3.5-16k", free: true, auth: true },
|
||||
{ id: "gpt-4", name: "GPT-4", free: false, auth: true },
|
||||
{ id: "gpt-4-32k", name: "GPT-4-32k", free: false, auth: true },
|
||||
|
||||
// anthropic models
|
||||
{ id: "claude-1", name: "Claude-2", free: true, auth: false },
|
||||
{ id: "claude-2", name: "Claude-2-100k", free: false, auth: true }, // not claude-2-100k
|
||||
|
||||
// spark desk
|
||||
{ id: "spark-desk", name: "SparkDesk 讯飞星火", free: false, auth: true },
|
||||
|
||||
// google palm2
|
||||
{ id: "chat-bison-001", name: "Palm2", free: true, auth: true },
|
||||
|
||||
// new bing
|
||||
{ id: "bing-creative", name: "New Bing", free: true, auth: true },
|
||||
|
||||
// zhipu models
|
||||
{
|
||||
id: "zhipu-chatglm-pro",
|
||||
name: "智谱 ChatGLM Pro",
|
||||
free: false,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
id: "zhipu-chatglm-std",
|
||||
name: "智谱 ChatGLM Std",
|
||||
free: false,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
id: "zhipu-chatglm-lite",
|
||||
name: "智谱 ChatGLM Lite",
|
||||
free: true,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const supportModelConvertor: Record<string, string> = {
|
||||
|
@ -71,10 +71,11 @@ export class Connection {
|
||||
}, 500);
|
||||
}
|
||||
} catch {
|
||||
if (t !== undefined) this.triggerCallback({
|
||||
message: t("request-failed"),
|
||||
end: true,
|
||||
});
|
||||
if (t !== undefined)
|
||||
this.triggerCallback({
|
||||
message: t("request-failed"),
|
||||
end: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ChatProps, Connection, StreamMessage } from "./connection.ts";
|
||||
import { Message } from "./types.ts";
|
||||
import { sharingEvent } from "../events/sharing.ts";
|
||||
import {connectionEvent} from "../events/connection.ts";
|
||||
import { connectionEvent } from "../events/connection.ts";
|
||||
|
||||
type ConversationCallback = (idx: number, message: Message[]) => void;
|
||||
|
||||
@ -34,7 +34,9 @@ export class Conversation {
|
||||
|
||||
connectionEvent.addEventListener((ev) => {
|
||||
if (ev.id === this.id) {
|
||||
console.debug(`[conversation] connection event (id: ${this.id}, event: ${ev.event})`);
|
||||
console.debug(
|
||||
`[conversation] connection event (id: ${this.id}, event: ${ev.event})`,
|
||||
);
|
||||
|
||||
switch (ev.event) {
|
||||
case "stop":
|
||||
@ -52,10 +54,12 @@ export class Conversation {
|
||||
break;
|
||||
|
||||
default:
|
||||
console.debug(`[conversation] unknown event: ${ev.event} (from: ${ev.id})`);
|
||||
console.debug(
|
||||
`[conversation] unknown event: ${ev.event} (from: ${ev.id})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
protected sendEvent(event: string, data?: string) {
|
||||
|
@ -8,6 +8,13 @@ export type Message = {
|
||||
end?: boolean;
|
||||
};
|
||||
|
||||
export type Model = {
|
||||
id: string;
|
||||
name: string;
|
||||
free: boolean;
|
||||
auth: boolean;
|
||||
};
|
||||
|
||||
export type Id = number;
|
||||
|
||||
export type ConversationInstance = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {EventCommitter} from "./struct.ts";
|
||||
import { EventCommitter } from "./struct.ts";
|
||||
|
||||
export type ConnectionEvent = {
|
||||
id: number;
|
||||
|
@ -1,18 +1,17 @@
|
||||
import "../assets/generation.less";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { selectAuthenticated } from "../store/auth.ts";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "../components/ui/button.tsx";
|
||||
import { ChevronLeft, Cloud, FileDown, Send } from "lucide-react";
|
||||
import { rest_api, supportModelConvertor, supportModels } from "../conf.ts";
|
||||
import { rest_api, supportModelConvertor } from "../conf.ts";
|
||||
import router from "../router.ts";
|
||||
import { Input } from "../components/ui/input.tsx";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import SelectGroup from "../components/SelectGroup.tsx";
|
||||
import { manager } from "../conversation/generation.ts";
|
||||
import { useToast } from "../components/ui/use-toast.ts";
|
||||
import { handleGenerationData } from "../utils.ts";
|
||||
import { selectModel, setModel } from "../store/chat.ts";
|
||||
import { selectModel } from "../store/chat.ts";
|
||||
import ModelSelector from "../components/home/ModelSelector.tsx";
|
||||
|
||||
type WrapperProps = {
|
||||
onSend?: (value: string, model: string) => boolean;
|
||||
@ -20,7 +19,6 @@ type WrapperProps = {
|
||||
|
||||
function Wrapper({ onSend }: WrapperProps) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const ref = useRef(null);
|
||||
const [stayed, setStayed] = useState<boolean>(false);
|
||||
const [hash, setHash] = useState<string>("");
|
||||
@ -28,14 +26,9 @@ function Wrapper({ onSend }: WrapperProps) {
|
||||
const [quota, setQuota] = useState<number>(0);
|
||||
const model = useSelector(selectModel);
|
||||
const modelRef = useRef(model);
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (auth && model === "GPT-3.5") dispatch(setModel("GPT-3.5-16k"));
|
||||
}, [auth]);
|
||||
|
||||
function clear() {
|
||||
setData("");
|
||||
setQuota(0);
|
||||
@ -154,19 +147,7 @@ function Wrapper({ onSend }: WrapperProps) {
|
||||
</Button>
|
||||
</div>
|
||||
<div className={`model-box`}>
|
||||
<SelectGroup
|
||||
current={model}
|
||||
list={supportModels}
|
||||
onChange={(value: string) => {
|
||||
if (!auth && value !== "GPT-3.5") {
|
||||
toast({
|
||||
title: t("login-require"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
dispatch(setModel(value));
|
||||
}}
|
||||
/>
|
||||
<ModelSelector />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import type { RootState } from "../store";
|
||||
import { selectAuthenticated, selectInit } from "../store/auth.ts";
|
||||
import { login, supportModels } from "../conf.ts";
|
||||
import { login } from "../conf.ts";
|
||||
import {
|
||||
deleteConversation,
|
||||
toggleConversation,
|
||||
@ -39,7 +39,7 @@ import {
|
||||
useEffectAsync,
|
||||
copyClipboard,
|
||||
} from "../utils.ts";
|
||||
import { toast, useToast } from "../components/ui/use-toast.ts";
|
||||
import { useToast } from "../components/ui/use-toast.ts";
|
||||
import { ConversationInstance, Message } from "../conversation/types.ts";
|
||||
import {
|
||||
selectCurrent,
|
||||
@ -47,7 +47,6 @@ import {
|
||||
selectHistory,
|
||||
selectMessages,
|
||||
selectWeb,
|
||||
setModel,
|
||||
setWeb,
|
||||
} from "../store/chat.ts";
|
||||
import {
|
||||
@ -66,10 +65,10 @@ import MessageSegment from "../components/Message.tsx";
|
||||
import { setMenu } from "../store/menu.ts";
|
||||
import FileProvider, { FileObject } from "../components/FileProvider.tsx";
|
||||
import router from "../router.ts";
|
||||
import SelectGroup from "../components/SelectGroup.tsx";
|
||||
import EditorProvider from "../components/EditorProvider.tsx";
|
||||
import ConversationSegment from "../components/home/ConversationSegment.tsx";
|
||||
import {connectionEvent} from "../events/connection.ts";
|
||||
import { connectionEvent } from "../events/connection.ts";
|
||||
import ModelSelector from "../components/home/ModelSelector.tsx";
|
||||
|
||||
function SideBar() {
|
||||
const { t } = useTranslation();
|
||||
@ -353,21 +352,19 @@ function ChatInterface() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{
|
||||
messages.map((message, i) =>
|
||||
<MessageSegment
|
||||
message={message}
|
||||
end={i === messages.length - 1}
|
||||
onEvent={(e: string) => {
|
||||
connectionEvent.emit({
|
||||
id: current,
|
||||
event: e,
|
||||
});
|
||||
}}
|
||||
key={i}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{messages.map((message, i) => (
|
||||
<MessageSegment
|
||||
message={message}
|
||||
end={i === messages.length - 1}
|
||||
onEvent={(e: string) => {
|
||||
connectionEvent.emit({
|
||||
id: current,
|
||||
event: e,
|
||||
});
|
||||
}}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -390,10 +387,6 @@ function ChatWrapper() {
|
||||
const target = useRef(null);
|
||||
manager.setDispatch(dispatch);
|
||||
|
||||
useEffect(() => {
|
||||
if (auth && model === "GPT-3.5") dispatch(setModel("GPT-3.5-16k"));
|
||||
}, [auth]);
|
||||
|
||||
function clearFile() {
|
||||
clearEvent?.();
|
||||
}
|
||||
@ -522,19 +515,7 @@ function ChatWrapper() {
|
||||
</Button>
|
||||
</div>
|
||||
<div className={`input-options`}>
|
||||
<SelectGroup
|
||||
current={model}
|
||||
list={supportModels}
|
||||
onChange={(model: string) => {
|
||||
if (!auth && model !== "GPT-3.5") {
|
||||
toast({
|
||||
title: t("login-require"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
dispatch(setModel(model));
|
||||
}}
|
||||
/>
|
||||
<ModelSelector />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { ConversationInstance } from "../conversation/types.ts";
|
||||
import {ConversationInstance, Model} from "../conversation/types.ts";
|
||||
import { Message } from "../conversation/types.ts";
|
||||
import { insertStart } from "../utils.ts";
|
||||
import { RootState } from "./index.ts";
|
||||
import { supportModels } from "../conf.ts";
|
||||
|
||||
type initialStateType = {
|
||||
history: ConversationInstance[];
|
||||
@ -12,12 +13,17 @@ type initialStateType = {
|
||||
current: number;
|
||||
};
|
||||
|
||||
function GetModel(model: string | undefined | null): string {
|
||||
return model && supportModels.filter((item: Model) => item.id === model).length
|
||||
? model : supportModels[0].id;
|
||||
}
|
||||
|
||||
const chatSlice = createSlice({
|
||||
name: "chat",
|
||||
initialState: {
|
||||
history: [],
|
||||
messages: [],
|
||||
model: "GPT-3.5",
|
||||
model: GetModel(localStorage.getItem("model")),
|
||||
web: false,
|
||||
current: -1,
|
||||
} as initialStateType,
|
||||
@ -44,6 +50,7 @@ const chatSlice = createSlice({
|
||||
state.messages = action.payload as Message[];
|
||||
},
|
||||
setModel: (state, action) => {
|
||||
localStorage.setItem("model", action.payload as string);
|
||||
state.model = action.payload as string;
|
||||
},
|
||||
setWeb: (state, action) => {
|
||||
|
Loading…
Reference in New Issue
Block a user