Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Lint
on:
push:
pull_request:
workflow_dispatch:

jobs:
lint:
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Test (including both unit tests and UI tests)

on:
push:
pull_request:
workflow_dispatch:

jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Test
run: pnpm run full-test
25 changes: 13 additions & 12 deletions examples/nextjs-streaming/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"clean": "rm -rf ./dist ./node_modules ./.next/"
},
"dependencies": {
"@ai-sdk/openai": "^1.1.12",
"ai": "^4.3.16",
"jotai": "^2.12.5",
"@ai-sdk/openai": "^2.0.30",
"ai": "^5.0.44",
"jotai": "^2.14.0",
"jotai-ai": "workspace:*",
"jotai-lazy": "^1.0.4",
"next": "15.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"next": "15.5.3",
"react": "^19.1.1",
"react-dom": "^19.1.1"
},
"devDependencies": {
"@types/node": "^22",
"@types/react": "^18.3.12",
"@types/react-dom": "^18",
"eslint": "^9",
"eslint-config-next": "15.2.0",
"typescript": "5.6.3"
"@types/react": "^19.1.13",
"@types/react-dom": "^19.1.9",
"eslint": "^9.35.0",
"eslint-config-next": "15.5.3",
"typescript": "5.9.2"
}
}
11 changes: 3 additions & 8 deletions examples/nextjs-streaming/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,9 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"]
},
"plugins": [
{
"name": "next"
}
]
"composite": true,
"paths": { "@/*": ["./*"] },
"plugins": [{ "name": "next" }]
},
"include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
Expand Down
17 changes: 11 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
"type": "module",
"author": "himself65 <himself65@outlook.com>",
"license": "MIT",
"packageManager": "pnpm@10.13.1",
"packageManager": "pnpm@10.16.1",
"engines": {
"node": ">=22.18.0"
},
"scripts": {
"build": "pnpm -r build",
"lint": "prettier -c ."
"build": "pnpm -r --filter './packages/**' build",
"build:examples": "pnpm -r --filter './examples/**' build",
"lint": "prettier -c .",
"clean": "rm -rf ./node_modules && pnpm -r clean",
"full-test": "pnpm -r test && pnpm -r ui-test"
},
"devDependencies": {
"@types/react": "18.3.12",
"prettier": "^3.3.3",
"typescript": "5.6.3"
"prettier": "^3.6.2",
"typescript": "5.9.2"
}
}
75 changes: 43 additions & 32 deletions packages/jotai-ai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,28 @@
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./react": {
"types": "./dist/react.d.ts",
"import": "./dist/react.js",
"require": "./dist/react.cjs"
"./legacy": {
"types": "./dist/legacy/index.d.ts",
"import": "./dist/legacy/index.js",
"require": "./dist/legacy/index.cjs"
},
"./legacy/react": {
"types": "./dist/legacy/react/index.d.ts",
"import": "./dist/legacy/react/index.js",
"require": "./dist/legacy/react/index.cjs"
}
},
"files": [
"src",
"dist"
"dist",
"tests"
],
"scripts": {
"build": "bunchee",
"dev": "bunchee --watch",
"lint": "prettier --c .",
"test": "echo \"Error: no test specified\" && exit 1",
"ui-test": "vitest --config vitest.ui.config.ts"
"ui-test": "vitest --config vitest.ui.config.ts --reporter=verbose",
"clean": "rm -rf ./dist ./node_modules"
},
"keywords": [
"ai",
Expand All @@ -35,34 +41,39 @@
],
"author": "himself65 <himself65@outlook.com>",
"license": "MIT",
"devDependencies": {
"@ai-sdk/provider": "^1.1.3",
"@ai-sdk/react": "^1.2.12",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/react": "18.3.12",
"@vitejs/plugin-react": "^4.3.3",
"bunchee": "^6.5.4",
"jsdom": "^25.0.1",
"react": "18.3.1",
"typescript": "5.8.3",
"vitest": "^2.1.5"
"dependencies": {
"@ai-sdk/provider-utils": "^3.0.10",
"@ai-sdk/ui-utils": "^1.2.11",
"ai": "^5.0.56",
"jotai": "^2.15.0",
"react": "^19.1.1"
},
"peerDependencies": {
"@ai-sdk/provider-utils": ">=2 <3",
"@ai-sdk/ui-utils": ">=1 <2",
"ai": ">=4 <5",
"react": ">=18",
"@ai-sdk/provider-utils": ">=3 <4",
"@ai-sdk/react": ">=2 <3",
"@ai-sdk/ui-utils": ">=2 <3",
"ai": ">=5 <6",
"jotai": ">=2.5.1",
"jotai-lazy": ">=1.0.4"
"react": ">=18"
},
"dependencies": {
"@ai-sdk/provider-utils": "^2.2.8",
"@ai-sdk/ui-utils": "^1.2.11",
"ai": "^4.3.16",
"jotai": "^2.12.5",
"jotai-lazy": "^1.0.4"
"devDependencies": {
"@ai-sdk/provider": "^2.0.0",
"@ai-sdk/react": "^2.0.56",
"@ai-sdk/test-server": "1.0.0-beta.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/react": "19.1.15",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^5.0.4",
"@vitest/browser": "^3.2.4",
"bunchee": "^6.6.0",
"jsdom": "^27.0.0",
"msw": "^2.11.3",
"swr": "^2.3.6",
"typescript": "5.9.2",
"vitest": "^3.2.4",
"zod": "^4.1.11"
}
}
93 changes: 93 additions & 0 deletions packages/jotai-ai/src/atom-with-chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { WritableAtom, Atom, Getter } from 'jotai';
import type { UIMessage, AbstractChat, ChatInit, ChatStatus } from 'ai';
import type { UseChatHelpers } from '@ai-sdk/react';

import { atom } from 'jotai';

// export interface AbstractReactChat<UI_MESSAGE extends UIMessage>
// extends Chat<UI_MESSAGE> {}

export type AtomWithChatRead<UI_MESSAGE extends UIMessage> = (
get: Getter,
) => AbstractChat<UI_MESSAGE>;

export type AtomWithChatInit<UI_MESSAGE extends UIMessage> = Omit<
ChatInit<UI_MESSAGE>,
'id' | 'messages'
>;

type JotaiChatHelpers<UI_MESSAGE extends UIMessage> = Omit<
UseChatHelpers<UI_MESSAGE>,
/**
* Function to update the messages state locally without triggering an API call.
* Useful for optimistic updates.
*
* TODO: Not implemented yet
*/
'setMessages'
> & {
lastMessage: UI_MESSAGE | undefined;
};

export type ChatAtom<UI_MESSAGE extends UIMessage> = Atom<
JotaiChatHelpers<UI_MESSAGE>
>;

export type AtomWithChatResult<UI_MESSAGE extends UIMessage> = {
idAtom: Atom<string>;
statusAtom: Atom<ChatStatus>;
errorAtom: WritableAtom<Error | undefined, never, void>;
messagesAtom: Atom<UI_MESSAGE[]>;
lastMessageAtom: Atom<UI_MESSAGE | undefined>;
chatAtom: ChatAtom<UI_MESSAGE>;
};

export function atomWithChat<UI_MESSAGE extends UIMessage>(
read: AtomWithChatRead<UIMessage>,
): AtomWithChatResult<UI_MESSAGE> {
const statusAtom = atom<ChatStatus>(get => read(get).status);
const idAtom = atom<string>(get => read(get).id);
const errorAtom = atom<Error | undefined, never, void>(
get => read(get).error,
(get, set) => {
const chat = read(get);
return chat.clearError();
},
);
const lastMessageAtom = atom<UI_MESSAGE | undefined>(
get => read(get).lastMessage,
);
const messagesAtom = atom<UI_MESSAGE[]>(get => read(get).messages);

const chatAtom = atom<JotaiChatHelpers<UI_MESSAGE>>(get => {
const chat = read(get);

// return {
// // state
// id: chat.id,
// status: chat.status,
// error: chat.error,
// messages: chat.messages,
// lastMessage: chat.lastMessage,

// // handlers
// sendMessage: chat.sendMessage,
// regenerate: chat.regenerate,
// stop: chat.stop,
// clearError: chat.clearError,
// resumeStream: chat.resumeStream,
// addToolResult: chat.addToolResult,
// };

return chat;
});

return {
idAtom,
errorAtom,
statusAtom,
messagesAtom,
chatAtom,
lastMessageAtom,
};
}
3 changes: 3 additions & 0 deletions packages/jotai-ai/src/atom-with-completion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/**
* Placeholder for future `atomWithCompletion` (eq to `useCompletion`) implementation.
*/
3 changes: 3 additions & 0 deletions packages/jotai-ai/src/atom-with-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/**
* Placeholder for future `atomWithObject` (eq to `useObject`) implementation.
*/
Loading
Loading