diff --git a/app/libs/theme.tsx b/app/libs/theme.tsx index 8b3bab0..aca24a0 100644 --- a/app/libs/theme.tsx +++ b/app/libs/theme.tsx @@ -1,4 +1,4 @@ -import { Button, createTheme, CSSVariablesResolver, DEFAULT_THEME, defaultVariantColorsResolver, isLightColor, parseThemeColor, rgba, Tooltip } from '@mantine/core' +import { Button, Container, createTheme, CSSVariablesResolver, DEFAULT_THEME, defaultVariantColorsResolver, isLightColor, parseThemeColor, rgba, Tooltip } from '@mantine/core' export const theme = createTheme({ cursorType: 'pointer', @@ -8,6 +8,13 @@ export const theme = createTheme({ autoContrast: true, fontFamily: '"Inter", sans-serif', headings: { fontFamily: '"Greycliff CF", sans-serif' }, + breakpoints: { + xs: '36em', + sm: '52em', + md: '62em', + lg: '75em', + xl: '88em', + }, components: { Tooltip: Tooltip.extend({ defaultProps: { withArrow: true, openDelay: 200 }, diff --git a/app/routes/api/completion.route.ts b/app/routes/api/completion.route.ts index 23de9b7..bccc988 100644 --- a/app/routes/api/completion.route.ts +++ b/app/routes/api/completion.route.ts @@ -28,7 +28,7 @@ export async function action({ request, params }: ActionFunctionArgs) { if (isNewConversation) { conversation = await prisma.conversation.create({ - data: { title: '', userId: user.id }, + data: { title: 'New chat', userId: user.id }, include: conversationInclude, }) } else { @@ -42,6 +42,9 @@ export async function action({ request, params }: ActionFunctionArgs) { return json({ message: 'Conversation is not found' }, { status: 404 }) } + const abortController = new AbortController() + const { signal } = abortController + return new Response(new ReadableStream({ async start(controller) { function enqueue(event: string, data: any) { @@ -74,15 +77,35 @@ export async function action({ request, params }: ActionFunctionArgs) { model: 'gpt-4o-mini', messages: messages as ChatCompletionMessageParam[], user: user.id, - }) + }, { signal }) stream.on('content', (content) => { + if (signal.aborted) { + stream.controller.abort() + return + } + enqueue('msg', { content }) }) const chatCompletion = await stream.finalChatCompletion() const fullResponse = chatCompletion.choices[0]?.message?.content || '' + let generatedTitle = '' + + if (isNewConversation) { + const newTitle = await openai.chat.completions.create({ + model: 'gpt-4o-mini', + messages: [ + ...messages as ChatCompletionMessageParam[], + { role: 'assistant', content: fullResponse }, + { role: 'system', content: `Please give me a title for this conversation, like chatgpt does, without any symbol` }, + ], + }) + + generatedTitle = newTitle.choices[0]?.message.content?.trim() || conversation.title + } + const [assistantMessage] = await Promise.all([ prisma.message.create({ data: { @@ -93,20 +116,9 @@ export async function action({ request, params }: ActionFunctionArgs) { }), (async () => { if (isNewConversation) { - const generatedTitle = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: [ - ...messages as ChatCompletionMessageParam[], - { role: 'assistant', content: fullResponse }, - { role: 'system', content: `Please give me a title for this conversation, like chatgpt does, without any symbol` }, - ], - }) - - const title = generatedTitle.choices[0]?.message.content?.trim() || content - await prisma.conversation.update({ where: { id: conversation.id, userId: user.id, deletedAt: null }, - data: { title, updatedAt: new Date() }, + data: { title: generatedTitle, updatedAt: new Date() }, }) } else { await prisma.conversation.update({ @@ -119,15 +131,24 @@ export async function action({ request, params }: ActionFunctionArgs) { enqueue('assistant-msg', { message: assistantMessage }) } catch (error) { - logger.error({ - code: 'ERROR_OPENAI_STREAM', - error, - }) - enqueue('error', { message: 'An error occured' }) + if (error.name === 'AbortError') { + enqueue('aborted', { message: 'Request was aborted' }) + } else { + logger.error({ + code: 'ERROR_OPENAI_STREAM', + error, + }) + enqueue('error', { message: 'An error occured' }) + } } finally { controller.close() } }, + + cancel() { + console.log('logdev cancel') + abortController.abort() + }, }), { headers: { 'Content-Type': 'text/event-stream', diff --git a/app/routes/conversation/conversation.route.tsx b/app/routes/conversation/conversation.route.tsx index 1291271..236980c 100644 --- a/app/routes/conversation/conversation.route.tsx +++ b/app/routes/conversation/conversation.route.tsx @@ -21,6 +21,8 @@ const CONVERSATION_SUGGESTIONS = [ 'Summarize a long document', ] +const CONTAINER_SIZE = 820 + export const meta: MetaFunction = () => { return [ { title: 'Chat - Remix workshop' }, @@ -208,7 +210,7 @@ export default function ChatRoute() { > {!state.messages.length ? ( - +
{CONVERSATION_SUGGESTIONS.map((suggestion) => ( @@ -232,7 +234,7 @@ export default function ChatRoute() { {state.messages.map((message) => { if (message.sender === 'user') { return ( - +
{message.content}
@@ -240,7 +242,7 @@ export default function ChatRoute() { ) } else { return ( - +
@@ -253,7 +255,7 @@ export default function ChatRoute() { })} {typeof state.completion === 'string' && ( - +
@@ -268,7 +270,7 @@ export default function ChatRoute() { )} - + diff --git a/app/routes/layout.route.tsx b/app/routes/layout.route.tsx index 425fecb..e050427 100644 --- a/app/routes/layout.route.tsx +++ b/app/routes/layout.route.tsx @@ -152,8 +152,20 @@ export default function LayoutRoute() { Are you sure you want to delete this conversation?
- - + +
@@ -161,7 +173,7 @@ export default function LayoutRoute() {
- setState({ desktopOpened: false })}> + setState({ desktopOpened: true })}> @@ -196,7 +208,7 @@ export default function LayoutRoute() {
- setState({ desktopOpened: true })}> + setState({ desktopOpened: false })}> diff --git a/articles.md b/articles.md index 5f27eb1..28b714a 100644 --- a/articles.md +++ b/articles.md @@ -1,4 +1,5 @@ - https://andrelandgraf.dev/blog/2023-01-07_why_you_shouldnt_use_useactiondata - https://remix.run/docs/en/main/discussion/form-vs-fetcher - https://reactrouter.com/dev/guides -- https://sergiodxa.com/tutorials/use-server-sent-events-with-remix \ No newline at end of file +- https://sergiodxa.com/tutorials/use-server-sent-events-with-remix +- https://github.com/perezcarreno/chatgpt-remix \ No newline at end of file diff --git a/postcss.config.js b/postcss.config.js index e8e62f1..43c3176 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -6,7 +6,7 @@ export default { 'postcss-simple-vars': { variables: { 'mantine-breakpoint-xs': '36em', - 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-sm': '52em', 'mantine-breakpoint-md': '62em', 'mantine-breakpoint-lg': '75em', 'mantine-breakpoint-xl': '88em', diff --git a/tailwind.config.ts b/tailwind.config.ts index 3310e3d..a7172b1 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -46,7 +46,8 @@ export default { }, screens: { xs: '36em', // 36em * 16px = 576px - sm: '48em', // 48em * 16px = 768px + // sm: '48em', // 48em * 16px = 768px + sm: '52em', // 52em * 16px = 832px md: '62em', // 62em * 16px = 992px lg: '75em', // 75em * 16px = 1200px xl: '88em', // 88em * 16px = 1408px