@tambo-ai/react 0.12.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 (158) hide show
  1. package/README.md +9 -0
  2. package/dist/hooks/__tests__/use-suggestions.test.d.ts +1 -0
  3. package/dist/hooks/__tests__/use-suggestions.test.js +167 -0
  4. package/dist/hooks/__tests__/use-suggestions.test.js.map +1 -0
  5. package/dist/hooks/react-query-hooks.d.ts +21 -0
  6. package/dist/hooks/react-query-hooks.js +33 -0
  7. package/dist/hooks/react-query-hooks.js.map +1 -0
  8. package/dist/hooks/use-component-state.d.ts +8 -0
  9. package/dist/hooks/use-component-state.js +42 -0
  10. package/dist/hooks/use-component-state.js.map +1 -0
  11. package/dist/hooks/use-current-message.d.ts +18 -0
  12. package/dist/hooks/use-current-message.js +73 -0
  13. package/dist/hooks/use-current-message.js.map +1 -0
  14. package/dist/hooks/use-query-client.d.ts +0 -0
  15. package/dist/hooks/use-query-client.js +2 -0
  16. package/dist/hooks/use-query-client.js.map +1 -0
  17. package/dist/hooks/use-suggestions.d.ts +44 -0
  18. package/dist/hooks/use-suggestions.js +111 -0
  19. package/dist/hooks/use-suggestions.js.map +1 -0
  20. package/dist/hooks/use-tambo-threads.d.ts +137 -0
  21. package/dist/hooks/use-tambo-threads.js +33 -0
  22. package/dist/hooks/use-tambo-threads.js.map +1 -0
  23. package/dist/hooks/use-thread-input.d.ts +48 -0
  24. package/dist/hooks/use-thread-input.js +53 -0
  25. package/dist/hooks/use-thread-input.js.map +1 -0
  26. package/dist/index.d.ts +12 -0
  27. package/dist/index.js +34 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/model/component-metadata.d.ts +32 -0
  30. package/dist/model/component-metadata.js +3 -0
  31. package/dist/model/component-metadata.js.map +1 -0
  32. package/dist/model/generate-component-response.d.ts +17 -0
  33. package/dist/model/generate-component-response.js +22 -0
  34. package/dist/model/generate-component-response.js.map +1 -0
  35. package/dist/model/tambo-thread.d.ts +7 -0
  36. package/dist/model/tambo-thread.js +3 -0
  37. package/dist/model/tambo-thread.js.map +1 -0
  38. package/dist/model/thread-input-error.d.ts +3 -0
  39. package/dist/model/thread-input-error.js +8 -0
  40. package/dist/model/thread-input-error.js.map +1 -0
  41. package/dist/model/validate-input.d.ts +6 -0
  42. package/dist/model/validate-input.js +27 -0
  43. package/dist/model/validate-input.js.map +1 -0
  44. package/dist/providers/index.d.ts +5 -0
  45. package/dist/providers/index.js +21 -0
  46. package/dist/providers/index.js.map +1 -0
  47. package/dist/providers/tambo-client-provider.d.ts +15 -0
  48. package/dist/providers/tambo-client-provider.js +66 -0
  49. package/dist/providers/tambo-client-provider.js.map +1 -0
  50. package/dist/providers/tambo-component-provider.d.ts +18 -0
  51. package/dist/providers/tambo-component-provider.js +129 -0
  52. package/dist/providers/tambo-component-provider.js.map +1 -0
  53. package/dist/providers/tambo-provider.d.ts +10 -0
  54. package/dist/providers/tambo-provider.js +70 -0
  55. package/dist/providers/tambo-provider.js.map +1 -0
  56. package/dist/providers/tambo-registry-provider.d.ts +21 -0
  57. package/dist/providers/tambo-registry-provider.js +117 -0
  58. package/dist/providers/tambo-registry-provider.js.map +1 -0
  59. package/dist/providers/tambo-thread-provider.d.ts +28 -0
  60. package/dist/providers/tambo-thread-provider.js +417 -0
  61. package/dist/providers/tambo-thread-provider.js.map +1 -0
  62. package/dist/setupTests.d.ts +1 -0
  63. package/dist/setupTests.js +20 -0
  64. package/dist/setupTests.js.map +1 -0
  65. package/dist/util/generate-component.d.ts +8 -0
  66. package/dist/util/generate-component.js +209 -0
  67. package/dist/util/generate-component.js.map +1 -0
  68. package/dist/util/messages.d.ts +2 -0
  69. package/dist/util/messages.js +12 -0
  70. package/dist/util/messages.js.map +1 -0
  71. package/dist/util/query-utils.d.ts +5 -0
  72. package/dist/util/query-utils.js +63 -0
  73. package/dist/util/query-utils.js.map +1 -0
  74. package/dist/util/registry.d.ts +7 -0
  75. package/dist/util/registry.js +99 -0
  76. package/dist/util/registry.js.map +1 -0
  77. package/dist/util/tool-caller.d.ts +3 -0
  78. package/dist/util/tool-caller.js +30 -0
  79. package/dist/util/tool-caller.js.map +1 -0
  80. package/esm/hooks/__tests__/use-suggestions.test.d.ts +1 -0
  81. package/esm/hooks/__tests__/use-suggestions.test.js +165 -0
  82. package/esm/hooks/__tests__/use-suggestions.test.js.map +1 -0
  83. package/esm/hooks/react-query-hooks.d.ts +21 -0
  84. package/esm/hooks/react-query-hooks.js +28 -0
  85. package/esm/hooks/react-query-hooks.js.map +1 -0
  86. package/esm/hooks/use-component-state.d.ts +8 -0
  87. package/esm/hooks/use-component-state.js +39 -0
  88. package/esm/hooks/use-component-state.js.map +1 -0
  89. package/esm/hooks/use-current-message.d.ts +18 -0
  90. package/esm/hooks/use-current-message.js +33 -0
  91. package/esm/hooks/use-current-message.js.map +1 -0
  92. package/esm/hooks/use-query-client.d.ts +0 -0
  93. package/esm/hooks/use-query-client.js +2 -0
  94. package/esm/hooks/use-query-client.js.map +1 -0
  95. package/esm/hooks/use-suggestions.d.ts +44 -0
  96. package/esm/hooks/use-suggestions.js +108 -0
  97. package/esm/hooks/use-suggestions.js.map +1 -0
  98. package/esm/hooks/use-tambo-threads.d.ts +137 -0
  99. package/esm/hooks/use-tambo-threads.js +30 -0
  100. package/esm/hooks/use-tambo-threads.js.map +1 -0
  101. package/esm/hooks/use-thread-input.d.ts +48 -0
  102. package/esm/hooks/use-thread-input.js +49 -0
  103. package/esm/hooks/use-thread-input.js.map +1 -0
  104. package/esm/index.d.ts +12 -0
  105. package/esm/index.js +10 -0
  106. package/esm/index.js.map +1 -0
  107. package/esm/model/component-metadata.d.ts +32 -0
  108. package/esm/model/component-metadata.js +2 -0
  109. package/esm/model/component-metadata.js.map +1 -0
  110. package/esm/model/generate-component-response.d.ts +17 -0
  111. package/esm/model/generate-component-response.js +18 -0
  112. package/esm/model/generate-component-response.js.map +1 -0
  113. package/esm/model/tambo-thread.d.ts +7 -0
  114. package/esm/model/tambo-thread.js +2 -0
  115. package/esm/model/tambo-thread.js.map +1 -0
  116. package/esm/model/thread-input-error.d.ts +3 -0
  117. package/esm/model/thread-input-error.js +4 -0
  118. package/esm/model/thread-input-error.js.map +1 -0
  119. package/esm/model/validate-input.d.ts +6 -0
  120. package/esm/model/validate-input.js +24 -0
  121. package/esm/model/validate-input.js.map +1 -0
  122. package/esm/providers/index.d.ts +5 -0
  123. package/esm/providers/index.js +6 -0
  124. package/esm/providers/index.js.map +1 -0
  125. package/esm/providers/tambo-client-provider.d.ts +15 -0
  126. package/esm/providers/tambo-client-provider.js +24 -0
  127. package/esm/providers/tambo-client-provider.js.map +1 -0
  128. package/esm/providers/tambo-component-provider.d.ts +18 -0
  129. package/esm/providers/tambo-component-provider.js +91 -0
  130. package/esm/providers/tambo-component-provider.js.map +1 -0
  131. package/esm/providers/tambo-provider.d.ts +10 -0
  132. package/esm/providers/tambo-provider.js +32 -0
  133. package/esm/providers/tambo-provider.js.map +1 -0
  134. package/esm/providers/tambo-registry-provider.d.ts +21 -0
  135. package/esm/providers/tambo-registry-provider.js +79 -0
  136. package/esm/providers/tambo-registry-provider.js.map +1 -0
  137. package/esm/providers/tambo-thread-provider.d.ts +28 -0
  138. package/esm/providers/tambo-thread-provider.js +379 -0
  139. package/esm/providers/tambo-thread-provider.js.map +1 -0
  140. package/esm/setupTests.d.ts +1 -0
  141. package/esm/setupTests.js +18 -0
  142. package/esm/setupTests.js.map +1 -0
  143. package/esm/util/generate-component.d.ts +8 -0
  144. package/esm/util/generate-component.js +202 -0
  145. package/esm/util/generate-component.js.map +1 -0
  146. package/esm/util/messages.d.ts +2 -0
  147. package/esm/util/messages.js +9 -0
  148. package/esm/util/messages.js.map +1 -0
  149. package/esm/util/query-utils.d.ts +5 -0
  150. package/esm/util/query-utils.js +59 -0
  151. package/esm/util/query-utils.js.map +1 -0
  152. package/esm/util/registry.d.ts +7 -0
  153. package/esm/util/registry.js +88 -0
  154. package/esm/util/registry.js.map +1 -0
  155. package/esm/util/tool-caller.d.ts +3 -0
  156. package/esm/util/tool-caller.js +26 -0
  157. package/esm/util/tool-caller.js.map +1 -0
  158. package/package.json +84 -0
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # Tambo AI React Client
2
+
3
+ ## Development
4
+
5
+ ```bash
6
+ npm install @tambo-ai/react
7
+ ```
8
+
9
+ PRs should use Conventional Commits.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const react_query_1 = require("@tanstack/react-query");
4
+ const react_1 = require("@testing-library/react");
5
+ const tambo_client_provider_1 = require("../../providers/tambo-client-provider");
6
+ const tambo_provider_1 = require("../../providers/tambo-provider");
7
+ const tambo_thread_provider_1 = require("../../providers/tambo-thread-provider");
8
+ const react_query_hooks_1 = require("../react-query-hooks");
9
+ const use_suggestions_1 = require("../use-suggestions");
10
+ const use_thread_input_1 = require("../use-thread-input");
11
+ // Mock the required providers
12
+ jest.mock("../../providers/tambo-client-provider", () => ({
13
+ useTamboClient: jest.fn(),
14
+ useTamboQueryClient: jest.fn().mockReturnValue(new react_query_1.QueryClient()),
15
+ }));
16
+ jest.mock("../../providers/tambo-provider", () => ({ useTambo: jest.fn() }));
17
+ jest.mock("../../providers/tambo-thread-provider", () => ({
18
+ useTamboThread: jest.fn(),
19
+ }));
20
+ jest.mock("../use-thread-input", () => ({ useTamboThreadInput: jest.fn() }));
21
+ // Mock the react-query-hooks
22
+ jest.mock("../react-query-hooks", () => ({
23
+ useTamboQuery: jest.fn(),
24
+ useTamboMutationResult: jest.fn().mockImplementation(({ mutationFn }) => ({
25
+ mutateAsync: mutationFn,
26
+ isLoading: false,
27
+ isError: false,
28
+ error: null,
29
+ })),
30
+ }));
31
+ describe("useTamboSuggestions", () => {
32
+ const mockSuggestions = [
33
+ {
34
+ id: "suggestion-1",
35
+ messageId: "test-message-id",
36
+ title: "Test Suggestion 1",
37
+ detailedSuggestion: "Test suggestion 1",
38
+ },
39
+ {
40
+ id: "suggestion-2",
41
+ messageId: "test-message-id",
42
+ title: "Test Suggestion 2",
43
+ detailedSuggestion: "Test suggestion 2",
44
+ },
45
+ ];
46
+ beforeEach(() => {
47
+ jest.clearAllMocks();
48
+ // Setup default mock implementations
49
+ tambo_client_provider_1.useTamboClient.mockReturnValue({
50
+ beta: { threads: { suggestions: { generate: jest.fn() } } },
51
+ });
52
+ tambo_provider_1.useTambo.mockReturnValue({ sendThreadMessage: jest.fn() });
53
+ tambo_thread_provider_1.useTamboThread.mockReturnValue({
54
+ thread: {
55
+ id: "test-thread-id",
56
+ messages: [
57
+ { id: "test-message-id", role: "hydra", content: "Test message" },
58
+ ],
59
+ },
60
+ });
61
+ use_thread_input_1.useTamboThreadInput.mockReturnValue({ setValue: jest.fn() });
62
+ // Default query mock returns empty array
63
+ react_query_hooks_1.useTamboQuery.mockReturnValue({
64
+ data: [],
65
+ isLoading: false,
66
+ isError: false,
67
+ error: null,
68
+ });
69
+ });
70
+ it("should initialize with empty suggestions and no selected suggestion", () => {
71
+ const { result } = (0, react_1.renderHook)(() => (0, use_suggestions_1.useTamboSuggestions)());
72
+ expect(result.current.suggestions).toEqual([]);
73
+ expect(result.current.selectedSuggestionId).toBeNull();
74
+ });
75
+ it("should generate suggestions when latest message is from Tambo", async () => {
76
+ const mockGenerate = jest.fn().mockResolvedValue(mockSuggestions);
77
+ tambo_client_provider_1.useTamboClient.mockReturnValue({
78
+ beta: { threads: { suggestions: { generate: mockGenerate } } },
79
+ });
80
+ // Mock the query result to return the mock suggestions
81
+ react_query_hooks_1.useTamboQuery.mockReturnValue({
82
+ data: mockSuggestions,
83
+ isLoading: false,
84
+ isError: false,
85
+ error: null,
86
+ });
87
+ const { result } = (0, react_1.renderHook)(() => (0, use_suggestions_1.useTamboSuggestions)());
88
+ // Wait for the effect to run
89
+ await (0, react_1.act)(async () => {
90
+ await new Promise((resolve) => setTimeout(resolve, 0));
91
+ });
92
+ // Since we're mocking useTamboQuery to return the suggestions directly,
93
+ // the generate function won't be called, so we don't need to check that
94
+ expect(result.current.suggestions).toEqual(mockSuggestions);
95
+ });
96
+ it("should not generate suggestions when latest message is not from Tambo", async () => {
97
+ const mockGenerate = jest.fn();
98
+ tambo_client_provider_1.useTamboClient.mockReturnValue({
99
+ beta: { threads: { suggestions: { generate: mockGenerate } } },
100
+ });
101
+ // Mock the thread to have a non-Tambo message
102
+ tambo_thread_provider_1.useTamboThread.mockReturnValue({
103
+ thread: {
104
+ id: "test-thread-id",
105
+ messages: [
106
+ { id: "test-message-id", role: "user", content: "Test message" },
107
+ ],
108
+ },
109
+ });
110
+ const { result } = (0, react_1.renderHook)(() => (0, use_suggestions_1.useTamboSuggestions)());
111
+ // Wait for the effect to run
112
+ await (0, react_1.act)(async () => {
113
+ await new Promise((resolve) => setTimeout(resolve, 0));
114
+ });
115
+ expect(mockGenerate).not.toHaveBeenCalled();
116
+ expect(result.current.suggestions).toEqual([]);
117
+ });
118
+ it("should accept a suggestion and update input value", async () => {
119
+ const mockSetValue = jest.fn();
120
+ use_thread_input_1.useTamboThreadInput.mockReturnValue({
121
+ setValue: mockSetValue,
122
+ });
123
+ const { result } = (0, react_1.renderHook)(() => (0, use_suggestions_1.useTamboSuggestions)());
124
+ await (0, react_1.act)(async () => {
125
+ await result.current.accept({
126
+ suggestion: mockSuggestions[0],
127
+ shouldSubmit: false,
128
+ });
129
+ });
130
+ expect(mockSetValue).toHaveBeenCalledWith("Test suggestion 1");
131
+ expect(result.current.selectedSuggestionId).toBe("suggestion-1");
132
+ });
133
+ it("should accept a suggestion and submit it", async () => {
134
+ const mockSendThreadMessage = jest.fn();
135
+ tambo_provider_1.useTambo.mockReturnValue({
136
+ sendThreadMessage: mockSendThreadMessage,
137
+ });
138
+ const { result } = (0, react_1.renderHook)(() => (0, use_suggestions_1.useTamboSuggestions)());
139
+ await (0, react_1.act)(async () => {
140
+ await result.current.accept({
141
+ suggestion: mockSuggestions[0],
142
+ shouldSubmit: true,
143
+ });
144
+ });
145
+ expect(mockSendThreadMessage).toHaveBeenCalledWith("Test suggestion 1", {
146
+ threadId: "test-thread-id",
147
+ });
148
+ expect(result.current.selectedSuggestionId).toBe("suggestion-1");
149
+ });
150
+ it("should throw error when accepting invalid suggestion", async () => {
151
+ const invalidSuggestion = {
152
+ id: "invalid-suggestion",
153
+ messageId: "test-message-id",
154
+ title: "Invalid Suggestion",
155
+ detailedSuggestion: "", // Empty suggestion should fail validation
156
+ };
157
+ const { result } = (0, react_1.renderHook)(() => (0, use_suggestions_1.useTamboSuggestions)());
158
+ await (0, react_1.act)(async () => {
159
+ await expect(result.current.accept({
160
+ suggestion: invalidSuggestion,
161
+ shouldSubmit: false,
162
+ })).rejects.toThrow("Message cannot be empty");
163
+ });
164
+ expect(result.current.selectedSuggestionId).toBeNull();
165
+ });
166
+ });
167
+ //# sourceMappingURL=use-suggestions.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-suggestions.test.js","sourceRoot":"","sources":["../../../src/hooks/__tests__/use-suggestions.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import TamboAI from \"@tambo-ai/typescript-sdk\";\nimport { QueryClient } from \"@tanstack/react-query\";\nimport { act, renderHook } from \"@testing-library/react\";\nimport { useTamboClient } from \"../../providers/tambo-client-provider\";\nimport { useTambo } from \"../../providers/tambo-provider\";\nimport { useTamboThread } from \"../../providers/tambo-thread-provider\";\nimport { useTamboQuery } from \"../react-query-hooks\";\nimport { useTamboSuggestions } from \"../use-suggestions\";\nimport { useTamboThreadInput } from \"../use-thread-input\";\n\n// Mock the required providers\njest.mock(\"../../providers/tambo-client-provider\", () => ({\n useTamboClient: jest.fn(),\n useTamboQueryClient: jest.fn().mockReturnValue(new QueryClient()),\n}));\n\njest.mock(\"../../providers/tambo-provider\", () => ({ useTambo: jest.fn() }));\n\njest.mock(\"../../providers/tambo-thread-provider\", () => ({\n useTamboThread: jest.fn(),\n}));\n\njest.mock(\"../use-thread-input\", () => ({ useTamboThreadInput: jest.fn() }));\n\n// Mock the react-query-hooks\njest.mock(\"../react-query-hooks\", () => ({\n useTamboQuery: jest.fn(),\n useTamboMutationResult: jest.fn().mockImplementation(({ mutationFn }) => ({\n mutateAsync: mutationFn,\n isLoading: false,\n isError: false,\n error: null,\n })),\n}));\n\ndescribe(\"useTamboSuggestions\", () => {\n const mockSuggestions: TamboAI.Beta.Threads.Suggestion[] = [\n {\n id: \"suggestion-1\",\n messageId: \"test-message-id\",\n title: \"Test Suggestion 1\",\n detailedSuggestion: \"Test suggestion 1\",\n },\n {\n id: \"suggestion-2\",\n messageId: \"test-message-id\",\n title: \"Test Suggestion 2\",\n detailedSuggestion: \"Test suggestion 2\",\n },\n ];\n\n beforeEach(() => {\n jest.clearAllMocks();\n // Setup default mock implementations\n (useTamboClient as jest.Mock).mockReturnValue({\n beta: { threads: { suggestions: { generate: jest.fn() } } },\n });\n (useTambo as jest.Mock).mockReturnValue({ sendThreadMessage: jest.fn() });\n (useTamboThread as jest.Mock).mockReturnValue({\n thread: {\n id: \"test-thread-id\",\n messages: [\n { id: \"test-message-id\", role: \"hydra\", content: \"Test message\" },\n ],\n },\n });\n (useTamboThreadInput as jest.Mock).mockReturnValue({ setValue: jest.fn() });\n // Default query mock returns empty array\n (useTamboQuery as jest.Mock).mockReturnValue({\n data: [],\n isLoading: false,\n isError: false,\n error: null,\n });\n });\n\n it(\"should initialize with empty suggestions and no selected suggestion\", () => {\n const { result } = renderHook(() => useTamboSuggestions());\n\n expect(result.current.suggestions).toEqual([]);\n expect(result.current.selectedSuggestionId).toBeNull();\n });\n\n it(\"should generate suggestions when latest message is from Tambo\", async () => {\n const mockGenerate = jest.fn().mockResolvedValue(mockSuggestions);\n (useTamboClient as jest.Mock).mockReturnValue({\n beta: { threads: { suggestions: { generate: mockGenerate } } },\n });\n\n // Mock the query result to return the mock suggestions\n (useTamboQuery as jest.Mock).mockReturnValue({\n data: mockSuggestions,\n isLoading: false,\n isError: false,\n error: null,\n });\n\n const { result } = renderHook(() => useTamboSuggestions());\n\n // Wait for the effect to run\n await act(async () => {\n await new Promise((resolve) => setTimeout(resolve, 0));\n });\n\n // Since we're mocking useTamboQuery to return the suggestions directly,\n // the generate function won't be called, so we don't need to check that\n expect(result.current.suggestions).toEqual(mockSuggestions);\n });\n\n it(\"should not generate suggestions when latest message is not from Tambo\", async () => {\n const mockGenerate = jest.fn();\n (useTamboClient as jest.Mock).mockReturnValue({\n beta: { threads: { suggestions: { generate: mockGenerate } } },\n });\n\n // Mock the thread to have a non-Tambo message\n (useTamboThread as jest.Mock).mockReturnValue({\n thread: {\n id: \"test-thread-id\",\n messages: [\n { id: \"test-message-id\", role: \"user\", content: \"Test message\" },\n ],\n },\n });\n\n const { result } = renderHook(() => useTamboSuggestions());\n\n // Wait for the effect to run\n await act(async () => {\n await new Promise((resolve) => setTimeout(resolve, 0));\n });\n\n expect(mockGenerate).not.toHaveBeenCalled();\n expect(result.current.suggestions).toEqual([]);\n });\n\n it(\"should accept a suggestion and update input value\", async () => {\n const mockSetValue = jest.fn();\n (useTamboThreadInput as jest.Mock).mockReturnValue({\n setValue: mockSetValue,\n });\n\n const { result } = renderHook(() => useTamboSuggestions());\n\n await act(async () => {\n await result.current.accept({\n suggestion: mockSuggestions[0],\n shouldSubmit: false,\n });\n });\n\n expect(mockSetValue).toHaveBeenCalledWith(\"Test suggestion 1\");\n expect(result.current.selectedSuggestionId).toBe(\"suggestion-1\");\n });\n\n it(\"should accept a suggestion and submit it\", async () => {\n const mockSendThreadMessage = jest.fn();\n (useTambo as jest.Mock).mockReturnValue({\n sendThreadMessage: mockSendThreadMessage,\n });\n\n const { result } = renderHook(() => useTamboSuggestions());\n\n await act(async () => {\n await result.current.accept({\n suggestion: mockSuggestions[0],\n shouldSubmit: true,\n });\n });\n\n expect(mockSendThreadMessage).toHaveBeenCalledWith(\"Test suggestion 1\", {\n threadId: \"test-thread-id\",\n });\n expect(result.current.selectedSuggestionId).toBe(\"suggestion-1\");\n });\n\n it(\"should throw error when accepting invalid suggestion\", async () => {\n const invalidSuggestion = {\n id: \"invalid-suggestion\",\n messageId: \"test-message-id\",\n title: \"Invalid Suggestion\",\n detailedSuggestion: \"\", // Empty suggestion should fail validation\n };\n\n const { result } = renderHook(() => useTamboSuggestions());\n\n await act(async () => {\n await expect(\n result.current.accept({\n suggestion: invalidSuggestion,\n shouldSubmit: false,\n }),\n ).rejects.toThrow(\"Message cannot be empty\");\n });\n\n expect(result.current.selectedSuggestionId).toBeNull();\n });\n});\n"]}
@@ -0,0 +1,21 @@
1
+ import { QueryKey, UseMutationOptions, UseQueryOptions, UseMutationResult } from "@tanstack/react-query";
2
+ /**
3
+ * Wrapper around useQuery that uses the internal tambo query client.
4
+ *
5
+ * Use this instead of useQuery from @tanstack/react-query
6
+ */
7
+ export declare function useTamboQuery<TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>): import("@tanstack/react-query").UseQueryResult<TData, TError>;
8
+ /**
9
+ * Wrapper around useMutation that uses the internal tambo query client.
10
+ *
11
+ * Use this instead of useMutation from @tanstack/react-query
12
+ */
13
+ export declare function useTamboMutation<TData = unknown, TError = Error, TVariables = void, TContext = unknown>(options: UseMutationOptions<TData, TError, TVariables, TContext>): UseMutationResult<TData, TError, TVariables, TContext>;
14
+ /**
15
+ * Type alias for the result of a mutation.
16
+ */
17
+ export type UseTamboMutationResult<TData = unknown, TError = Error, TVariables = void, TContext = unknown> = UseMutationResult<TData, TError, TVariables, TContext>;
18
+ /**
19
+ * Hook for creating a mutation with the tambo query client.
20
+ */
21
+ export declare function useTamboMutationResult<TData = unknown, TError = Error, TVariables = void, TContext = unknown>(options: UseMutationOptions<TData, TError, TVariables, TContext>): UseMutationResult<TData, TError, TVariables, TContext>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useTamboQuery = useTamboQuery;
4
+ exports.useTamboMutation = useTamboMutation;
5
+ exports.useTamboMutationResult = useTamboMutationResult;
6
+ // tamboHooks.ts
7
+ const react_query_1 = require("@tanstack/react-query");
8
+ const tambo_client_provider_1 = require("../providers/tambo-client-provider");
9
+ /**
10
+ * Wrapper around useQuery that uses the internal tambo query client.
11
+ *
12
+ * Use this instead of useQuery from @tanstack/react-query
13
+ */
14
+ function useTamboQuery(options) {
15
+ const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
16
+ return (0, react_query_1.useQuery)(options, queryClient);
17
+ }
18
+ /**
19
+ * Wrapper around useMutation that uses the internal tambo query client.
20
+ *
21
+ * Use this instead of useMutation from @tanstack/react-query
22
+ */
23
+ function useTamboMutation(options) {
24
+ const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
25
+ return (0, react_query_1.useMutation)(options, queryClient);
26
+ }
27
+ /**
28
+ * Hook for creating a mutation with the tambo query client.
29
+ */
30
+ function useTamboMutationResult(options) {
31
+ return useTamboMutation(options);
32
+ }
33
+ //# sourceMappingURL=react-query-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-query-hooks.js","sourceRoot":"","sources":["../../src/hooks/react-query-hooks.ts"],"names":[],"mappings":";;AAgBA,sCAQC;AAOD,4CAQC;AAeD,wDAOC;AA7DD,gBAAgB;AAChB,uDAO+B;AAC/B,8EAAyE;AAEzE;;;;GAIG;AACH,SAAgB,aAAa,CAK3B,OAAgE;IAChE,MAAM,WAAW,GAAG,IAAA,2CAAmB,GAAE,CAAC;IAC1C,OAAO,IAAA,sBAAQ,EAAC,OAAO,EAAE,WAAW,CAAC,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAK9B,OAAgE;IAChE,MAAM,WAAW,GAAG,IAAA,2CAAmB,GAAE,CAAC;IAC1C,OAAO,IAAA,yBAAW,EAAC,OAAO,EAAE,WAAW,CAAC,CAAC;AAC3C,CAAC;AAYD;;GAEG;AACH,SAAgB,sBAAsB,CAKpC,OAAgE;IAChE,OAAO,gBAAgB,CAAsC,OAAO,CAAC,CAAC;AACxE,CAAC","sourcesContent":["// tamboHooks.ts\nimport {\n QueryKey,\n useMutation,\n UseMutationOptions,\n useQuery,\n UseQueryOptions,\n UseMutationResult,\n} from \"@tanstack/react-query\";\nimport { useTamboQueryClient } from \"../providers/tambo-client-provider\";\n\n/**\n * Wrapper around useQuery that uses the internal tambo query client.\n *\n * Use this instead of useQuery from @tanstack/react-query\n */\nexport function useTamboQuery<\n TQueryFnData = unknown,\n TError = Error,\n TData = TQueryFnData,\n TQueryKey extends QueryKey = QueryKey,\n>(options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>) {\n const queryClient = useTamboQueryClient();\n return useQuery(options, queryClient);\n}\n\n/**\n * Wrapper around useMutation that uses the internal tambo query client.\n *\n * Use this instead of useMutation from @tanstack/react-query\n */\nexport function useTamboMutation<\n TData = unknown,\n TError = Error,\n TVariables = void,\n TContext = unknown,\n>(options: UseMutationOptions<TData, TError, TVariables, TContext>) {\n const queryClient = useTamboQueryClient();\n return useMutation(options, queryClient);\n}\n\n/**\n * Type alias for the result of a mutation.\n */\nexport type UseTamboMutationResult<\n TData = unknown,\n TError = Error,\n TVariables = void,\n TContext = unknown,\n> = UseMutationResult<TData, TError, TVariables, TContext>;\n\n/**\n * Hook for creating a mutation with the tambo query client.\n */\nexport function useTamboMutationResult<\n TData = unknown,\n TError = Error,\n TVariables = void,\n TContext = unknown,\n>(options: UseMutationOptions<TData, TError, TVariables, TContext>) {\n return useTamboMutation<TData, TError, TVariables, TContext>(options);\n}\n"]}
@@ -0,0 +1,8 @@
1
+ type StateUpdateResult<T> = [currentState: T, setState: (newState: T) => void];
2
+ /**
3
+ * Behaves similarly to useState, but the value is stored in the thread
4
+ * message, and the state is keyed by the keyName
5
+ */
6
+ export declare function useTamboComponentState<S = undefined>(keyName: string): StateUpdateResult<S | undefined>;
7
+ export declare function useTamboComponentState<S>(keyName: string, initialValue?: S): StateUpdateResult<S>;
8
+ export {};
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useTamboComponentState = useTamboComponentState;
4
+ const react_1 = require("react");
5
+ const providers_1 = require("../providers");
6
+ const use_current_message_1 = require("./use-current-message");
7
+ function useTamboComponentState(keyName, initialValue) {
8
+ const { threadId, messageId } = (0, use_current_message_1.useTamboMessageContext)();
9
+ const { updateThreadMessage } = (0, providers_1.useTamboThread)();
10
+ const client = (0, providers_1.useTamboClient)();
11
+ const message = (0, use_current_message_1.useTamboCurrentMessage)();
12
+ if (!message) {
13
+ // throw new Error(`Message not found ${messageId}`);
14
+ console.warn(`Message not found ${messageId}`);
15
+ }
16
+ const value = message?.componentState && keyName in message.componentState
17
+ ? message.componentState[keyName]
18
+ : initialValue;
19
+ const setValue = (0, react_1.useCallback)(async (newValue) => {
20
+ if (!message) {
21
+ console.warn(`Cannot update missing message ${messageId}`);
22
+ return;
23
+ }
24
+ await updateThreadMessage(messageId, {
25
+ ...message,
26
+ componentState: {
27
+ ...message.componentState,
28
+ [keyName]: newValue,
29
+ },
30
+ }, false);
31
+ await client.beta.threads.messages.updateComponentState(threadId, messageId, { state: { [keyName]: newValue } });
32
+ }, [
33
+ client.beta.threads.messages,
34
+ keyName,
35
+ message,
36
+ messageId,
37
+ threadId,
38
+ updateThreadMessage,
39
+ ]);
40
+ return [value, setValue];
41
+ }
42
+ //# sourceMappingURL=use-component-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-component-state.js","sourceRoot":"","sources":["../../src/hooks/use-component-state.tsx"],"names":[],"mappings":";;AAoBA,wDAkDC;AAtED,iCAAoC;AACpC,4CAA8D;AAC9D,+DAG+B;AAe/B,SAAgB,sBAAsB,CACpC,OAAe,EACf,YAAgB;IAEhB,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAA,4CAAsB,GAAE,CAAC;IACzD,MAAM,EAAE,mBAAmB,EAAE,GAAG,IAAA,0BAAc,GAAE,CAAC;IACjD,MAAM,MAAM,GAAG,IAAA,0BAAc,GAAE,CAAC;IAEhC,MAAM,OAAO,GAAG,IAAA,4CAAsB,GAAE,CAAC;IACzC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,qDAAqD;QACrD,OAAO,CAAC,IAAI,CAAC,qBAAqB,SAAS,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,KAAK,GACT,OAAO,EAAE,cAAc,IAAI,OAAO,IAAI,OAAO,CAAC,cAAc;QAC1D,CAAC,CAAE,OAAO,CAAC,cAAc,CAAC,OAAO,CAAO;QACxC,CAAC,CAAC,YAAY,CAAC;IACnB,MAAM,QAAQ,GAAG,IAAA,mBAAW,EAC1B,KAAK,EAAE,QAAW,EAAE,EAAE;QACpB,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,iCAAiC,SAAS,EAAE,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QACD,MAAM,mBAAmB,CACvB,SAAS,EACT;YACE,GAAG,OAAO;YACV,cAAc,EAAE;gBACd,GAAG,OAAO,CAAC,cAAc;gBACzB,CAAC,OAAO,CAAC,EAAE,QAAQ;aACpB;SACF,EACD,KAAK,CACN,CAAC;QACF,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CACrD,QAAQ,EACR,SAAS,EACT,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,CACnC,CAAC;IACJ,CAAC,EACD;QACE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ;QAC5B,OAAO;QACP,OAAO;QACP,SAAS;QACT,QAAQ;QACR,mBAAmB;KACpB,CACF,CAAC;IACF,OAAO,CAAC,KAAU,EAAE,QAAQ,CAAC,CAAC;AAChC,CAAC","sourcesContent":["import { useCallback } from \"react\";\nimport { useTamboClient, useTamboThread } from \"../providers\";\nimport {\n useTamboCurrentMessage,\n useTamboMessageContext,\n} from \"./use-current-message\";\n\ntype StateUpdateResult<T> = [currentState: T, setState: (newState: T) => void];\n\n/**\n * Behaves similarly to useState, but the value is stored in the thread\n * message, and the state is keyed by the keyName\n */\nexport function useTamboComponentState<S = undefined>(\n keyName: string,\n): StateUpdateResult<S | undefined>;\nexport function useTamboComponentState<S>(\n keyName: string,\n initialValue?: S,\n): StateUpdateResult<S>;\nexport function useTamboComponentState<S>(\n keyName: string,\n initialValue?: S,\n): StateUpdateResult<S> {\n const { threadId, messageId } = useTamboMessageContext();\n const { updateThreadMessage } = useTamboThread();\n const client = useTamboClient();\n\n const message = useTamboCurrentMessage();\n if (!message) {\n // throw new Error(`Message not found ${messageId}`);\n console.warn(`Message not found ${messageId}`);\n }\n const value =\n message?.componentState && keyName in message.componentState\n ? (message.componentState[keyName] as S)\n : initialValue;\n const setValue = useCallback(\n async (newValue: S) => {\n if (!message) {\n console.warn(`Cannot update missing message ${messageId}`);\n return;\n }\n await updateThreadMessage(\n messageId,\n {\n ...message,\n componentState: {\n ...message.componentState,\n [keyName]: newValue,\n },\n },\n false,\n );\n await client.beta.threads.messages.updateComponentState(\n threadId,\n messageId,\n { state: { [keyName]: newValue } },\n );\n },\n [\n client.beta.threads.messages,\n keyName,\n message,\n messageId,\n threadId,\n updateThreadMessage,\n ],\n );\n return [value as S, setValue];\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import React, { PropsWithChildren } from "react";
2
+ /** Wraps all components, so that they can find what thread and message they are in */
3
+ export declare const TamboMessageProvider: React.FC<PropsWithChildren<{
4
+ threadId: string;
5
+ messageId: string;
6
+ }>>;
7
+ /** Wraps a component with a ComponentMessageProvider - this allows the provider
8
+ * to be used outside of a TSX file */
9
+ export declare function wrapWithTamboMessageProvider(children: React.ReactNode, threadId: string, messageId: string): React.JSX.Element;
10
+ /** Hook used inside a component wrapped with ComponentMessageProvider, to get
11
+ * the threadId and messageId */
12
+ export declare const useTamboMessageContext: () => {
13
+ threadId: string;
14
+ messageId: string;
15
+ };
16
+ /** Hook used inside a component wrapped with ComponentMessageProvider, to get
17
+ * the current message */
18
+ export declare const useTamboCurrentMessage: () => import("..").TamboThreadMessage | undefined;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.useTamboCurrentMessage = exports.useTamboMessageContext = exports.TamboMessageProvider = void 0;
37
+ exports.wrapWithTamboMessageProvider = wrapWithTamboMessageProvider;
38
+ const react_1 = __importStar(require("react"));
39
+ const providers_1 = require("../providers");
40
+ const TamboMessageContext = (0, react_1.createContext)({});
41
+ /** Wraps all components, so that they can find what thread and message they are in */
42
+ const TamboMessageProvider = ({ children, threadId, messageId }) => {
43
+ return (react_1.default.createElement(TamboMessageContext.Provider, { value: { threadId, messageId } }, children));
44
+ };
45
+ exports.TamboMessageProvider = TamboMessageProvider;
46
+ /** Wraps a component with a ComponentMessageProvider - this allows the provider
47
+ * to be used outside of a TSX file */
48
+ function wrapWithTamboMessageProvider(children, threadId, messageId) {
49
+ return (react_1.default.createElement(exports.TamboMessageProvider, { threadId: threadId, messageId: messageId }, children));
50
+ }
51
+ /** Hook used inside a component wrapped with ComponentMessageProvider, to get
52
+ * the threadId and messageId */
53
+ const useTamboMessageContext = () => {
54
+ const context = (0, react_1.useContext)(TamboMessageContext);
55
+ if (!context) {
56
+ throw new Error("useTamboMessageContext must be used within a TamboMessageProvider");
57
+ }
58
+ return context;
59
+ };
60
+ exports.useTamboMessageContext = useTamboMessageContext;
61
+ /** Hook used inside a component wrapped with ComponentMessageProvider, to get
62
+ * the current message */
63
+ const useTamboCurrentMessage = () => {
64
+ const { messageId, threadId } = (0, exports.useTamboMessageContext)();
65
+ const { thread } = (0, providers_1.useTamboThread)();
66
+ if (thread.id && threadId && thread.id !== threadId) {
67
+ console.warn(`Thread ID mismatch ${thread.id} !== ${threadId}`);
68
+ }
69
+ const message = thread.messages.find((m) => m.id === messageId);
70
+ return message;
71
+ };
72
+ exports.useTamboCurrentMessage = useTamboCurrentMessage;
73
+ //# sourceMappingURL=use-current-message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-current-message.js","sourceRoot":"","sources":["../../src/hooks/use-current-message.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBA,oEAUC;AA/BD,+CAA4E;AAC5E,4CAA8C;AAE9C,MAAM,mBAAmB,GAAG,IAAA,qBAAa,EAGtC,EAA6C,CAAC,CAAC;AAElD,sFAAsF;AAC/E,MAAM,oBAAoB,GAE7B,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE;IACxC,OAAO,CACL,8BAAC,mBAAmB,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,IACzD,QAAQ,CACoB,CAChC,CAAC;AACJ,CAAC,CAAC;AARW,QAAA,oBAAoB,wBAQ/B;AAEF;sCACsC;AACtC,SAAgB,4BAA4B,CAC1C,QAAyB,EACzB,QAAgB,EAChB,SAAiB;IAEjB,OAAO,CACL,8BAAC,4BAAoB,IAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,IAC3D,QAAQ,CACY,CACxB,CAAC;AACJ,CAAC;AACD;gCACgC;AAEzB,MAAM,sBAAsB,GAAG,GAAG,EAAE;IACzC,MAAM,OAAO,GAAG,IAAA,kBAAU,EAAC,mBAAmB,CAAC,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AARW,QAAA,sBAAsB,0BAQjC;AAEF;yBACyB;AAClB,MAAM,sBAAsB,GAAG,GAAG,EAAE;IACzC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAA,8BAAsB,GAAE,CAAC;IACzD,MAAM,EAAE,MAAM,EAAE,GAAG,IAAA,0BAAc,GAAE,CAAC;IACpC,IAAI,MAAM,CAAC,EAAE,IAAI,QAAQ,IAAI,MAAM,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,sBAAsB,MAAM,CAAC,EAAE,QAAQ,QAAQ,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC;IAChE,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AATW,QAAA,sBAAsB,0BASjC","sourcesContent":["import React, { createContext, PropsWithChildren, useContext } from \"react\";\nimport { useTamboThread } from \"../providers\";\n\nconst TamboMessageContext = createContext<{\n threadId: string;\n messageId: string;\n}>({} as { threadId: string; messageId: string });\n\n/** Wraps all components, so that they can find what thread and message they are in */\nexport const TamboMessageProvider: React.FC<\n PropsWithChildren<{ threadId: string; messageId: string }>\n> = ({ children, threadId, messageId }) => {\n return (\n <TamboMessageContext.Provider value={{ threadId, messageId }}>\n {children}\n </TamboMessageContext.Provider>\n );\n};\n\n/** Wraps a component with a ComponentMessageProvider - this allows the provider\n * to be used outside of a TSX file */\nexport function wrapWithTamboMessageProvider(\n children: React.ReactNode,\n threadId: string,\n messageId: string,\n) {\n return (\n <TamboMessageProvider threadId={threadId} messageId={messageId}>\n {children}\n </TamboMessageProvider>\n );\n}\n/** Hook used inside a component wrapped with ComponentMessageProvider, to get\n * the threadId and messageId */\n\nexport const useTamboMessageContext = () => {\n const context = useContext(TamboMessageContext);\n if (!context) {\n throw new Error(\n \"useTamboMessageContext must be used within a TamboMessageProvider\",\n );\n }\n return context;\n};\n\n/** Hook used inside a component wrapped with ComponentMessageProvider, to get\n * the current message */\nexport const useTamboCurrentMessage = () => {\n const { messageId, threadId } = useTamboMessageContext();\n const { thread } = useTamboThread();\n if (thread.id && threadId && thread.id !== threadId) {\n console.warn(`Thread ID mismatch ${thread.id} !== ${threadId}`);\n }\n\n const message = thread.messages.find((m) => m.id === messageId);\n return message;\n};\n"]}
File without changes
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=use-query-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-query-client.js","sourceRoot":"","sources":["../../src/hooks/use-query-client.ts"],"names":[],"mappings":"","sourcesContent":[""]}
@@ -0,0 +1,44 @@
1
+ import TamboAI from "@tambo-ai/typescript-sdk";
2
+ import { CombinedMutationResult } from "../util/query-utils";
3
+ import { UseTamboMutationResult } from "./react-query-hooks";
4
+ /**
5
+ * Configuration options for the useTamboSuggestions hook
6
+ */
7
+ export interface useTamboSuggestionsOptions {
8
+ /** Maximum number of suggestions to generate (1-10, default 3) */
9
+ maxSuggestions?: number;
10
+ }
11
+ /**
12
+ * Return value interface for useTamboSuggestions hook
13
+ */
14
+ export interface useTamboSuggestionsResultInternal {
15
+ /** List of available suggestions (also available in generateResult.data) */
16
+ suggestions: TamboAI.Beta.Threads.Suggestion[];
17
+ /** ID of the currently selected suggestion */
18
+ selectedSuggestionId: string | null;
19
+ /**
20
+ * Accept and apply a suggestion (also available in acceptResult.mutateAsync)
21
+ * @param suggestion - The suggestion to accept
22
+ * @param shouldSubmit - Whether to automatically submit after accepting (default: false)
23
+ */
24
+ accept: (acceptOptions: {
25
+ suggestion: TamboAI.Beta.Threads.Suggestion;
26
+ shouldSubmit?: boolean;
27
+ }) => Promise<void>;
28
+ /** Result and network state for accepting a suggestion */
29
+ acceptResult: UseTamboMutationResult<void, Error, {
30
+ suggestion: TamboAI.Beta.Threads.Suggestion;
31
+ shouldSubmit?: boolean;
32
+ }>;
33
+ /** Result and network state for generating suggestions */
34
+ generateResult: UseTamboMutationResult<TamboAI.Beta.Threads.Suggestions.SuggestionGenerateResponse | undefined, Error, AbortController>;
35
+ }
36
+ type useTamboSuggestionsResult = CombinedMutationResult<any, Error> & useTamboSuggestionsResultInternal;
37
+ /**
38
+ * Hook for managing Tambo AI suggestions in a thread
39
+ *
40
+ * @param options - Configuration options for suggestion generation
41
+ * @returns Object containing suggestions state and control functions
42
+ */
43
+ export declare function useTamboSuggestions(options?: useTamboSuggestionsOptions): useTamboSuggestionsResult;
44
+ export {};
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useTamboSuggestions = useTamboSuggestions;
4
+ const react_1 = require("react");
5
+ const validate_input_1 = require("../model/validate-input");
6
+ const tambo_client_provider_1 = require("../providers/tambo-client-provider");
7
+ const tambo_provider_1 = require("../providers/tambo-provider");
8
+ const tambo_registry_provider_1 = require("../providers/tambo-registry-provider");
9
+ const tambo_thread_provider_1 = require("../providers/tambo-thread-provider");
10
+ const query_utils_1 = require("../util/query-utils");
11
+ const registry_1 = require("../util/registry");
12
+ const react_query_hooks_1 = require("./react-query-hooks");
13
+ const use_thread_input_1 = require("./use-thread-input");
14
+ /**
15
+ * Hook for managing Tambo AI suggestions in a thread
16
+ *
17
+ * @param options - Configuration options for suggestion generation
18
+ * @returns Object containing suggestions state and control functions
19
+ */
20
+ function useTamboSuggestions(options = {}) {
21
+ const { maxSuggestions = 3 } = options;
22
+ const { thread } = (0, tambo_thread_provider_1.useTamboThread)();
23
+ const { sendThreadMessage } = (0, tambo_provider_1.useTambo)();
24
+ const tamboClient = (0, tambo_client_provider_1.useTamboClient)();
25
+ const { componentList, toolRegistry, componentToolAssociations } = (0, tambo_registry_provider_1.useTamboRegistry)();
26
+ const [selectedSuggestionId, setSelectedSuggestionId] = (0, react_1.useState)(null);
27
+ const { setValue: setInputValue } = (0, use_thread_input_1.useTamboThreadInput)();
28
+ const latestMessage = thread.messages[thread.messages.length - 1];
29
+ const isLatestFromTambo = latestMessage?.role === "hydra"; // TODO: change to tambo
30
+ const latestMessageId = latestMessage?.id;
31
+ // Reset selected suggestion when the message changes
32
+ (0, react_1.useEffect)(() => {
33
+ setSelectedSuggestionId(null);
34
+ }, [latestMessageId]);
35
+ // Use React Query to fetch suggestions when a new hydra message is received
36
+ const suggestionsQuery = (0, react_query_hooks_1.useTamboQuery)({
37
+ // Only include latestMessageId in the queryKey if the message is from hydra
38
+ queryKey: ["suggestions", isLatestFromTambo ? latestMessageId : null],
39
+ queryFn: async () => {
40
+ if (!latestMessageId || !isLatestFromTambo) {
41
+ return [];
42
+ }
43
+ // Get registered components from the registry
44
+ const components = (0, registry_1.getAvailableComponents)(componentList, toolRegistry, componentToolAssociations);
45
+ return tamboClient.beta.threads.suggestions.generate(thread.id, latestMessageId, {
46
+ maxSuggestions,
47
+ // The API expects an array of arrays for availableComponents
48
+ availableComponents: [components],
49
+ });
50
+ },
51
+ // Only run the query if we have a valid message from hydra
52
+ enabled: Boolean(latestMessageId && isLatestFromTambo),
53
+ // Don't refetch on window focus or reconnect
54
+ refetchOnWindowFocus: false,
55
+ refetchOnReconnect: false,
56
+ // Don't retry on failure
57
+ retry: false,
58
+ });
59
+ // Accept suggestion mutation
60
+ const acceptMutationState = (0, react_query_hooks_1.useTamboMutationResult)({
61
+ mutationFn: async ({ suggestion, shouldSubmit = false }) => {
62
+ const validation = (0, validate_input_1.validateInput)(suggestion.detailedSuggestion);
63
+ if (!validation.isValid) {
64
+ if (validation.error) {
65
+ throw validation.error;
66
+ }
67
+ throw new Error(use_thread_input_1.INPUT_ERROR_MESSAGES.VALIDATION);
68
+ }
69
+ if (shouldSubmit) {
70
+ await sendThreadMessage(validation.sanitizedInput, {
71
+ threadId: thread.id,
72
+ });
73
+ }
74
+ else {
75
+ setInputValue(validation.sanitizedInput);
76
+ }
77
+ setSelectedSuggestionId(suggestion.id);
78
+ },
79
+ });
80
+ // Generate suggestions mutation
81
+ const generateMutationState = (0, react_query_hooks_1.useTamboMutationResult)({
82
+ mutationFn: async (abortController) => {
83
+ if (!latestMessageId || !isLatestFromTambo) {
84
+ return undefined;
85
+ }
86
+ // Get registered components from the registry
87
+ const components = (0, registry_1.getAvailableComponents)(componentList, toolRegistry, componentToolAssociations);
88
+ return tamboClient.beta.threads.suggestions.generate(thread.id, latestMessageId, {
89
+ maxSuggestions,
90
+ // The API expects an array of arrays for availableComponents
91
+ availableComponents: [components],
92
+ }, { signal: abortController.signal });
93
+ },
94
+ // Don't retry on failure
95
+ retry: false,
96
+ });
97
+ // Use the query data if available, otherwise use the mutation data
98
+ // Only return suggestions if the latest message is from hydra
99
+ const suggestions = isLatestFromTambo
100
+ ? (suggestionsQuery.data ?? generateMutationState.data ?? [])
101
+ : [];
102
+ return {
103
+ suggestions,
104
+ accept: acceptMutationState.mutateAsync,
105
+ selectedSuggestionId,
106
+ acceptResult: acceptMutationState,
107
+ generateResult: generateMutationState,
108
+ ...(0, query_utils_1.combineMutationResults)(acceptMutationState, generateMutationState),
109
+ };
110
+ }
111
+ //# sourceMappingURL=use-suggestions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-suggestions.js","sourceRoot":"","sources":["../../src/hooks/use-suggestions.ts"],"names":[],"mappings":"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","sourcesContent":["import TamboAI from \"@tambo-ai/typescript-sdk\";\nimport { useEffect, useState } from \"react\";\nimport { validateInput } from \"../model/validate-input\";\nimport { useTamboClient } from \"../providers/tambo-client-provider\";\nimport { useTambo } from \"../providers/tambo-provider\";\nimport { useTamboRegistry } from \"../providers/tambo-registry-provider\";\nimport { useTamboThread } from \"../providers/tambo-thread-provider\";\nimport {\n CombinedMutationResult,\n combineMutationResults,\n} from \"../util/query-utils\";\nimport { getAvailableComponents } from \"../util/registry\";\nimport {\n UseTamboMutationResult,\n useTamboMutationResult,\n useTamboQuery,\n} from \"./react-query-hooks\";\nimport { INPUT_ERROR_MESSAGES, useTamboThreadInput } from \"./use-thread-input\";\n\n/**\n * Configuration options for the useTamboSuggestions hook\n */\nexport interface useTamboSuggestionsOptions {\n /** Maximum number of suggestions to generate (1-10, default 3) */\n maxSuggestions?: number;\n}\n\n/**\n * Return value interface for useTamboSuggestions hook\n */\nexport interface useTamboSuggestionsResultInternal {\n /** List of available suggestions (also available in generateResult.data) */\n suggestions: TamboAI.Beta.Threads.Suggestion[];\n /** ID of the currently selected suggestion */\n selectedSuggestionId: string | null;\n /**\n * Accept and apply a suggestion (also available in acceptResult.mutateAsync)\n * @param suggestion - The suggestion to accept\n * @param shouldSubmit - Whether to automatically submit after accepting (default: false)\n */\n accept: (acceptOptions: {\n suggestion: TamboAI.Beta.Threads.Suggestion;\n shouldSubmit?: boolean;\n }) => Promise<void>;\n\n /** Result and network state for accepting a suggestion */\n acceptResult: UseTamboMutationResult<\n void,\n Error,\n { suggestion: TamboAI.Beta.Threads.Suggestion; shouldSubmit?: boolean }\n >;\n\n /** Result and network state for generating suggestions */\n generateResult: UseTamboMutationResult<\n TamboAI.Beta.Threads.Suggestions.SuggestionGenerateResponse | undefined,\n Error,\n AbortController\n >;\n}\n\ntype useTamboSuggestionsResult = CombinedMutationResult<any, Error> &\n useTamboSuggestionsResultInternal;\n\n/**\n * Hook for managing Tambo AI suggestions in a thread\n *\n * @param options - Configuration options for suggestion generation\n * @returns Object containing suggestions state and control functions\n */\nexport function useTamboSuggestions(\n options: useTamboSuggestionsOptions = {},\n): useTamboSuggestionsResult {\n const { maxSuggestions = 3 } = options;\n const { thread } = useTamboThread();\n const { sendThreadMessage } = useTambo();\n const tamboClient = useTamboClient();\n const { componentList, toolRegistry, componentToolAssociations } =\n useTamboRegistry();\n\n const [selectedSuggestionId, setSelectedSuggestionId] = useState<\n string | null\n >(null);\n const { setValue: setInputValue } = useTamboThreadInput();\n\n const latestMessage = thread.messages[thread.messages.length - 1];\n const isLatestFromTambo = latestMessage?.role === \"hydra\"; // TODO: change to tambo\n const latestMessageId = latestMessage?.id;\n\n // Reset selected suggestion when the message changes\n useEffect(() => {\n setSelectedSuggestionId(null);\n }, [latestMessageId]);\n\n // Use React Query to fetch suggestions when a new hydra message is received\n const suggestionsQuery = useTamboQuery({\n // Only include latestMessageId in the queryKey if the message is from hydra\n queryKey: [\"suggestions\", isLatestFromTambo ? latestMessageId : null],\n queryFn: async () => {\n if (!latestMessageId || !isLatestFromTambo) {\n return [];\n }\n\n // Get registered components from the registry\n const components = getAvailableComponents(\n componentList,\n toolRegistry,\n componentToolAssociations,\n );\n\n return tamboClient.beta.threads.suggestions.generate(\n thread.id,\n latestMessageId,\n {\n maxSuggestions,\n // The API expects an array of arrays for availableComponents\n availableComponents: [components],\n },\n );\n },\n // Only run the query if we have a valid message from hydra\n enabled: Boolean(latestMessageId && isLatestFromTambo),\n // Don't refetch on window focus or reconnect\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n // Don't retry on failure\n retry: false,\n });\n\n // Accept suggestion mutation\n const acceptMutationState = useTamboMutationResult<\n void,\n Error,\n { suggestion: TamboAI.Beta.Threads.Suggestion; shouldSubmit?: boolean }\n >({\n mutationFn: async ({ suggestion, shouldSubmit = false }) => {\n const validation = validateInput(suggestion.detailedSuggestion);\n if (!validation.isValid) {\n if (validation.error) {\n throw validation.error;\n }\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n if (shouldSubmit) {\n await sendThreadMessage(validation.sanitizedInput, {\n threadId: thread.id,\n });\n } else {\n setInputValue(validation.sanitizedInput);\n }\n setSelectedSuggestionId(suggestion.id);\n },\n });\n\n // Generate suggestions mutation\n const generateMutationState = useTamboMutationResult<\n TamboAI.Beta.Threads.Suggestions.SuggestionGenerateResponse | undefined,\n Error,\n AbortController\n >({\n mutationFn: async (abortController: AbortController) => {\n if (!latestMessageId || !isLatestFromTambo) {\n return undefined;\n }\n\n // Get registered components from the registry\n const components = getAvailableComponents(\n componentList,\n toolRegistry,\n componentToolAssociations,\n );\n\n return tamboClient.beta.threads.suggestions.generate(\n thread.id,\n latestMessageId,\n {\n maxSuggestions,\n // The API expects an array of arrays for availableComponents\n availableComponents: [components],\n },\n { signal: abortController.signal },\n );\n },\n // Don't retry on failure\n retry: false,\n });\n\n // Use the query data if available, otherwise use the mutation data\n // Only return suggestions if the latest message is from hydra\n const suggestions = isLatestFromTambo\n ? (suggestionsQuery.data ?? generateMutationState.data ?? [])\n : [];\n\n return {\n suggestions,\n accept: acceptMutationState.mutateAsync,\n selectedSuggestionId,\n acceptResult: acceptMutationState,\n generateResult: generateMutationState,\n ...combineMutationResults(acceptMutationState, generateMutationState),\n };\n}\n"]}