diff --git a/frontend/src/_locales/zh-hans/main.json b/frontend/src/_locales/zh-hans/main.json index 07462ae..edd905c 100644 --- a/frontend/src/_locales/zh-hans/main.json +++ b/frontend/src/_locales/zh-hans/main.json @@ -154,5 +154,8 @@ "Restart": "重启", "API Chat Model Name": "API聊天模型名", "API Completion Model Name": "API补全模型名", - "Localhost": "本地" + "Localhost": "本地", + "Retry": "重试", + "Delete": "删除", + "Edit": "编辑" } \ No newline at end of file diff --git a/frontend/src/components/CopyButton.tsx b/frontend/src/components/CopyButton.tsx index 71a2ea6..7dc3304 100644 --- a/frontend/src/components/CopyButton.tsx +++ b/frontend/src/components/CopyButton.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { ClipboardSetText } from '../../wailsjs/runtime'; import { ToolTipButton } from './ToolTipButton'; -export const CopyButton: FC<{ content: string }> = ({ content }) => { +export const CopyButton: FC<{ content: string, showDelay?: number, }> = ({ content, showDelay = 0 }) => { const { t } = useTranslation(); const [copied, setCopied] = useState(false); @@ -19,7 +19,8 @@ export const CopyButton: FC<{ content: string }> = ({ content }) => { }; return ( - : } + : } onClick={onClick} /> ); }; diff --git a/frontend/src/components/ReadButton.tsx b/frontend/src/components/ReadButton.tsx index fe95bf6..265b980 100644 --- a/frontend/src/components/ReadButton.tsx +++ b/frontend/src/components/ReadButton.tsx @@ -7,13 +7,28 @@ import { observer } from 'mobx-react-lite'; const synth = window.speechSynthesis; -export const ReadButton: FC<{ content: string }> = observer(({ content }) => { +export const ReadButton: FC<{ + content: string, + inSpeaking?: boolean, + showDelay?: number, + setSpeakingOuter?: (speaking: boolean) => void +}> = observer(({ + content, + inSpeaking = false, + showDelay = 0, + setSpeakingOuter +}) => { const { t } = useTranslation(); - const [speaking, setSpeaking] = useState(false); + const [speaking, setSpeaking] = useState(inSpeaking); let lang: string = commonStore.settings.language; if (lang === 'dev') lang = 'en'; + const setSpeakingInner = (speaking: boolean) => { + setSpeakingOuter?.(speaking); + setSpeaking(speaking); + }; + const startSpeak = () => { synth.cancel(); @@ -31,22 +46,22 @@ export const ReadButton: FC<{ content: string }> = observer(({ content }) => { Object.assign(utterance, { rate: 1, volume: 1, - onend: () => setSpeaking(false), - onerror: () => setSpeaking(false), + onend: () => setSpeakingInner(false), + onerror: () => setSpeakingInner(false), voice: voice }); synth.speak(utterance); - setSpeaking(true); + setSpeakingInner(true); }; const stopSpeak = () => { synth.cancel(); - setSpeaking(false); + setSpeakingInner(false); }; return ( - : } onClick={speaking ? stopSpeak : startSpeak} /> ); diff --git a/frontend/src/components/ToolTipButton.tsx b/frontend/src/components/ToolTipButton.tsx index 91022de..441a834 100644 --- a/frontend/src/components/ToolTipButton.tsx +++ b/frontend/src/components/ToolTipButton.tsx @@ -11,6 +11,7 @@ export const ToolTipButton: FC<{ appearance?: 'secondary' | 'primary' | 'outline' | 'subtle' | 'transparent'; disabled?: boolean, onClick?: MouseEventHandler + showDelay?: number, }> = ({ text, desc, @@ -20,10 +21,11 @@ export const ToolTipButton: FC<{ shape, appearance, disabled, - onClick + onClick, + showDelay = 0 }) => { return ( - + diff --git a/frontend/src/pages/Chat.tsx b/frontend/src/pages/Chat.tsx index b67ae97..c014b6c 100644 --- a/frontend/src/pages/Chat.tsx +++ b/frontend/src/pages/Chat.tsx @@ -1,12 +1,13 @@ -import React, { FC, useEffect, useRef } from 'react'; +import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Avatar, PresenceBadge, Textarea } from '@fluentui/react-components'; +import { Avatar, Button, Menu, MenuPopover, MenuTrigger, PresenceBadge, Textarea } from '@fluentui/react-components'; import commonStore, { ModelStatus } from '../stores/commonStore'; import { observer } from 'mobx-react-lite'; import { v4 as uuid } from 'uuid'; import classnames from 'classnames'; import { fetchEventSource } from '@microsoft/fetch-event-source'; -import { ConversationPair, getConversationPairs, Record } from '../utils/get-conversation-pairs'; +import { KebabHorizontalIcon, PencilIcon, SyncIcon, TrashIcon } from '@primer/octicons-react'; +import { ConversationPair } from '../utils/get-conversation-pairs'; import logo from '../assets/images/logo.jpg'; import MarkdownRender from '../components/MarkdownRender'; import { ToolTipButton } from '../components/ToolTipButton'; @@ -22,6 +23,8 @@ import { toastWithButton } from '../utils'; export const userName = 'M E'; export const botName = 'A I'; +export const welcomeUuid = 'welcome'; + export enum MessageType { Normal, Error @@ -48,6 +51,122 @@ export type Conversation = { let chatSseController: AbortController | null = null; +const MoreUtilsButton: FC<{ uuid: string, setEditing: (editing: boolean) => void }> = observer(({ + uuid, + setEditing +}) => { + const { t } = useTranslation(); + const [speaking, setSpeaking] = useState(false); + + const messageItem = commonStore.conversation[uuid]; + + return + + ; +}); + +const ChatMessageItem: FC<{ + uuid: string, onSubmit: (message: string | null, answerId: string | null, + startUuid: string | null, endUuid: string | null, includeEndUuid: boolean) => void +}> = observer(({ uuid, onSubmit }) => { + const { t } = useTranslation(); + const [editing, setEditing] = useState(false); + const textareaRef = useRef(null); + const messageItem = commonStore.conversation[uuid]; + + console.log(uuid); + + const setEditingInner = (editing: boolean) => { + setEditing(editing); + if (editing) { + setTimeout(() => { + const textarea = textareaRef.current; + if (textarea) { + textarea.focus(); + textarea.selectionStart = textarea.value.length; + textarea.selectionEnd = textarea.value.length; + textarea.style.height = textarea.scrollHeight + 'px'; + } + }); + } + }; + + return
{ + const utils = document.getElementById('utils-' + uuid); + if (utils) utils.classList.remove('invisible'); + }} + onMouseLeave={() => { + const utils = document.getElementById('utils-' + uuid); + if (utils) utils.classList.add('invisible'); + }} + > + +
+ {!editing ? + {messageItem.content} : +