@hanzo/react 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +317 -53
  2. package/dist/index.d.mts +255 -0
  3. package/dist/index.d.ts +255 -0
  4. package/dist/index.js +548 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +539 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +84 -91
  9. package/src/components/HanzoProvider.test.tsx +346 -0
  10. package/src/components/HanzoProvider.tsx +508 -0
  11. package/src/hooks/index.ts +39 -0
  12. package/src/hooks/types.ts +162 -0
  13. package/src/hooks/useAuth.ts +0 -0
  14. package/src/hooks/useComponent.ts +0 -0
  15. package/src/hooks/useGenerativeUI.ts +0 -0
  16. package/src/hooks/useMCP.ts +0 -0
  17. package/src/hooks/useMessage.ts +105 -0
  18. package/src/hooks/useModelConfig.ts +0 -0
  19. package/src/hooks/useStreaming.ts +161 -0
  20. package/src/hooks/useSuggestions.ts +0 -0
  21. package/src/hooks/useThread.ts +0 -0
  22. package/src/hooks/useTool.ts +0 -0
  23. package/src/index.ts +40 -0
  24. package/src/types/index.ts +25 -0
  25. package/src/utils/cn.ts +6 -0
  26. package/src/utils/id.ts +6 -0
  27. package/src/utils/stream.ts +33 -0
  28. package/LICENSE +0 -21
  29. package/dist/index.cjs.js +0 -15755
  30. package/dist/index.cjs.js.map +0 -1
  31. package/dist/index.css +0 -789
  32. package/dist/index.esm.js +0 -15736
  33. package/dist/index.esm.js.map +0 -1
  34. package/dist/index.umd.js +0 -15756
  35. package/dist/index.umd.js.map +0 -1
  36. package/src/controls/.DS_Store +0 -0
  37. package/src/controls/MUICheckbox.js +0 -108
  38. package/src/controls/MUIKeyboardDatePicker.js +0 -90
  39. package/src/controls/MUIPhone.js +0 -33
  40. package/src/controls/MUISwitch.js +0 -107
  41. package/src/controls/MUIText.js +0 -298
  42. package/src/controls/NumericFormats.js +0 -57
  43. package/src/controls/control.js +0 -148
  44. package/src/controls/index.js +0 -7
  45. package/src/controls/material-ui-phone-number/components/Item.js +0 -66
  46. package/src/controls/material-ui-phone-number/components/flags.css +0 -789
  47. package/src/controls/material-ui-phone-number/components/index.js +0 -884
  48. package/src/controls/material-ui-phone-number/components/polyfills.js +0 -82
  49. package/src/controls/material-ui-phone-number/country_data.js +0 -1536
  50. package/src/controls/material-ui-phone-number/index.js +0 -3
  51. package/src/index.js +0 -1
  52. /package/src/{core/index.js → hooks/useAttachments.ts} +0 -0
package/package.json CHANGED
@@ -1,108 +1,101 @@
1
1
  {
2
2
  "name": "@hanzo/react",
3
- "version": "0.1.2",
4
- "description": "Hanzo React Components",
5
- "keywords": [
6
- "hanzo",
7
- "react"
8
- ],
9
- "author": "David Tai <david@verus.io>",
10
- "homepage": "http://hanzo.ai",
11
- "license": "ISC",
12
- "main": "dist/index.cjs.js",
13
- "module": "dist/index.esm.js",
14
- "browser": "dist/index.umd.js",
15
- "directories": {
16
- "src": "src",
17
- "dist": "dist"
3
+ "version": "1.0.0",
4
+ "description": "React package for building AI-powered applications with generative UI",
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org/",
7
+ "access": "public",
8
+ "scope": "@hanzo"
18
9
  },
10
+ "author": "Hanzo AI, Inc.",
11
+ "license": "BSD-3-Clause",
12
+ "main": "./dist/index.js",
13
+ "module": "./dist/index.mjs",
14
+ "types": "./dist/index.d.ts",
15
+ "sideEffects": false,
19
16
  "files": [
17
+ "dist",
20
18
  "src",
21
- "dist"
19
+ "README.md",
20
+ "LICENSE"
22
21
  ],
23
- "publishConfig": {
24
- "access": "public"
25
- },
26
22
  "repository": {
27
23
  "type": "git",
28
- "url": "https://github.com/hanzoai/hanzo"
24
+ "url": "git+https://github.com/hanzoai/ui.git",
25
+ "directory": "pkg/react"
29
26
  },
27
+ "keywords": [
28
+ "ai",
29
+ "react",
30
+ "generative-ui",
31
+ "llm",
32
+ "chat",
33
+ "copilot",
34
+ "hanzo",
35
+ "mcp",
36
+ "model-context-protocol"
37
+ ],
30
38
  "scripts": {
31
- "audit": "node_modules/.bin/source-map-explorer dist/index.umd.js",
32
- "prepare": "install-peers && npm run build",
33
- "clean": "rm -rf dist",
34
- "build": "npm run clean && node_modules/.bin/rollup -c",
35
- "build:watch": "node_modules/.bin/rollup -c -w",
36
- "watch": "node_modules/.bin/concurrently 'npm run build:watch' 'node_modules/.bin/serve -l tcp://127.0.0.1'",
37
- "test": "node_modules/.bin/jest && npm run build",
38
- "test:coverage": "node_modules/.bin/jest --collect-coverage",
39
- "storybook": "start-storybook -p 6006",
40
- "build-storybook": "build-storybook"
41
- },
42
- "bugs": {
43
- "url": "https://github.com/hanzoai/hanzo/issues"
44
- },
45
- "devDependencies": {
46
- "@babel/core": "7.11.1",
47
- "@babel/plugin-proposal-class-properties": "7.10.4",
48
- "@babel/plugin-proposal-decorators": "7.10.5",
49
- "@babel/plugin-transform-react-jsx": "7.10.4",
50
- "@babel/preset-env": "7.11.0",
51
- "@storybook/addon-actions": "^5.3.19",
52
- "@storybook/addon-links": "^5.3.19",
53
- "@storybook/addons": "^5.3.19",
54
- "@storybook/react": "^5.3.19",
55
- "autoprefixer": "9.8.6",
56
- "babel-core": "7.0.0-bridge.0",
57
- "babel-jest": "26.2.2",
58
- "babel-loader": "^8.1.0",
59
- "babel-plugin-transform-react-pug": "7.0.1",
60
- "concurrently": "5.3.0",
61
- "install-peers-cli": "^2.2.0",
62
- "jest": "26.2.2",
63
- "node-eval": "2.0.0",
64
- "postcss-assets": "5.0.0",
65
- "postcss-url": "8.0.0",
66
- "rollup": "2.23.1",
67
- "rollup-plugin-babel": "4.4.0",
68
- "rollup-plugin-commonjs": "10.1.0",
69
- "rollup-plugin-filesize": "9.0.2",
70
- "rollup-plugin-json": "4.0.0",
71
- "rollup-plugin-node-resolve": "5.2.0",
72
- "rollup-plugin-peer-deps-external": "2.2.3",
73
- "rollup-plugin-postcss": "3.1.4",
74
- "rollup-plugin-visualizer": "4.1.0",
75
- "serve": "11.3.2",
76
- "source-map-explorer": "2.4.2",
77
- "styled-jsx": "3.3.0",
78
- "styled-jsx-plugin-stylus": "0.0.4"
39
+ "build": "tsup",
40
+ "dev": "tsup --watch",
41
+ "test": "vitest",
42
+ "test:coverage": "vitest --coverage",
43
+ "typecheck": "tsc --noEmit",
44
+ "lint": "eslint src",
45
+ "clean": "rm -rf dist"
79
46
  },
80
- "renovate": {
81
- "extends": [
82
- "config:js-lib"
83
- ],
84
- "automerge": true,
85
- "major": {
86
- "automerge": false
47
+ "exports": {
48
+ ".": {
49
+ "types": "./dist/index.d.ts",
50
+ "import": "./dist/index.mjs",
51
+ "require": "./dist/index.js"
87
52
  },
88
- "automergeType": "branch"
53
+ "./hooks": {
54
+ "types": "./dist/hooks/index.d.ts",
55
+ "import": "./dist/hooks/index.mjs",
56
+ "require": "./dist/hooks/index.js"
57
+ },
58
+ "./components": {
59
+ "types": "./dist/components/index.d.ts",
60
+ "import": "./dist/components/index.mjs",
61
+ "require": "./dist/components/index.js"
62
+ },
63
+ "./tools": {
64
+ "types": "./dist/tools/index.d.ts",
65
+ "import": "./dist/tools/index.mjs",
66
+ "require": "./dist/tools/index.js"
67
+ },
68
+ "./mcp": {
69
+ "types": "./dist/mcp/index.d.ts",
70
+ "import": "./dist/mcp/index.mjs",
71
+ "require": "./dist/mcp/index.js"
72
+ }
89
73
  },
90
- "browserslist": "> 0.25%, not dead",
91
74
  "dependencies": {
92
- "@date-io/moment": "2.8.0",
93
- "@hanzo/utils": "^0.1.2",
94
- "@material-ui/pickers": "3.2.10",
95
- "classnames": "2.2.6",
96
- "fast-memoize": "2.5.2",
97
- "flag-icon-css": "3.5.0",
98
- "raf": "^3.4.1",
99
- "react-number-format": "4.4.1"
75
+ "@modelcontextprotocol/sdk": "^1.10.2",
76
+ "clsx": "^2.1.1",
77
+ "nanoid": "^5.0.0",
78
+ "openai": "^4.0.0",
79
+ "swr": "^2.3.0",
80
+ "tailwind-merge": "^3.3.1",
81
+ "zod": "^3.25.0",
82
+ "zustand": "^5.0.0"
100
83
  },
101
84
  "peerDependencies": {
102
- "@material-ui/core": "^4.11.0",
103
- "moment-timezone": "^0.5.31",
104
- "react": "^16.13.1",
105
- "react-dom": "^16.13.1"
85
+ "react": "^18.0.0 || ^19.0.0",
86
+ "react-dom": "^18.0.0 || ^19.0.0"
106
87
  },
107
- "gitHead": "3274694b7821dac896bd13f3bd7fc347c745ec9b"
108
- }
88
+ "devDependencies": {
89
+ "@testing-library/react": "^16.3.0",
90
+ "@types/node": "^20.14.15",
91
+ "@types/react": "^18.3.1",
92
+ "@types/react-dom": "^18.3.1",
93
+ "@vitest/coverage-v8": "^4.0.6",
94
+ "@vitest/ui": "^4.0.6",
95
+ "react": "^18.3.1",
96
+ "react-dom": "^18.3.1",
97
+ "tsup": "^8.5.0",
98
+ "typescript": "^5.6.3",
99
+ "vitest": "^4.0.6"
100
+ }
101
+ }
@@ -0,0 +1,346 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { render, screen, waitFor } from '@testing-library/react'
3
+ import userEvent from '@testing-library/user-event'
4
+ import React from 'react'
5
+ import { HanzoProvider, useHanzo } from './HanzoProvider'
6
+ import { z } from 'zod'
7
+
8
+ // Mock fetch
9
+ global.fetch = vi.fn()
10
+
11
+ describe('HanzoProvider', () => {
12
+ beforeEach(() => {
13
+ vi.clearAllMocks()
14
+ })
15
+
16
+ it('should provide context to children', () => {
17
+ const TestComponent = () => {
18
+ const context = useHanzo()
19
+ return <div>Context: {context ? 'Available' : 'Not Available'}</div>
20
+ }
21
+
22
+ render(
23
+ <HanzoProvider apiKey="test-key">
24
+ <TestComponent />
25
+ </HanzoProvider>
26
+ )
27
+
28
+ expect(screen.getByText('Context: Available')).toBeInTheDocument()
29
+ })
30
+
31
+ it('should throw error when useHanzo is used outside provider', () => {
32
+ const TestComponent = () => {
33
+ const context = useHanzo()
34
+ return <div>{context}</div>
35
+ }
36
+
37
+ // Suppress console.error for this test
38
+ const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
39
+
40
+ expect(() => render(<TestComponent />)).toThrow(
41
+ 'useHanzo must be used within HanzoProvider'
42
+ )
43
+
44
+ spy.mockRestore()
45
+ })
46
+
47
+ it('should initialize with default thread', async () => {
48
+ const TestComponent = () => {
49
+ const { threads, activeThreadId } = useHanzo()
50
+ return (
51
+ <div>
52
+ <div>Threads: {threads.size}</div>
53
+ <div>Active: {activeThreadId || 'none'}</div>
54
+ </div>
55
+ )
56
+ }
57
+
58
+ render(
59
+ <HanzoProvider apiKey="test-key">
60
+ <TestComponent />
61
+ </HanzoProvider>
62
+ )
63
+
64
+ await waitFor(() => {
65
+ expect(screen.getByText(/Threads: 1/)).toBeInTheDocument()
66
+ expect(screen.getByText(/Active: /)).toBeInTheDocument()
67
+ })
68
+ })
69
+
70
+ it('should register components', () => {
71
+ const TestComponent = () => <div>Test Component</div>
72
+ const components = [
73
+ {
74
+ name: 'test',
75
+ component: TestComponent,
76
+ description: 'Test component'
77
+ }
78
+ ]
79
+
80
+ const ConsumerComponent = () => {
81
+ const { components: registeredComponents } = useHanzo()
82
+ return <div>Components: {registeredComponents.size}</div>
83
+ }
84
+
85
+ render(
86
+ <HanzoProvider apiKey="test-key" components={components}>
87
+ <ConsumerComponent />
88
+ </HanzoProvider>
89
+ )
90
+
91
+ expect(screen.getByText('Components: 1')).toBeInTheDocument()
92
+ })
93
+
94
+ it('should register tools', () => {
95
+ const tools = [
96
+ {
97
+ name: 'calculator',
98
+ description: 'Calculate math',
99
+ parameters: z.object({ expression: z.string() }),
100
+ execute: async ({ expression }: any) => ({ result: eval(expression) })
101
+ }
102
+ ]
103
+
104
+ const ConsumerComponent = () => {
105
+ const { tools: registeredTools } = useHanzo()
106
+ return <div>Tools: {registeredTools.size}</div>
107
+ }
108
+
109
+ render(
110
+ <HanzoProvider apiKey="test-key" tools={tools}>
111
+ <ConsumerComponent />
112
+ </HanzoProvider>
113
+ )
114
+
115
+ expect(screen.getByText('Tools: 1')).toBeInTheDocument()
116
+ })
117
+
118
+ it('should send message to API', async () => {
119
+ const mockResponse = {
120
+ choices: [{
121
+ message: {
122
+ content: 'AI response'
123
+ }
124
+ }]
125
+ }
126
+
127
+ ;(global.fetch as any).mockResolvedValueOnce({
128
+ ok: true,
129
+ json: async () => mockResponse
130
+ })
131
+
132
+ const TestComponent = () => {
133
+ const { sendMessage } = useHanzo()
134
+ const [response, setResponse] = React.useState<any>(null)
135
+
136
+ const handleSend = async () => {
137
+ const msg = await sendMessage('Hello')
138
+ setResponse(msg)
139
+ }
140
+
141
+ return (
142
+ <div>
143
+ <button onClick={handleSend}>Send</button>
144
+ {response && <div>Response: {response.content}</div>}
145
+ </div>
146
+ )
147
+ }
148
+
149
+ render(
150
+ <HanzoProvider apiKey="test-key">
151
+ <TestComponent />
152
+ </HanzoProvider>
153
+ )
154
+
155
+ const button = screen.getByText('Send')
156
+ await userEvent.click(button)
157
+
158
+ await waitFor(() => {
159
+ expect(screen.getByText('Response: AI response')).toBeInTheDocument()
160
+ })
161
+
162
+ expect(global.fetch).toHaveBeenCalledWith(
163
+ 'https://api.hanzo.ai/v1/chat/completions',
164
+ expect.objectContaining({
165
+ method: 'POST',
166
+ headers: expect.objectContaining({
167
+ 'Content-Type': 'application/json',
168
+ 'Authorization': 'Bearer test-key'
169
+ })
170
+ })
171
+ )
172
+ })
173
+
174
+ it('should handle API errors', async () => {
175
+ ;(global.fetch as any).mockRejectedValueOnce(new Error('Network error'))
176
+
177
+ const onError = vi.fn()
178
+
179
+ const TestComponent = () => {
180
+ const { sendMessage } = useHanzo()
181
+ const [error, setError] = React.useState<any>(null)
182
+
183
+ const handleSend = async () => {
184
+ try {
185
+ await sendMessage('Hello')
186
+ } catch (err) {
187
+ setError(err)
188
+ }
189
+ }
190
+
191
+ return (
192
+ <div>
193
+ <button onClick={handleSend}>Send</button>
194
+ {error && <div>Error: {error.message}</div>}
195
+ </div>
196
+ )
197
+ }
198
+
199
+ render(
200
+ <HanzoProvider apiKey="test-key" onError={onError}>
201
+ <TestComponent />
202
+ </HanzoProvider>
203
+ )
204
+
205
+ const button = screen.getByText('Send')
206
+ await userEvent.click(button)
207
+
208
+ await waitFor(() => {
209
+ expect(screen.getByText('Error: Network error')).toBeInTheDocument()
210
+ })
211
+
212
+ expect(onError).toHaveBeenCalledWith(expect.any(Error))
213
+ })
214
+
215
+ it('should execute tools', async () => {
216
+ const mockExecute = vi.fn().mockResolvedValue({ result: 42 })
217
+
218
+ const tools = [
219
+ {
220
+ name: 'calculator',
221
+ description: 'Calculate',
222
+ parameters: z.object({ expression: z.string() }),
223
+ execute: mockExecute
224
+ }
225
+ ]
226
+
227
+ const TestComponent = () => {
228
+ const { executeTool } = useHanzo()
229
+ const [result, setResult] = React.useState<any>(null)
230
+
231
+ const handleExecute = async () => {
232
+ const res = await executeTool('calculator', { expression: '2+2' })
233
+ setResult(res)
234
+ }
235
+
236
+ return (
237
+ <div>
238
+ <button onClick={handleExecute}>Execute</button>
239
+ {result && <div>Result: {result.result}</div>}
240
+ </div>
241
+ )
242
+ }
243
+
244
+ render(
245
+ <HanzoProvider apiKey="test-key" tools={tools}>
246
+ <TestComponent />
247
+ </HanzoProvider>
248
+ )
249
+
250
+ const button = screen.getByText('Execute')
251
+ await userEvent.click(button)
252
+
253
+ await waitFor(() => {
254
+ expect(screen.getByText('Result: 42')).toBeInTheDocument()
255
+ })
256
+
257
+ expect(mockExecute).toHaveBeenCalledWith({ expression: '2+2' })
258
+ })
259
+
260
+ it('should render components dynamically', () => {
261
+ const CustomComponent = ({ text }: { text: string }) => (
262
+ <div>Custom: {text}</div>
263
+ )
264
+
265
+ const components = [
266
+ {
267
+ name: 'custom',
268
+ component: CustomComponent,
269
+ description: 'Custom component'
270
+ }
271
+ ]
272
+
273
+ const TestComponent = () => {
274
+ const { renderComponent } = useHanzo()
275
+ const rendered = renderComponent('custom', { text: 'Hello' })
276
+
277
+ return <div>{rendered}</div>
278
+ }
279
+
280
+ render(
281
+ <HanzoProvider apiKey="test-key" components={components}>
282
+ <TestComponent />
283
+ </HanzoProvider>
284
+ )
285
+
286
+ expect(screen.getByText('Custom: Hello')).toBeInTheDocument()
287
+ })
288
+
289
+ it('should manage thread operations', async () => {
290
+ const TestComponent = () => {
291
+ const {
292
+ threads,
293
+ createThread,
294
+ switchThread,
295
+ deleteThread,
296
+ activeThreadId
297
+ } = useHanzo()
298
+
299
+ const handleCreate = () => {
300
+ const thread = createThread({ name: 'New Thread' })
301
+ switchThread(thread.id)
302
+ }
303
+
304
+ const handleDelete = () => {
305
+ if (activeThreadId) {
306
+ deleteThread(activeThreadId)
307
+ }
308
+ }
309
+
310
+ return (
311
+ <div>
312
+ <div>Thread Count: {threads.size}</div>
313
+ <button onClick={handleCreate}>Create Thread</button>
314
+ <button onClick={handleDelete}>Delete Thread</button>
315
+ </div>
316
+ )
317
+ }
318
+
319
+ render(
320
+ <HanzoProvider apiKey="test-key">
321
+ <TestComponent />
322
+ </HanzoProvider>
323
+ )
324
+
325
+ // Initially should have 1 thread
326
+ await waitFor(() => {
327
+ expect(screen.getByText('Thread Count: 1')).toBeInTheDocument()
328
+ })
329
+
330
+ // Create a new thread
331
+ const createButton = screen.getByText('Create Thread')
332
+ await userEvent.click(createButton)
333
+
334
+ await waitFor(() => {
335
+ expect(screen.getByText('Thread Count: 2')).toBeInTheDocument()
336
+ })
337
+
338
+ // Delete current thread
339
+ const deleteButton = screen.getByText('Delete Thread')
340
+ await userEvent.click(deleteButton)
341
+
342
+ await waitFor(() => {
343
+ expect(screen.getByText('Thread Count: 1')).toBeInTheDocument()
344
+ })
345
+ })
346
+ })