Skip to content

Commit 50dfc4f

Browse files
authored
feat: implement code fencing properly and include code highlighting (#3)
* feat: implement code fencing properly and include code highlighting * chore: cleanup and optimizations * chore: fix eslint and faulty hooks * fix: some more eslint issues
1 parent 54ff79f commit 50dfc4f

File tree

26 files changed

+461
-415
lines changed

26 files changed

+461
-415
lines changed

eslint.config.mjs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,23 @@ import globals from 'globals';
33
import tseslint from 'typescript-eslint';
44

55
import importPlugin from 'eslint-plugin-import';
6+
import reactHooksPlugin from 'eslint-plugin-react-hooks';
67

78
export default tseslint.config(
89
{
9-
ignores: ['dist'],
10+
ignores: ['**/node_modules/**', '**/build/**', '**/dist/**'],
1011
},
1112
{
1213
name: 'default',
1314
extends: [js.configs.recommended, ...tseslint.configs.recommended],
14-
files: ['packages/**/src/**/*.ts'],
15+
files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'],
1516
languageOptions: {
1617
ecmaVersion: 2020,
1718
globals: globals.browser,
1819
},
1920
plugins: {
2021
import: importPlugin,
22+
'react-hooks': reactHooksPlugin,
2123
},
2224
settings: {
2325
react: {
@@ -93,6 +95,8 @@ export default tseslint.config(
9395
'@typescript-eslint/no-empty-object-type': 'off',
9496
'@typescript-eslint/no-empty-generator-function': 'off',
9597
'@typescript-eslint/no-explicit-any': 'off',
98+
'react-hooks/rules-of-hooks': 'error',
99+
'react-hooks/exhaustive-deps': 'error',
96100
},
97101
},
98102
);

examples/nextjs-ai-chatbot/app/api/chats/[id]/route.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { createConfigFromEnv } from '@stream-io/ai-sdk-storage/dist/utils';
44

55
const storage = createStreamStorageClient(createConfigFromEnv());
66

7-
export async function GET(req: Request, { params }: any) {
7+
8+
export async function GET(_req: Request, { params }: any) {
89
const { id } = await params;
910
try {
1011
return NextResponse.json(
1112
await storage.streamStorage.getChannelMessages(id),
1213
);
14+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1315
} catch (error) {
1416
return NextResponse.json([], { status: 404 });
1517
}

examples/nextjs-ai-chatbot/app/api/transcribe/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NextRequest } from 'next/server';
1+
import type { NextRequest } from 'next/server';
22
import { experimental_transcribe as transcribe } from 'ai';
33
import { openai } from '@ai-sdk/openai';
44

examples/nextjs-ai-chatbot/app/layout.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'animate.css';
44
import './globals.css';
55
import Sidebar from '@/components/sidebar';
66
import Composer from '@/components/composer';
7-
import { LayoutProps } from '@/types';
7+
import type { LayoutProps } from '@/types';
88
import { AppProvider } from '@/contexts/app';
99

1010
const geistSans = Geist({
@@ -24,20 +24,20 @@ export const metadata: Metadata = {
2424

2525
export default function RootLayout({ children }: LayoutProps) {
2626
return (
27-
<html lang="en" data-theme="dim">
27+
<html lang='en' data-theme='dim'>
2828
<body
2929
className={`${geistSans.variable} ${geistMono.variable} antialiased prose`}
3030
>
3131
<AppProvider>
32-
<div className="grid grid-cols-1 md:grid-cols-[300px_auto] h-screen w-screen">
33-
<div className="bg-base-200 px-5 py-2 md:relative absolute top-0 bottom-0 translate-x-[-100%] md:translate-x-0 transition-all duration-300">
32+
<div className='grid grid-cols-1 md:grid-cols-[300px_auto] h-screen w-screen'>
33+
<div className='bg-base-200 px-5 py-2 md:relative absolute top-0 bottom-0 translate-x-[-100%] md:translate-x-0 transition-all duration-300'>
3434
<Sidebar />
3535
</div>
36-
<div className="flex flex-col h-full relative px-5">
37-
<div className="w-full mx-auto flex flex-col h-[100vh] gap-2">
38-
<div className="flex-1 overflow-y-auto">{children}</div>
39-
<div className="flex-shrink-0 pb-5">
40-
<div className="max-w-3xl mx-auto">
36+
<div className='flex flex-col h-full relative px-5'>
37+
<div className='w-full mx-auto flex flex-col h-[100vh] gap-2'>
38+
<div className='flex-1 overflow-y-auto'>{children}</div>
39+
<div className='flex-shrink-0 pb-5'>
40+
<div className='max-w-3xl mx-auto'>
4141
<Composer />
4242
</div>
4343
</div>

examples/nextjs-ai-chatbot/components/composer.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import { MODELS } from '@/utils/models';
44
import {
55
ArrowUpIcon,
66
ImageIcon,
7-
Mic,
87
MicIcon,
98
MicOffIcon,
109
Paperclip,
11-
SpeakerIcon,
1210
XIcon,
1311
} from 'lucide-react';
1412
import { useEffect, useRef, useState } from 'react';
@@ -19,7 +17,7 @@ import { useTranscriber } from '@/hooks/usetranscribe';
1917
export default function Composer() {
2018
const [isActive, setIsActive] = useState(false);
2119
const [input, setInput] = useState('');
22-
const [files, setFiles] = useState<FileList[] | undefined>(undefined);
20+
const [files, setFiles] = useState<File[] | undefined>(undefined);
2321
const fileInputRef = useRef<HTMLInputElement>(null);
2422
const { isListening, transcript, start, stop } = useTranscriber();
2523

@@ -53,6 +51,7 @@ export default function Composer() {
5351

5452
useEffect(() => {
5553
if (transcript) {
54+
// eslint-disable-next-line
5655
handleSubmit({
5756
preventDefault: () => {},
5857
target: { value: transcript },
@@ -75,6 +74,7 @@ export default function Composer() {
7574
: 'outline-transparent'
7675
}`}
7776
>
77+
{}
7878
{[...(files || [])]?.map((file: any, index) => (
7979
<div key={`attachment-${index}`} className="badge badge-sm mb-2">
8080
<ImageIcon className="w-3 h-3" />
@@ -109,8 +109,7 @@ export default function Composer() {
109109
ref={fileInputRef}
110110
onChange={(e) => {
111111
if (e.target.files) {
112-
//@ts-ignore
113-
setFiles(e.target.files);
112+
setFiles([...e.target.files]);
114113
}
115114
}}
116115
/>

examples/nextjs-ai-chatbot/components/markdown.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Weather from './tools/weather';
1010
export const NonMemoizedMarkdown = ({ children }: { children: string }) => {
1111
const components = {
1212
p: ({ children }: { children: string }) => {
13-
return <div className="no-prose">{children}</div>;
13+
return <div className='no-prose'>{children}</div>;
1414
},
1515
pre: ({ children, ...props }: any) => {
1616
const codeElement = React.Children.only(children);
@@ -33,21 +33,21 @@ export const NonMemoizedMarkdown = ({ children }: { children: string }) => {
3333
},
3434
ol: ({ node, children, ...props }: any) => {
3535
return (
36-
<ol className="list-decimal list-inside ml-4" {...props}>
36+
<ol className='list-decimal list-inside ml-4' {...props}>
3737
{children}
3838
</ol>
3939
);
4040
},
4141
li: ({ node, children, ...props }: any) => {
4242
return (
43-
<li className="py-1" {...props}>
43+
<li className='py-1' {...props}>
4444
{children}
4545
</li>
4646
);
4747
},
4848
ul: ({ node, children, ...props }: any) => {
4949
return (
50-
<ul className="list-decimal list-inside ml-4" {...props}>
50+
<ul className='list-decimal list-inside ml-4' {...props}>
5151
{children}
5252
</ul>
5353
);

examples/nextjs-ai-chatbot/components/messages.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { UIMessage } from '@ai-sdk/react';
3+
import type { UIMessage } from '@ai-sdk/react';
44
import { Sparkles } from 'lucide-react';
55
import { Markdown } from '@/components/markdown';
66
import { useApp } from '@/contexts/app';
@@ -23,19 +23,19 @@ export default function Messages() {
2323

2424
if (isLoadingMessages) {
2525
return (
26-
<div className="flex justify-center items-center h-full animate__animated animate__fadeIn">
27-
<span className="loading loading-spinner loading-md"></span>
26+
<div className='flex justify-center items-center h-full animate__animated animate__fadeIn'>
27+
<span className='loading loading-spinner loading-md'></span>
2828
</div>
2929
);
3030
}
3131

3232
if (!id) {
3333
return (
34-
<div className="flex justify-center items-center flex-col h-full ">
35-
<h1 className="animate__animated animate__fadeInUp mb-0 ">
36-
<span className="ai-thinking">Welcome to AI Assistant</span>
34+
<div className='flex justify-center items-center flex-col h-full '>
35+
<h1 className='animate__animated animate__fadeInUp mb-0 '>
36+
<span className='ai-thinking'>Welcome to AI Assistant</span>
3737
</h1>
38-
<p className="animate__animated animate__fadeInUp animate__delay-1s text-gray-500">
38+
<p className='animate__animated animate__fadeInUp animate__delay-1s text-gray-500'>
3939
Ready to help you with any questions or tasks. How can I assist you
4040
today?
4141
</p>
@@ -44,29 +44,29 @@ export default function Messages() {
4444
}
4545

4646
return (
47-
<div className="py-15 pl-3 max-w-3xl mx-auto space-y-5">
47+
<div className='py-15 pl-3 max-w-3xl mx-auto space-y-5'>
4848
{messages.map((m: UIMessage) => (
4949
<div
5050
key={m.id}
5151
className={`chat animate__animated animate__fadeIn ${
5252
m.role === 'user' ? 'chat-end' : 'chat-start'
5353
}`}
5454
>
55-
<div className="flex gap-2 flex-wrap">
55+
<div className='flex gap-2 flex-wrap'>
5656
{m.parts.map(
5757
(part, index) =>
5858
part.type === 'file' &&
5959
part.url && (
6060
<a
6161
key={'file-' + index + m.id}
6262
href={part.url}
63-
target="_blank"
64-
className="not-prose mb-2"
63+
target='_blank'
64+
className='not-prose mb-2'
6565
>
6666
<Image
6767
src={part.url}
6868
alt={part.filename || 'unknown'}
69-
className="w-30 h-30 rounded-lg object-cover"
69+
className='w-30 h-30 rounded-lg object-cover'
7070
width={300}
7171
height={300}
7272
/>
@@ -91,9 +91,9 @@ export default function Messages() {
9191
</div>
9292
))}
9393
{status === 'submitted' && (
94-
<div className="flex gap-1 items-center text-sm my-4 animate-pulse">
95-
<Sparkles className="w-3 h-3 animate-pulse text-[#00ffe0]" />
96-
<span className="ai-thinking">Thinking</span>
94+
<div className='flex gap-1 items-center text-sm my-4 animate-pulse'>
95+
<Sparkles className='w-3 h-3 animate-pulse text-[#00ffe0]' />
96+
<span className='ai-thinking'>Thinking</span>
9797
</div>
9898
)}
9999
<div ref={messagesEndRef} />

examples/nextjs-ai-chatbot/components/sidebar.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
import { useApp } from '@/contexts/app';
44
import { Dot, MessageSquarePlus, MessageSquareText } from 'lucide-react';
55
import Link from 'next/link';
6-
import { Chat as ChatType } from '@/types';
6+
import type { Chat as ChatType } from '@/types';
77
import { useParams } from 'next/navigation';
88

99
export default function Sidebar({ title = 'Chats' }) {
1010
const { chats } = useApp();
1111
const { id } = useParams();
1212
return (
1313
<>
14-
<h3 className="flex items-center gap-2">
15-
<MessageSquareText className="w-5 h-5" /> {title}
14+
<h3 className='flex items-center gap-2'>
15+
<MessageSquareText className='w-5 h-5' /> {title}
1616
</h3>
17-
<div className="animate__animated animate__fadeIn relative">
17+
<div className='animate__animated animate__fadeIn relative'>
1818
{chats?.length > 0 ? (
1919
chats.map((chat: ChatType) => (
2020
<Link
@@ -25,23 +25,23 @@ export default function Sidebar({ title = 'Chats' }) {
2525
id === chat.id ? 'text-primary' : ''
2626
}`}
2727
>
28-
<div className="flex items-center gap-1 min-w-0">
29-
<Dot className="flex-shrink-0" />
30-
<span className="truncate">{chat.name}</span>
28+
<div className='flex items-center gap-1 min-w-0'>
29+
<Dot className='flex-shrink-0' />
30+
<span className='truncate'>{chat.name}</span>
3131
</div>
3232
</Link>
3333
))
3434
) : (
35-
<div className="flex items-center gap-2 text-gray-600">
35+
<div className='flex items-center gap-2 text-gray-600'>
3636
No chats found
3737
</div>
3838
)}
3939
</div>
4040
<Link
41-
href="/"
42-
className="mb-2 btn btn-default btn-soft left-4 right-4 absolute bottom-2"
41+
href='/'
42+
className='mb-2 btn btn-default btn-soft left-4 right-4 absolute bottom-2'
4343
>
44-
<MessageSquarePlus className="w-4 h-4" />
44+
<MessageSquarePlus className='w-4 h-4' />
4545
New Chat
4646
</Link>
4747
</>

examples/nextjs-ai-chatbot/components/tools/weather.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import { WeatherToolResponse } from '@/types';
2-
import { MapPinIcon, CloudSunIcon } from 'lucide-react';
1+
import type { WeatherToolResponse } from '@/types';
2+
import { CloudSunIcon, MapPinIcon } from 'lucide-react';
33

44
export default function Weather({ data }: { data: string }) {
55
const weather: WeatherToolResponse = JSON.parse(data);
66
return (
7-
<div className="bg-base-200 p-4 pb-8 rounded-lg relative overflow-hidden min-w-sm">
8-
<div className="text-lg font-bold flex items-center gap-2">
9-
<MapPinIcon className="w-4 h-4" />
7+
<div className='bg-base-200 p-4 pb-8 rounded-lg relative overflow-hidden min-w-sm'>
8+
<div className='text-lg font-bold flex items-center gap-2'>
9+
<MapPinIcon className='w-4 h-4' />
1010
{weather.location?.name || 'Unknown'}
1111
</div>
12-
<div className="grid grid-cols-[1fr_2fr] gap-4 items-center justify-center my-4 font-bold">
12+
<div className='grid grid-cols-[1fr_2fr] gap-4 items-center justify-center my-4 font-bold'>
1313
<div>
1414
<img
1515
src={weather.current?.condition?.icon}
1616
alt={weather.current?.condition?.text}
17-
className="object-cover not-prose bg-base-100 rounded-lg p-2"
17+
className='object-cover not-prose bg-base-100 rounded-lg p-2'
1818
width={64}
1919
height={64}
2020
/>
@@ -24,15 +24,15 @@ export default function Weather({ data }: { data: string }) {
2424
<br /> Feels like: {weather.current?.feelslike_c}°C
2525
</div>
2626
</div>
27-
<div className="text-sm text-gray-500">
27+
<div className='text-sm text-gray-500'>
2828
<div>Condition: {weather.current?.condition?.text}</div>
2929
<div>Wind: {weather.current?.wind_kph} km/h</div>
3030
<div>Humidity: {weather.current?.humidity}%</div>
3131
<div>Pressure: {weather.current?.pressure_mb} mb</div>
3232
</div>
33-
<div className="flex items-center gap-1 opacity-50 text-xs absolute bottom-2 right-2 text-[#00ffe0]">
34-
<CloudSunIcon className="w-3 h-3" />
35-
<span className="ai-thinking">weather tool</span>
33+
<div className='flex items-center gap-1 opacity-50 text-xs absolute bottom-2 right-2 text-[#00ffe0]'>
34+
<CloudSunIcon className='w-3 h-3' />
35+
<span className='ai-thinking'>weather tool</span>
3636
</div>
3737
</div>
3838
);

0 commit comments

Comments
 (0)