Skip to content
This repository has been archived by the owner on Sep 23, 2024. It is now read-only.

Commit

Permalink
Select model (#126)
Browse files Browse the repository at this point in the history
* feat: nextui

* feat: select model directly over textarea; feat: information icon; fix: context menu visibility
  • Loading branch information
clement2026 authored Apr 19, 2024
1 parent b754be0 commit 2b1b031
Show file tree
Hide file tree
Showing 16 changed files with 2,313 additions and 173 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "talk-vite",
"private": true,
"version": "2.1.3",
"version": "2.1.4",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -12,6 +12,7 @@
"dependencies": {
"@headlessui/react": "^1.7.17",
"@microsoft/fetch-event-source": "^2.0.1",
"@nextui-org/react": "^2.3.5",
"@tanstack/react-virtual": "^3.0.0-beta.60",
"@types/highlight.js": "^10.1.0",
"@types/markdown-it": "^13.0.2",
Expand All @@ -20,7 +21,7 @@
"crypto-js": "4.2.0",
"date-fns": "^2.30.0",
"downshift": "^8.1.0",
"framer-motion": "^10.16.1",
"framer-motion": "^11.1.5",
"granim": "^2.0.0",
"highlight.js": "^11.8.0",
"highlightjs-copy": "^1.0.5",
Expand Down
10 changes: 4 additions & 6 deletions src/api/sse/sse.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {useEffect} from 'react'
import {useSnapshot} from "valtio/react"
import {networkState} from "../../state/network-state.ts"
import {appState, findChatProxy, findMessage, findMessage2} from "../../state/app-state.ts"
import {appState, findChatProxy, findMessage, findMessage2, findUserMessageByTicketId} from "../../state/app-state.ts"
import {ServerAbility} from "./server-ability.ts"
import {newError, newThinking, onAudio, onEOF, onError, onThinking, onTyping} from "../../data-structure/message.tsx"
import {
Expand All @@ -26,9 +26,6 @@ import {createDemoChatIfNecessary} from "../../data/chat.ts";

export const SSE = () => {
const {passwordHash} = useSnapshot(appState.auth)
// console.info("SSE rendered", new Date().toLocaleString())


useEffect(() => {
const ep = SSEEndpoint()
const streamId = randomHash32Char()
Expand Down Expand Up @@ -70,7 +67,8 @@ export const SSE = () => {
if (msg) {
onThinking(msg)
} else {
const message = newThinking(meta.messageID, meta.ticketId, meta.role)
const found = findUserMessageByTicketId(chat, meta.ticketId);
const message = newThinking(meta.messageID, meta.ticketId, meta.role, found?.context)
chat.messages.push(message)
}
})
Expand Down Expand Up @@ -114,7 +112,7 @@ export const SSE = () => {
if (msg) {
onError(msg, error.errMsg)
} else {
const m = newError(error.messageID, error.ticketId, error.role, error.errMsg)
const m = newError(error.messageID, error.ticketId, error.role, error.errMsg, undefined)
chat.messages.push(m)
}
})
Expand Down
8 changes: 5 additions & 3 deletions src/data-structure/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,18 @@ export type Message = {
context?: Context
}

export const newThinking = (id: string, ticketId: string, role: Role): Message => ({
export const newThinking = (id: string, ticketId: string, role: Role, ctx?: Context): Message => ({
id: id,
ticketId: ticketId,
role: role,
status: "thinking",
text: "",
createdAt: Date.now(),
lastUpdatedAt: Date.now(),
context: ctx
})

export const newError = (id: string, ticketId: string, role: Role, errorMessage: string): Message => ({
export const newError = (id: string, ticketId: string, role: Role, errorMessage: string, ctx?: Context): Message => ({
id: id,
ticketId: ticketId,
role: role,
Expand All @@ -57,6 +58,7 @@ export const newError = (id: string, ticketId: string, role: Role, errorMessage:
errorMessage: errorMessage,
createdAt: Date.now(),
lastUpdatedAt: Date.now(),
context: ctx
})

export const newSending = (ctx: Context): Message => ({
Expand Down Expand Up @@ -243,7 +245,7 @@ export const isAttached = (m: Message): boolean => {
// Return message of history and new index
// If search can't continue, return -1 as new index
// Range of currentIndex: [-1, messages.length - 1]
export const searchLinuxTerminalHistoryPotision = (messages: Message[],
export const searchLinuxTerminalHistoryPosition = (messages: Message[],
currentIndex: number,
currentBuffer: string,
keyUpOrDown: 'up' | 'down'
Expand Down
4 changes: 2 additions & 2 deletions src/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default function Error() {
}}
/>
<CountDownButton
text={"Clear All Chats"}
text={"Only Clear All Chats"}
countDownMs={1000}
color="red"
icon={<BsTrash3 className="text-lg"/>}
Expand All @@ -67,7 +67,7 @@ export default function Error() {
}}
/>
<CountDownButton
text={"Clear Settings (Keep All Chats)"}
text={"Only Clear Settings"}
countDownMs={1000}
color="red"
icon={<BsTrash3 className="text-lg"/>}
Expand Down
101 changes: 98 additions & 3 deletions src/home/chat-window/compnent/dot.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import React, {useState} from "react";
/* eslint-disable valtio/state-snapshot-rule */
import React, {useCallback, useEffect, useState} from "react";
import {cx} from "../../../util/util.tsx";
import {capitalize} from "lodash";
import {Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@nextui-org/react";
import {Selection} from "@react-types/shared/src/selection";
import {useSnapshot} from "valtio/react";
import {appState, Chat} from "../../../state/app-state.ts";
import {Model} from "../../../api/sse/server-ability.ts";

interface PopOverDotProps {
text: string
Expand All @@ -26,12 +32,101 @@ export const PopOverDot: React.FC<PopOverDotProps> = ({text, popOverText, fixedR
)}>
{text}
</p>
<div className={cx("absolute bg-neutral-800/[0.8] rounded-full px-4 py-1 -top-8",
<div className={cx("absolute bg-neutral-800/[0.8] rounded-full px-4 py-1 -top-8",
"left-1/3 -translate-x-1/3 transform whitespace-nowrap",
"text-sm text-neutral-200 backdrop-blur-lg",
hovering ? "block" : "hidden"
)}>
{capitalize(popOverText)}
</div>
</div>
}
}

interface ModelSelectionProps {
chatProxy: Chat
}

export default function ModelSelection({chatProxy}: ModelSelectionProps) {
const chatSnap = useSnapshot(chatProxy);


const [models, setModels] = useState<Model[]>([]);
const [currentModel, setCurrentModel] = useState<string | undefined>(undefined);

useEffect(() => {
const models: Model[] = [];
if (chatSnap.option.llm.gemini.enabled && appState.ability.llm.gemini.models) {
appState.ability.llm.gemini.models?.forEach(it => models.push(it as Model))
setCurrentModel(chatSnap.option.llm.gemini.model)
} else if (chatSnap.option.llm.chatGPT.enabled && appState.ability.llm.chatGPT.models) {
appState.ability.llm.chatGPT.models?.forEach(it => models.push(it as Model))
setCurrentModel(chatSnap.option.llm.chatGPT.model)
}
setModels(models)
}, [chatSnap.option.llm.chatGPT.enabled, chatSnap.option.llm.chatGPT.model, chatSnap.option.llm.gemini.enabled, chatSnap.option.llm.gemini.model]);

const changeModel = useCallback((key: Selection) => {
if (chatSnap.option.llm.gemini.enabled) {
const found = appState.ability.llm.gemini.models?.find(it => key === 'all' || key.has(it.name))
if (found) {
chatProxy.option.llm.gemini.model = found.name
}
} else if (chatSnap.option.llm.chatGPT.enabled) {
const found = appState.ability.llm.chatGPT.models?.find(it => key === 'all' || key.has(it.name))
if (found) {
chatProxy.option.llm.chatGPT.model = found.name
}
}
if (key === 'all') {
setCurrentModel(undefined)
} else {
(key as Set<string>).forEach(it => setCurrentModel(it))
}
}, [chatProxy.option.llm.chatGPT, chatProxy.option.llm.gemini, chatSnap.option.llm.chatGPT.enabled, chatSnap.option.llm.gemini.enabled]);

const selectedValue = currentModel ?? ""

const selectedKeys = currentModel ? [currentModel] : []

if (models.length === 0 || !currentModel) return null

return (
<Dropdown className="bg-neutral-200">
<DropdownTrigger>
<Button
className={cx("select-none text-[13px] rounded-full h-5 text-violet-100 bg-black/[0.3]"
, "text-neutral-100 text-center leading-none px-2 pb-0.5")}
>
{selectedValue + " " + modelEmoji(selectedValue)}
</Button>
</DropdownTrigger>
<DropdownMenu
aria-label="Single selection example"
disallowEmptySelection
selectionMode="single"
selectedKeys={selectedKeys}
onSelectionChange={key => changeModel(key)}
>
{models.map((model) => (
<DropdownItem key={model.name}>
<div className="flex justify-between">
<div>{model.displayName}</div>
<div>{modelEmoji(model.name)}</div>
</div>
</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
);
}

const modelEmoji = (model: string) => {
switch (model) {
case "gpt-4":
return "🚀🚀🚀"
case "gpt-3.5-turbo":
return "👌"
default :
return "";
}
}
70 changes: 0 additions & 70 deletions src/home/chat-window/compnent/drop-down-menu.tsx

This file was deleted.

40 changes: 40 additions & 0 deletions src/home/chat-window/compnent/my-text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {useSnapshot} from "valtio/react";
import {appState} from "../../../state/app-state.ts";
import {controlState} from "../../../state/control-state.ts";
import {debounce, throttle} from "lodash";
import {Popover, PopoverContent, PopoverTrigger} from "@nextui-org/react";

hljs.configure({
ignoreUnescapedHTML: true,
Expand Down Expand Up @@ -144,10 +145,49 @@ export const MyText: React.FC<TextProps> = ({messageSnap, theme}) => {
<p className="text-xs inline">{messageSnap.errorMessage}</p>
</div>
}
{messageSnap.context &&
<Popover color={"foreground"} backdrop="blur">
<PopoverTrigger>
<span className="icon-[octicon--info-24] pointer-events-auto cursor-pointer"/>
</PopoverTrigger>
<PopoverContent>
<InfoTooltip messageSnap={messageSnap}/>
</PopoverContent>
</Popover>
}
</div>
</div>
}

interface InfoTooltipProps {
messageSnap: Message
}

const InfoTooltip: React.FC<InfoTooltipProps> = ({messageSnap}) => {
const gemini = messageSnap.context?.talkOption.llmOption?.gemini;
const gpt = messageSnap.context?.talkOption.llmOption?.chatGPT;
const context = messageSnap.context;
return <div className="flex flex-col gap-2 px-1 py-2">
{gemini &&
<InfoTooltipKV k="Gemini Model" value={gemini.model}/>
}
{gpt &&
<InfoTooltipKV k="ChatGPT Model" value={gpt.model}/>
}
{context && <InfoTooltipKV k="Attached Messages" value={context.attachedMessageCount}/>}
{context && <InfoTooltipKV k="Prompt Messages" value={context.promptCount}/>}
<InfoTooltipKV k="Words" value={messageSnap.text.split(" ").length}/>
<InfoTooltipKV k="Chars" value={messageSnap.text.length}/>
</div>
}

const InfoTooltipKV: React.FC<{ k: string, value: number | string }> = ({k, value}) => {
return <div className="flex gap-2 justify-between">
<div className="font-thin">{k}</div>
<div className="">{value}</div>
</div>
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const md = new MarkdownIt({
Expand Down
Loading

0 comments on commit 2b1b031

Please sign in to comment.