Shared in-app AI assistant ("FS AI") chat widget for Next.js apps. REST + Server-Sent Events streaming. Drop-in panel + floating button + Next.js API proxy + Tailwind tokens.
Ships:
<AssistantPanel>— slide-in chat drawer (general or course-aware modes).<AssistantNavButton>— toggle button (inline or floating FAB).<AssistantContextProvider>+useAssistant()— panel open/close state, persisted tolocalStorage.configureFsAi({ getToken })— wires host app auth into the FS AI Axios client.createFsAiProxyHandler()— Next.js API route factory that proxies/api/fs-ai/**to the upstream origin and injects the server-onlyX-API-Key.- Hooks:
useAssistantConversation,useAssistantPhase,useAssistantStream. - Helpers:
buildLearningMetadata,canUseLearningAssistant,isFsAiApiConfigured, conversation history store, user-profile store, SSE decoder, markdown sanitizer.
npm install @likemex/fs-react-lib
# or
pnpm add @likemex/fs-react-lib
# or
yarn add @likemex/fs-react-libPeer deps (install whichever your app uses):
react^17 || ^18 +react-dom^17 || ^18next^12 || ^13 || ^14axios>=0.24antd^5 +@ant-design/cssinjs^1@tanstack/react-query^4react-icons^4 || ^5react-markdown^7 || ^8remark-gfm^3 || ^4
import {
AssistantContextProvider,
AssistantPanel,
AssistantNavButton,
configureFsAi,
} from '@likemex/fs-react-lib';
import { auth } from 'services/auth';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ConfigProvider, App as AntdApp } from 'antd';
import { StyleProvider } from '@ant-design/cssinjs';
import 'antd/dist/reset.css';
configureFsAi({ getToken: () => auth.getToken() ?? null });
const queryClient = new QueryClient();
export default function MyApp({ Component, pageProps }) {
return (
<ConfigProvider theme={{ token: { colorPrimary: '#842CDD' } }}>
<AntdApp>
<StyleProvider hashPriority="high">
<QueryClientProvider client={queryClient}>
<AssistantContextProvider>
<Component {...pageProps} />
<AssistantNavButton floating canUse />
<AssistantPanel surface="general" canUse />
</AssistantContextProvider>
</QueryClientProvider>
</StyleProvider>
</AntdApp>
</ConfigProvider>
);
}pages/api/fs-ai/[[...slug]].ts:
import { createFsAiProxyHandler } from '@likemex/fs-react-lib';
export const config = { api: { bodyParser: false, responseLimit: false as const } };
export default createFsAiProxyHandler();tailwind.config.js:
const assistantTokens = require('@likemex/fs-react-lib/tailwind-tokens');
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
// scan compiled lib so Tailwind picks up its classes
'./node_modules/@likemex/fs-react-lib/dist/**/*.{js,d.ts}',
],
theme: {
extend: {
colors: {
// merge into existing palette
primaryFS: assistantTokens.primaryFS,
pinkFS: assistantTokens.pinkFS,
blackFS: assistantTokens.blackFS,
successFS: assistantTokens.successFS,
},
},
},
};The compiled lib is CJS but pulls in ESM-only peers (react-markdown@8, remark-gfm@3). Next 12 needs:
// next.config.js
const withTM = require('next-transpile-modules')(['@likemex/fs-react-lib']);
module.exports = withTM({
experimental: { esmExternals: 'loose' },
// ...rest of your config
});Next 13+: replace next-transpile-modules with built-in transpilePackages: ['@likemex/fs-react-lib'].
NEXT_PUBLIC_FS_AI_USE_PROXY=true
FS_AI_API_URL=https://<your-fs-ai-host>
FS_AI_X_API_KEY=<server-only-key>| Var | Side | Purpose |
|---|---|---|
NEXT_PUBLIC_FS_AI_USE_PROXY |
Client | "true" routes through the Next API proxy (recommended — keeps the API key server-side). |
NEXT_PUBLIC_FS_AI_API_URL |
Client | Direct upstream origin (when proxy disabled). |
FS_AI_API_URL |
Server | Upstream origin used by the proxy factory. |
FS_AI_X_API_KEY |
Server | X-API-Key header injected by the proxy. Never exposed to the browser. |
NEXT_PUBLIC_LEARNING_ASSISTANT_SHOW_FOR_ALL |
Client | Dev flag — show the assistant regardless of pass entitlement. |
AssistantPanel accepts a modes prop. When modes.length === 1 the ModePicker screen is skipped — chat opens directly.
// Course-bound app (5-mode picker)
<AssistantPanel
surface="watch"
courseId={courseId}
lessonId={lessonId}
modes={['general', 'before_class', 'during_class', 'after_class', 'apply']}
/>
// General-only app (no picker, no course)
<AssistantPanel surface="general" />courseId is optional. When omitted:
learning_mode='general'is set at conversation creation.patchLearningModeis skipped.learning_state.course_idis omitted from message metadata.- Conversation history is bucketed under
surface='general'withcourseId=null.
AssistantPanel uses TanStack Query and reads localStorage. For pages built with getStaticProps (SSG), wrap the panel with next/dynamic to skip server render:
import dynamic from 'next/dynamic';
const AssistantPanel = dynamic(
() => import('@likemex/fs-react-lib').then(m => m.AssistantPanel),
{ ssr: false }
);Pages using getServerSideProps or no data fetching do not need this — the QueryClientProvider in _app is sufficient.
import {
// Components
AssistantPanel,
AssistantNavButton,
AssistantContextProvider,
Composer,
MessageList,
MessageBubble,
ModePicker,
SuggestedActions,
WelcomeMessage,
ASSISTANT_PANEL_WIDTH,
SUGGESTED_ACTIONS_BY_MODE,
// Context
useAssistant,
AssistantContext,
// Auth + service
configureFsAi,
fsAiApi,
// Hooks
useAssistantConversation,
useAssistantPhase,
useAssistantStream,
// Helpers
buildLearningMetadata,
canUseLearningAssistant,
isFsAiApiConfigured,
listAssistantConversations,
upsertAssistantConversation,
removeAssistantConversation,
readAssistantFullPagePreference,
writeAssistantFullPagePreference,
readAssistantUserProfile,
writeAssistantUserProfile,
clearAssistantUserProfile,
isAssistantUserProfileComplete,
decodeSseDataPayload,
extractTextFromStreamJson,
filterDisplayableAssistantSources,
sanitizeAssistantMarkdown,
visibleStripped,
isOrphanListMarkerLine,
// Proxy factory
createFsAiProxyHandler,
} from '@likemex/fs-react-lib';Full type definitions in dist/index.d.ts.
// CommonJS (tailwind.config.js)
const tokens = require('@likemex/fs-react-lib/tailwind-tokens');
// → { primaryFS, pinkFS, blackFS, successFS }git clone https://github.com/LikeMeX/fs-react-lib.git
cd fs-react-lib
pnpm install
pnpm run build # tsc → dist/
pnpm run lint # tsc --noEmitPublish (maintainers): tag a release, GitHub Action runs build + npm publish --provenance. See .github/workflows/publish.yaml.
MIT © LikeMeX