diff --git a/browser-extension/src/@types/backend-api.d.ts b/browser-extension/src/@types/backend-api.d.ts index 415c74cf..a637484c 100644 --- a/browser-extension/src/@types/backend-api.d.ts +++ b/browser-extension/src/@types/backend-api.d.ts @@ -10,3 +10,8 @@ export type CreateSystemContextRequest = { }; export type GetSystemContextsRequest = (PrimaryKey & SystemContext)[]; + +export type StreamingChunk = { + text: string; + stopReason?: string; +}; diff --git a/browser-extension/src/app/features/chat/ChatMessage.tsx b/browser-extension/src/app/features/chat/ChatMessage.tsx index 45401495..cd210ca0 100644 --- a/browser-extension/src/app/features/chat/ChatMessage.tsx +++ b/browser-extension/src/app/features/chat/ChatMessage.tsx @@ -19,7 +19,7 @@ const ChatMessage: React.FC = (props) => { className={twMerge( 'border-t last:border-b p-2', isUser ? 'bg-aws-squid-ink brightness-150' : '', - props.className, + props.className )} > {message.title && ( @@ -39,7 +39,7 @@ const ChatMessage: React.FC = (props) => { className={twMerge( 'transition-all ', message.role === 'system' && isOpen && 'max-h-[300px] overflow-y-auto', - message.role === 'system' && !isOpen && 'max-h-0 overflow-hidden', + message.role === 'system' && !isOpen && 'max-h-0 overflow-hidden' )} > {message.content.split('\n').map((c, idx) => ( diff --git a/browser-extension/src/app/features/chat/chatSlice.ts b/browser-extension/src/app/features/chat/chatSlice.ts index 2587174c..5ddadf9c 100644 --- a/browser-extension/src/app/features/chat/chatSlice.ts +++ b/browser-extension/src/app/features/chat/chatSlice.ts @@ -34,6 +34,13 @@ export const chatSlice = createSlice({ setInitialStateIfNeeded(state, action.payload.tabId); state[action.payload.tabId].messages = action.payload.messages; }, + updateMessageContent: ( + state, + action: PayloadAction + ) => { + setInitialStateIfNeeded(state, action.payload.tabId); + state[action.payload.tabId].messages[action.payload.index].content = action.payload.content; + }, clearMessages: (state, action: PayloadAction) => { setInitialStateIfNeeded(state, action.payload.tabId); state[action.payload.tabId].messages = []; @@ -41,7 +48,7 @@ export const chatSlice = createSlice({ }, }); -export const { setMessages, clearMessages } = chatSlice.actions; +export const { setMessages, updateMessageContent, clearMessages } = chatSlice.actions; export const chatMessages = (state: RootState, tabId: number) => { setInitialStateIfNeeded(state.chat, tabId); @@ -56,7 +63,7 @@ export const replaceMessages = setMessages({ tabId, messages, - }), + }) ); }; @@ -71,7 +78,7 @@ export const pushMessages = messages: produce(currentMessages, (draft) => { draft.push(...messages); }), - }), + }) ); }; @@ -84,12 +91,11 @@ export const overwriteLatestMessage = return; } dispatch( - setMessages({ + updateMessageContent({ tabId, - messages: produce(currentMessages, (draft) => { - draft[draft.length - 1].content = content.replace(/<([^>]+)>([\s\S]*?)<\/\1>/, '$2'); - }), - }), + index: currentMessages.length - 1, + content: content.replace(/<([^>]+)>([\s\S]*?)<\/\1>/, '$2'), + }) ); }; diff --git a/browser-extension/src/app/features/chat/useChat.ts b/browser-extension/src/app/features/chat/useChat.ts index 879ad9b1..52c1edb0 100644 --- a/browser-extension/src/app/features/chat/useChat.ts +++ b/browser-extension/src/app/features/chat/useChat.ts @@ -11,6 +11,7 @@ import usePredict from './usePredict'; import { Message } from '../../../@types/chat'; import Browser from 'webextension-polyfill'; import { PromptSetting } from '../../../@types/settings'; +import { StreamingChunk } from '../../../@types/backend-api'; const useChat = () => { // 複数のタブで起動する場合があるので、タブごとに状態を管理する @@ -83,14 +84,20 @@ const useChat = () => { role: 'assistant', content: '▍', }, - ]), + ]) ); // Assistant の発言を更新 let tmpChunk = ''; - for await (const chunk of stream) { - tmpChunk += chunk; + for await (const chunks of stream) { + // チャンクデータは改行コード区切りで送信されてくるので、分割して処理する + for (const chunk_ of chunks.split('\n')) { + if (chunk_) { + const chunk: StreamingChunk = JSON.parse(chunk_); + tmpChunk += chunk.text; + } + } // chunk は 10 文字以上でまとめて処理する // バッファリングしないと以下のエラーが出る diff --git a/browser-extension/src/app/features/highlight-menu/HighlightMenu.tsx b/browser-extension/src/app/features/highlight-menu/HighlightMenu.tsx index b466c401..bf593181 100644 --- a/browser-extension/src/app/features/highlight-menu/HighlightMenu.tsx +++ b/browser-extension/src/app/features/highlight-menu/HighlightMenu.tsx @@ -5,7 +5,6 @@ import { useInteractions, flip, shift, - inline, autoUpdate, autoPlacement, } from '@floating-ui/react'; @@ -25,9 +24,16 @@ const HighlightMenu: React.FC = (props) => { // https://floating-ui.com/docs/react-examples const { refs, floatingStyles, context } = useFloating({ placement: 'bottom', + transform: true, open: isOpen, onOpenChange: setIsOpen, - middleware: [inline(), flip(), shift(), autoPlacement()], + middleware: [ + flip(), + shift(), + autoPlacement({ + allowedPlacements: ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end'], + }), + ], whileElementsMounted: autoUpdate, }); @@ -99,10 +105,11 @@ const HighlightMenu: React.FC = (props) => { ref={refs.setFloating} style={{ ...floatingStyles, + zIndex: 9999999999999, }} {...getFloatingProps()} > -
+
{isOpenContextList ? ( { @@ -111,18 +118,17 @@ const HighlightMenu: React.FC = (props) => { /> ) : ( )} diff --git a/browser-extension/src/pages/chat/ChatPage.tsx b/browser-extension/src/pages/chat/ChatPage.tsx index 9d1f1c41..57ac3ace 100644 --- a/browser-extension/src/pages/chat/ChatPage.tsx +++ b/browser-extension/src/pages/chat/ChatPage.tsx @@ -55,7 +55,6 @@ const ChatPage: React.FC = () => { if (message.type === 'CONTENT') { setContent(message.content); } else if (message.type === 'SYSTEM-CONTEXT') { - console.log(message); setPromptSetting(message.systemContext); } }); @@ -66,7 +65,6 @@ const ChatPage: React.FC = () => { if (message.type === 'CONTENT') { setContent(message.content); } else if (message.type === 'SYSTEM-CONTEXT') { - console.log(message); setPromptSetting(message.systemContext); } });