diff --git a/components/ApplicationBar.tsx b/components/ApplicationBar.tsx index 722de64..66fe3a7 100644 --- a/components/ApplicationBar.tsx +++ b/components/ApplicationBar.tsx @@ -130,6 +130,7 @@ export function ApplicationBar(props: { ...(props.sx || {}), }} > + setPagesMenuAnchor(event.currentTarget)}>
@@ -138,6 +139,9 @@ export function ApplicationBar(props: { + setPagesMenuAnchor(event.currentTarget)}> + + {/* {chatModelId && } */} @@ -147,16 +151,6 @@ export function ApplicationBar(props: { ) : ( ))} - - )} - - ) : ( - - )} + {errorMessage && ( + {collapsedText}} variant="soft"> + + {errorMessage} + + + )} + + {isCollapsed && ( + + )} + {/* Copy message */} - {!fromSystem && !isEditing && ( - - - - - - )} {/* Message Operations menu */} {!!menuAnchor && ( - + - Copy {isSpeakable && ( - Speak )} - - - - - {isEditing ? 'Discard' : 'Edit'} - {!isEditing && (double-click)} - - - {fromAssistant && ( - - - - - Retry - - )} - {fromUser && ( - - - - - {props.isLast ? 'Run Again' : 'Restart From Here'} - - )} - - - - - Delete - )} diff --git a/components/Composer.tsx b/components/Composer.tsx index 86ac991..f11d188 100644 --- a/components/Composer.tsx +++ b/components/Composer.tsx @@ -27,6 +27,7 @@ import { useComposerStore, useSettingsStore } from '@/lib/store-settings'; import { useSpeechRecognition } from '@/components/util/useSpeechRecognition'; import vision from 'react-cloud-vision-api'; import axios from 'axios'; +import { DeleteForever } from '@mui/icons-material'; // CSS helpers @@ -43,11 +44,11 @@ const PromptTemplates = { const expandPromptTemplate = (template: string, dict: object) => - (inputValue: string): string => { - let expanded = template.replaceAll('{{input}}', (inputValue || '').trim()).trim(); - for (const [key, value] of Object.entries(dict)) expanded = expanded.replaceAll(`{{${key}}}`, value.trim()); - return expanded; - }; + (inputValue: string): string => { + let expanded = template.replaceAll('{{input}}', (inputValue || '').trim()).trim(); + for (const [key, value] of Object.entries(dict)) expanded = expanded.replaceAll(`{{${key}}}`, value.trim()); + return expanded; + }; vision.init({ auth: process.env.VISION_API_KEY }); @@ -209,17 +210,15 @@ export function Composer(props: { formData.append('image', file); const { data } = await axios.post('https://api.imgbb.com/1/upload?expiration=600&key=6fb05d54395ce902d195e68d44ac0ba6', formData); - const { data: getData } = await axios.get('https://hook.eu1.make.com/36n1b3tlrfzbiicubweup3rkfjtdq8n7?img=' + data.data.url); - newText = `A image containing information of: \n` + getData`\n ` + newText = `A image containing information of: \n` + getData`\n `; } else if (file.type === 'application/pdf') fileText = await extractPdfText(file); else fileText = await file.text(); - if (fileText) newText = expandPromptTemplate(PromptTemplates.PasteFile, { fileName: "", fileText })(newText); + if (fileText) newText = expandPromptTemplate(PromptTemplates.PasteFile, { fileName: '', fileText })(newText); } catch (error) { // show errors in the prompt box itself - FUTURE: show in a toast console.error(error); - } } @@ -345,12 +344,8 @@ export function Composer(props: { const textPlaceholder: string = `Type ${props.isDeveloperMode ? 'your message and drop source files' : 'a message, or drop text files'}...`; return ( - - - + + {/* Left pane (buttons and Textarea) */} @@ -489,27 +484,30 @@ export function Composer(props: { Stop ) : ( - +
+ {' '} + +
)}
diff --git a/components/OpenInCodepen.tsx b/components/OpenInCodepen.tsx deleted file mode 100644 index 224d2b5..0000000 --- a/components/OpenInCodepen.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Button, Tooltip } from '@mui/joy'; - -interface CodeBlockProps { - codeBlock: { - code: string; - language?: string; - }; -} - -export function OpenInCodepen({ codeBlock }: CodeBlockProps): JSX.Element { - const { code, language } = codeBlock; - const hasCSS = language === 'css'; - const hasJS = ['javascript', 'json', 'typescript'].includes(language || ''); - const hasHTML = !hasCSS && !hasJS; // use HTML as fallback if an unanticipated frontend language is used - - const handleOpenInCodepen = () => { - const data = { - title: `GPT ${new Date().toISOString()}`, // eg "GPT 2021-08-31T15:00:00.000Z" - css: hasCSS ? code : '', - html: hasHTML ? code : '', - js: hasJS ? code : '', - editors: `${hasHTML ? 1 : 0}${hasCSS ? 1 : 0}${hasJS ? 1 : 0}` // eg '101' for HTML, JS - }; - - const form = document.createElement('form'); - form.method = 'POST'; - form.action = 'https://codepen.io/pen/define'; - form.target = '_blank'; - - const input = document.createElement('input'); - input.type = 'hidden'; - input.name = 'data'; - input.value = JSON.stringify(data); - - form.appendChild(input); - document.body.appendChild(form); - form.submit(); - document.body.removeChild(form); - }; - - return ( - - - - ); -} diff --git a/components/OpenInReplit.tsx b/components/OpenInReplit.tsx deleted file mode 100644 index 6b28c56..0000000 --- a/components/OpenInReplit.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Button, Tooltip } from '@mui/joy'; - -interface CodeBlockProps { - codeBlock: { - code: string; - language?: string; - }; -} - -export function OpenInReplit({ codeBlock }: CodeBlockProps): JSX.Element { - const { code, language } = codeBlock; - - const replitLanguageMap: { [key: string]: string } = { - python: 'python3', - csharp: 'csharp', - java: 'java', - }; - - const handleOpenInReplit = () => { - const replitLanguage = replitLanguageMap[language || 'python']; - const url = new URL(`https://replit.com/languages/${replitLanguage}`); - url.searchParams.set('code', code); - url.searchParams.set('title', `GPT ${new Date().toISOString()}`); - window.open(url.toString(), '_blank'); - }; - - return ( - - - - ); -} diff --git a/components/Pages.tsx b/components/Pages.tsx index c0aff0c..e38f3d3 100644 --- a/components/Pages.tsx +++ b/components/Pages.tsx @@ -7,35 +7,35 @@ import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import { ConfirmationModal } from '@/components/dialogs/ConfirmationModal'; // import { Link } from '@/components/util/Link'; -import { InlineTextEdit } from '@/components/util/InlineTextEdit'; import { SystemPurposes } from '@/lib/data'; import { conversationTitle, MAX_CONVERSATIONS, useChatStore, useConversationIDs } from '@/lib/store-chats'; import { useSettingsStore } from '@/lib/store-settings'; - const DEBUG_CONVERSATION_IDs = false; const SPECIAL_ID_ALL_CHATS = 'all-chats'; - function ConversationListItem(props: { - conversationId: string, - isActive: boolean, isSingle: boolean, showSymbols: boolean, - conversationActivate: (conversationId: string) => void, - conversationDelete: (e: React.MouseEvent, conversationId: string) => void, + conversationId: string; + isActive: boolean; + isSingle: boolean; + showSymbols: boolean; + conversationActivate: (conversationId: string) => void; + conversationDelete: (e: React.MouseEvent, conversationId: string) => void; }) { - // state const [isEditingTitle, setIsEditingTitle] = React.useState(false); // bind to conversation - const conversation = useChatStore(state => { - const conversation = state.conversations.find(conversation => conversation.id === props.conversationId); - return conversation && { - assistantTyping: !!conversation.abortController, - setUserTitle: state.setUserTitle, - systemPurposeId: conversation.systemPurposeId, - title: conversationTitle(conversation), - }; + const conversation = useChatStore((state) => { + const conversation = state.conversations.find((conversation) => conversation.id === props.conversationId); + return ( + conversation && { + assistantTyping: !!conversation.abortController, + setUserTitle: state.setUserTitle, + systemPurposeId: conversation.systemPurposeId, + title: conversationTitle(conversation), + } + ); }, shallow); // sanity check: shouldn't happen, but just in case @@ -54,21 +54,22 @@ function ConversationListItem(props: { return ( props.conversationActivate(props.conversationId)} sx={{ // py: 0, '&:hover > button': { opacity: 1 }, }} > - {/* Icon */} - {props.showSymbols && - {assistantTyping - ? ( + {props.showSymbols && ( + + {assistantTyping ? ( */} )} - } - - {/* Text */} - {!isEditingTitle ? ( - - - {DEBUG_CONVERSATION_IDs ? props.conversationId.slice(0, 10) : title}{assistantTyping && '...'} - - - ) : ( - - - + )} {/* Edit */} @@ -110,41 +99,43 @@ function ConversationListItem(props: { {/* Delete */} {!props.isSingle && ( props.conversationDelete(e, props.conversationId)}> + variant="outlined" + color="neutral" + size="sm" + sx={{ ml: 1, opacity: { xs: 1, sm: 0 }, transition: 'opacity 0.3s', ...(props.isActive ? { color: 'white' } : {}) }} + onClick={(e) => props.conversationDelete(e, props.conversationId)} + > )} - ); } - /** * FIXME: use a proper Pages drawer instead of this menu */ -export function PagesMenu(props: { conversationId: string | null, pagesMenuAnchor: HTMLElement | null, onClose: () => void }) { +export function PagesMenu(props: { conversationId: string | null; pagesMenuAnchor: HTMLElement | null; onClose: () => void }) { // state const [deleteConfirmationId, setDeleteConfirmationId] = React.useState(null); // external state const conversationIDs = useConversationIDs(); - const { setActiveConversationId, createConversation, deleteConversation, setActiveConversation } = useChatStore(state => ({ - setActiveConversationId: state.setActiveConversationId, - createConversation: state.createConversation, - deleteConversation: state.deleteConversation, - setActiveConversation: state.setActiveConversationId, - }), shallow); - const showSymbols = useSettingsStore(state => state.zenMode) !== 'cleaner'; - + const { setActiveConversationId, createConversation, deleteConversation, setActiveConversation } = useChatStore( + (state) => ({ + setActiveConversationId: state.setActiveConversationId, + createConversation: state.createConversation, + deleteConversation: state.deleteConversation, + setActiveConversation: state.setActiveConversationId, + }), + shallow, + ); + const showSymbols = useSettingsStore((state) => state.zenMode) !== 'cleaner'; const hasChats = conversationIDs.length > 0; const singleChat = conversationIDs.length === 1; const maxReached = conversationIDs.length >= MAX_CONVERSATIONS; - const handleNew = () => createConversation(); const handleConversationActivate = (conversationId: string) => setActiveConversation(conversationId); @@ -161,9 +152,8 @@ export function PagesMenu(props: { conversationId: string | null, pagesMenuAncho if (hasChats && deleteConfirmationId) { if (deleteConfirmationId === SPECIAL_ID_ALL_CHATS) { createConversation(); - conversationIDs.forEach(conversationId => deleteConversation(conversationId)); - } else - deleteConversation(deleteConfirmationId); + conversationIDs.forEach((conversationId) => deleteConversation(conversationId)); + } else deleteConversation(deleteConfirmationId); setDeleteConfirmationId(null); } }; @@ -173,73 +163,84 @@ export function PagesMenu(props: { conversationId: string | null, pagesMenuAncho setDeleteConfirmationId(SPECIAL_ID_ALL_CHATS); }; + const NewPrefix = maxReached && ( + + ⚠️ + + ); - const NewPrefix = maxReached && ⚠️ ; - - return <> - - - - {/**/} - {/* */} - {/* Active chats*/} - {/* */} - {/**/} - - - - - {NewPrefix}New - - - - {conversationIDs.map(conversationId => - )} - - - - - - - Delete all - - - - {/**/} - {/* */} - {/* Scratchpad*/} - {/* */} - {/**/} - {/**/} - {/* */} - {/* */} - {/* Feature #17*/} - {/* */} - {/**/} - - - - {/* Confirmations */} - setDeleteConfirmationId(null)} onPositive={handleConfirmedDeleteConversation} - confirmationText={deleteConfirmationId === SPECIAL_ID_ALL_CHATS - ? 'Are you absolutely sure you want to delete ALL conversations? This action cannot be undone.' - : 'Are you sure you want to delete this conversation?'} - positiveActionText={deleteConfirmationId === SPECIAL_ID_ALL_CHATS - ? 'Yes, delete all' - : 'Delete conversation'} - /> - - ; -} \ No newline at end of file + return ( + <> + + {/**/} + {/* */} + {/* Active chats*/} + {/* */} + {/**/} + + + + + + {NewPrefix}New + + + {conversationIDs.map((conversationId) => ( + + ))} + + + + + + + + Delete all + + + {/**/} + {/* */} + {/* Scratchpad*/} + {/* */} + {/**/} + {/**/} + {/* */} + {/* */} + {/* Feature #17*/} + {/* */} + {/**/} + + + {/* Confirmations */} + setDeleteConfirmationId(null)} + onPositive={handleConfirmedDeleteConversation} + confirmationText={ + deleteConfirmationId === SPECIAL_ID_ALL_CHATS + ? 'Are you absolutely sure you want to delete ALL conversations? This action cannot be undone.' + : 'Are you sure you want to delete this conversation?' + } + positiveActionText={deleteConfirmationId === SPECIAL_ID_ALL_CHATS ? 'Yes, delete all' : 'Delete conversation'} + /> + + ); +} diff --git a/components/RenderImage.tsx b/components/RenderImage.tsx index 26336dc..925236b 100644 --- a/components/RenderImage.tsx +++ b/components/RenderImage.tsx @@ -3,6 +3,9 @@ import React from 'react'; const RenderImage = ({ imgBlock }) => { const { content } = imgBlock; + console.log("content:") + console.log(content) + return Image; }; diff --git a/components/util/InlineTextEdit.tsx b/components/util/InlineTextEdit.tsx deleted file mode 100644 index 1d0a698..0000000 --- a/components/util/InlineTextEdit.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from 'react'; -import { Textarea } from '@mui/joy'; -import { SxProps } from '@mui/joy/styles/types'; - -export function InlineTextEdit(props: { initialText: string; onEdit: (text: string) => void; sx?: SxProps }) { - const [text, setText] = React.useState(props.initialText); - - const handleEditTextChanged = (e: React.ChangeEvent) => setText(e.target.value); - - const handleEditKeyPressed = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey && !e.altKey) { - e.preventDefault(); - props.onEdit(text); - } - }; - - const handleEditBlur = () => props.onEdit(text); - - return ( - -