diff --git a/eslint.config.mjs b/eslint.config.mjs index 69f8e7b..a9fb714 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,21 +3,23 @@ import globals from 'globals'; import tseslint from 'typescript-eslint'; import importPlugin from 'eslint-plugin-import'; +import reactHooksPlugin from 'eslint-plugin-react-hooks'; export default tseslint.config( { - ignores: ['dist'], + ignores: ['**/node_modules/**', '**/build/**', '**/dist/**'], }, { name: 'default', extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['packages/**/src/**/*.ts'], + files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: { import: importPlugin, + 'react-hooks': reactHooksPlugin, }, settings: { react: { @@ -93,6 +95,8 @@ export default tseslint.config( '@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-empty-generator-function': 'off', '@typescript-eslint/no-explicit-any': 'off', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error', }, }, ); diff --git a/examples/nextjs-ai-chatbot/app/api/chats/[id]/route.ts b/examples/nextjs-ai-chatbot/app/api/chats/[id]/route.ts index 3c13120..d673159 100644 --- a/examples/nextjs-ai-chatbot/app/api/chats/[id]/route.ts +++ b/examples/nextjs-ai-chatbot/app/api/chats/[id]/route.ts @@ -4,12 +4,14 @@ import { createConfigFromEnv } from '@stream-io/ai-sdk-storage/dist/utils'; const storage = createStreamStorageClient(createConfigFromEnv()); -export async function GET(req: Request, { params }: any) { + +export async function GET(_req: Request, { params }: any) { const { id } = await params; try { return NextResponse.json( await storage.streamStorage.getChannelMessages(id), ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return NextResponse.json([], { status: 404 }); } diff --git a/examples/nextjs-ai-chatbot/app/api/transcribe/route.ts b/examples/nextjs-ai-chatbot/app/api/transcribe/route.ts index 57dd879..0a39db6 100644 --- a/examples/nextjs-ai-chatbot/app/api/transcribe/route.ts +++ b/examples/nextjs-ai-chatbot/app/api/transcribe/route.ts @@ -1,4 +1,4 @@ -import { NextRequest } from 'next/server'; +import type { NextRequest } from 'next/server'; import { experimental_transcribe as transcribe } from 'ai'; import { openai } from '@ai-sdk/openai'; diff --git a/examples/nextjs-ai-chatbot/app/layout.tsx b/examples/nextjs-ai-chatbot/app/layout.tsx index 3e24259..dee279b 100644 --- a/examples/nextjs-ai-chatbot/app/layout.tsx +++ b/examples/nextjs-ai-chatbot/app/layout.tsx @@ -4,7 +4,7 @@ import 'animate.css'; import './globals.css'; import Sidebar from '@/components/sidebar'; import Composer from '@/components/composer'; -import { LayoutProps } from '@/types'; +import type { LayoutProps } from '@/types'; import { AppProvider } from '@/contexts/app'; const geistSans = Geist({ @@ -24,20 +24,20 @@ export const metadata: Metadata = { export default function RootLayout({ children }: LayoutProps) { return ( - +
+
Ready to help you with any questions or tasks. How can I assist you today?
@@ -44,7 +44,7 @@ export default function Messages() { } return ( -src/App.tsx and save to test HMR
+
Click on the Vite and React logos to learn more
> diff --git a/package.json b/package.json index e530ea2..8070665 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "prettier": "^3.6.2", "typescript": "catalog:", "typescript-eslint": "^8.46.2", + "eslint-plugin-react-hooks": "^5.2.0", "vitest": "^4.0.6" }, "packageManager": "pnpm@10.13.1" diff --git a/packages/react-native-sdk/package.json b/packages/react-native-sdk/package.json index 986527f..fbd98bd 100644 --- a/packages/react-native-sdk/package.json +++ b/packages/react-native-sdk/package.json @@ -44,14 +44,16 @@ "dependencies": { "@khanacademy/simple-markdown": "^2.1.0", "linkifyjs": "^4.3.2", - "lodash": "4.17.21" + "lodash": "4.17.21", + "react-native-syntax-highlighter": "^2.1.0", + "react-syntax-highlighter": "15.5.0" }, "devDependencies": { "@types/lodash": "4.17.20", "@types/node": "^24", - "@types/react": "19.1.1", + "@types/react": "19.2.2", "concurrently": "catalog:", - "react": "19.1.1", + "react": "19.2.0", "react-native": "^0.82.1", "react-native-builder-bob": "0.40.14", "react-native-gesture-handler": "^2.29.0", diff --git a/packages/react-native-sdk/src/MarkdownRichText.tsx b/packages/react-native-sdk/src/MarkdownRichText.tsx index 616132d..1e700fa 100644 --- a/packages/react-native-sdk/src/MarkdownRichText.tsx +++ b/packages/react-native-sdk/src/MarkdownRichText.tsx @@ -1,16 +1,10 @@ -import { useCallback, useMemo } from 'react'; -import { - generateMarkdownText, - type MarkdownOutputProps, - type MarkdownTableRowProps, -} from './markdown'; +import { useMemo } from 'react'; +import { generateMarkdownText } from './markdown'; import { Markdown } from './markdown'; -import styles from './markdown/styles.ts'; import type { MarkdownRules, MarkdownStyle } from './markdown'; -import { Linking, Text, View } from 'react-native'; +import { Linking } from 'react-native'; -import type { SingleASTNode } from '@khanacademy/simple-markdown'; -import { MarkdownReactiveScrollView } from './components'; +import { useStableCallback } from './internal/hooks/useStableCallback.ts'; export const MarkdownRichText = ({ text, @@ -27,92 +21,17 @@ export const MarkdownRichText = ({ }) => { const markdownText = useMemo(() => generateMarkdownText(text), [text]); - /** - * ===================================================================== - */ - - const onLink = (url: string) => + const onLink = useStableCallback((url: string) => onLinkParam ? onLinkParam(url) : Linking.canOpenURL(url).then( (canOpenUrl) => canOpenUrl && Linking.openURL(url), - ); - - // take the @ mentions and turn them into markdown? - // translate links - // const { mentioned_users } = message; - // const mentionedUsernames = (mentioned_users || []) - // .map((user) => user.name || user.id) - // .filter(Boolean) - // .sort((a, b) => b.length - a.length) - // .map(escapeRegExp); - // const mentionedUsers = mentionedUsernames.map((username) => `@${username}`).join('|'); - // const regEx = new RegExp(`^\\B(${mentionedUsers})`, 'g'); - // const mentionsMatchFunction: MatchFunction = (source) => regEx.exec(source); - // - // const mentionsReact: ReactNodeOutput = (node, output, { ...state }) => { - // /**removes the @ prefix of username */ - // const userName = node.content[0]?.content?.substring(1); - // const onPress = (event: GestureResponderEvent) => { - // if (!preventPress && onPressParam) { - // onPressParam({ - // additionalInfo: { - // user: mentioned_users?.find((user: UserResponse) => userName === user.name), - // }, - // emitter: 'textMention', - // event, - // }); - // } - // }; - // - // const onLongPress = (event: GestureResponderEvent) => { - // if (!preventPress && onLongPressParam) { - // onLongPressParam({ - // emitter: 'textMention', - // event, - // }); - // } - // }; - // - // return ( - //