@letta-ai/letta-react 0.0.1
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.
- package/README.md +117 -0
- package/dist/hooks/useAgentMessages/useAgentMessages.d.ts +27 -0
- package/dist/hooks/useAgentMessages/useAgentMessages.js +217 -0
- package/dist/hooks/useAgentState/useAgentState.d.ts +13 -0
- package/dist/hooks/useAgentState/useAgentState.js +46 -0
- package/dist/hooks/useCachedState/useCachedState.d.ts +2 -0
- package/dist/hooks/useCachedState/useCachedState.js +19 -0
- package/dist/hooks/useGlobalLettaConfig/useGlobalLettaConfig.d.ts +17 -0
- package/dist/hooks/useGlobalLettaConfig/useGlobalLettaConfig.js +22 -0
- package/dist/hooks/useLettaClient/useLettaClient.d.ts +2 -0
- package/dist/hooks/useLettaClient/useLettaClient.js +8 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.js +1 -0
- package/eslint.config.mjs +3 -0
- package/examples/view-and-send-messages/package-lock.json +1020 -0
- package/examples/view-and-send-messages/package.json +15 -0
- package/examples/view-and-send-messages/src/App.css +24 -0
- package/examples/view-and-send-messages/src/App.tsx +92 -0
- package/examples/view-and-send-messages/src/client.tsx +34 -0
- package/examples/view-and-send-messages/src/index.html +13 -0
- package/examples/view-and-send-messages/tsconfig.json +21 -0
- package/examples/view-and-send-messages/vite.config.mts +6 -0
- package/hooks/useAgentMessages/useAgentMessages.d.ts +27 -0
- package/hooks/useAgentMessages/useAgentMessages.js +217 -0
- package/hooks/useAgentState/useAgentState.d.ts +13 -0
- package/hooks/useAgentState/useAgentState.js +46 -0
- package/hooks/useCachedState/useCachedState.d.ts +2 -0
- package/hooks/useCachedState/useCachedState.js +19 -0
- package/hooks/useGlobalLettaConfig/useGlobalLettaConfig.d.ts +17 -0
- package/hooks/useGlobalLettaConfig/useGlobalLettaConfig.js +22 -0
- package/hooks/useLettaClient/useLettaClient.d.ts +2 -0
- package/hooks/useLettaClient/useLettaClient.js +8 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/jest.config.ts +10 -0
- package/package.json +39 -0
- package/project.json +20 -0
- package/src/hooks/useAgentMessages/useAgentMessages.ts +329 -0
- package/src/hooks/useAgentState/useAgentState.ts +55 -0
- package/src/hooks/useCachedState/useCachedState.ts +30 -0
- package/src/hooks/useGlobalLettaConfig/useGlobalLettaConfig.tsx +49 -0
- package/src/hooks/useLettaClient/useLettaClient.ts +17 -0
- package/src/index.ts +3 -0
- package/src/types.ts +6 -0
- package/tsconfig.app.json +10 -0
- package/tsconfig.json +16 -0
- package/tsconfig.spec.json +15 -0
- package/types.d.ts +5 -0
- package/types.js +1 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"name": "view-and-send-messages",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"main": "index.js",
|
5
|
+
"scripts": {
|
6
|
+
"start": "vite ./src --config vite.config.mts --port 3000"
|
7
|
+
},
|
8
|
+
"author": "",
|
9
|
+
"license": "MIT",
|
10
|
+
"description": "",
|
11
|
+
"devDependencies": {
|
12
|
+
"vite": "^6.2.0",
|
13
|
+
"vite-tsconfig-paths": "^5.1.4"
|
14
|
+
}
|
15
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
.messages {
|
2
|
+
list-style: none;
|
3
|
+
padding: 0;
|
4
|
+
flex-direction: column;
|
5
|
+
gap: 1rem;
|
6
|
+
display: flex;
|
7
|
+
}
|
8
|
+
|
9
|
+
.messages li {
|
10
|
+
border: 1px solid #ccc;
|
11
|
+
border-radius: 3px;
|
12
|
+
padding: 1rem;
|
13
|
+
}
|
14
|
+
|
15
|
+
.send-message {
|
16
|
+
display: flex;
|
17
|
+
gap: 1rem;
|
18
|
+
margin-top: 1rem;
|
19
|
+
}
|
20
|
+
|
21
|
+
.send-message input {
|
22
|
+
flex: 1;
|
23
|
+
padding: 0.5rem;
|
24
|
+
}
|
@@ -0,0 +1,92 @@
|
|
1
|
+
import React, { FormEvent, useCallback } from 'react';
|
2
|
+
import { useState } from 'react';
|
3
|
+
import './App.css';
|
4
|
+
import { useAgentMessages, useAgentState } from '@letta-ai/letta-react';
|
5
|
+
|
6
|
+
function App() {
|
7
|
+
const [messageToSend, setMessageToSend] = useState<string>('');
|
8
|
+
const {
|
9
|
+
messages,
|
10
|
+
fetchOlderMessages,
|
11
|
+
isFetching,
|
12
|
+
hasOlderMessages,
|
13
|
+
isLoading,
|
14
|
+
isSending,
|
15
|
+
sendMessage,
|
16
|
+
} = useAgentMessages({
|
17
|
+
agentId: 'agent-ed85493d-2164-4404-b52e-119ccbc987b4',
|
18
|
+
});
|
19
|
+
|
20
|
+
const { agentState } = useAgentState({
|
21
|
+
agentId: 'agent-ed85493d-2164-4404-b52e-119ccbc987b4',
|
22
|
+
});
|
23
|
+
|
24
|
+
const handleSubmit = useCallback(
|
25
|
+
(e: FormEvent<HTMLFormElement>) => {
|
26
|
+
e.preventDefault();
|
27
|
+
|
28
|
+
setMessageToSend('');
|
29
|
+
|
30
|
+
sendMessage({
|
31
|
+
messages: [
|
32
|
+
{
|
33
|
+
role: 'user',
|
34
|
+
content: messageToSend,
|
35
|
+
},
|
36
|
+
],
|
37
|
+
});
|
38
|
+
},
|
39
|
+
[messageToSend, sendMessage]
|
40
|
+
);
|
41
|
+
|
42
|
+
return (
|
43
|
+
<main>
|
44
|
+
<header>Talking to: {agentState?.name}</header>
|
45
|
+
{hasOlderMessages && (
|
46
|
+
<button onClick={fetchOlderMessages} disabled={isFetching}>
|
47
|
+
{isFetching ? 'Loading older messages' : 'Load older messages'}
|
48
|
+
</button>
|
49
|
+
)}
|
50
|
+
|
51
|
+
{isLoading ? (
|
52
|
+
<div>Loading messages!</div>
|
53
|
+
) : (
|
54
|
+
<ul className="messages">
|
55
|
+
{messages.map((message) => {
|
56
|
+
if (message.messageType === 'user_message') {
|
57
|
+
return (
|
58
|
+
<li key={`${message.id}${message.messageType}`}>
|
59
|
+
{message.content}
|
60
|
+
{message.id}
|
61
|
+
</li>
|
62
|
+
);
|
63
|
+
}
|
64
|
+
|
65
|
+
if (message.messageType === 'assistant_message') {
|
66
|
+
return (
|
67
|
+
<li key={`${message.id}${message.messageType}`}>
|
68
|
+
{message.content}
|
69
|
+
</li>
|
70
|
+
);
|
71
|
+
}
|
72
|
+
|
73
|
+
return null;
|
74
|
+
})}
|
75
|
+
</ul>
|
76
|
+
)}
|
77
|
+
{isSending ? 'Sending message...' : null}
|
78
|
+
<form onSubmit={handleSubmit} className="send-message">
|
79
|
+
<input
|
80
|
+
placeholder="Send a message"
|
81
|
+
value={messageToSend}
|
82
|
+
onChange={(e) => {
|
83
|
+
setMessageToSend(e.target.value);
|
84
|
+
}}
|
85
|
+
/>
|
86
|
+
<button disabled={isSending}>Send Message</button>
|
87
|
+
</form>
|
88
|
+
</main>
|
89
|
+
);
|
90
|
+
}
|
91
|
+
|
92
|
+
export default App;
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { StrictMode } from 'react';
|
3
|
+
import { hydrateRoot } from 'react-dom/client';
|
4
|
+
import App from './App';
|
5
|
+
import { LettaProvider } from '@letta-ai/letta-react';
|
6
|
+
|
7
|
+
interface DisableSSRProps {
|
8
|
+
children: React.ReactNode;
|
9
|
+
}
|
10
|
+
|
11
|
+
function DisableSSR(props: DisableSSRProps) {
|
12
|
+
const [mounted, setMounted] = React.useState(false);
|
13
|
+
|
14
|
+
React.useEffect(() => {
|
15
|
+
setMounted(true);
|
16
|
+
}, []);
|
17
|
+
|
18
|
+
return mounted ? <>{props.children}</> : null;
|
19
|
+
}
|
20
|
+
|
21
|
+
hydrateRoot(
|
22
|
+
document.getElementById('root') as HTMLElement,
|
23
|
+
<StrictMode>
|
24
|
+
<DisableSSR>
|
25
|
+
<LettaProvider
|
26
|
+
options={{
|
27
|
+
baseUrl: 'http://localhost:8283',
|
28
|
+
}}
|
29
|
+
>
|
30
|
+
<App />
|
31
|
+
</LettaProvider>
|
32
|
+
</DisableSSR>
|
33
|
+
</StrictMode>
|
34
|
+
);
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8" />
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6
|
+
<title>Demo</title>
|
7
|
+
<!--app-head-->
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<div id="root"><!--app-html--></div>
|
11
|
+
<script type="module" src="./client.tsx"></script>
|
12
|
+
</body>
|
13
|
+
</html>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
{
|
2
|
+
"files": [],
|
3
|
+
"include": ["src"],
|
4
|
+
|
5
|
+
"compilerOptions": {
|
6
|
+
"outDir": "dist",
|
7
|
+
"rootDir": "src",
|
8
|
+
"baseUrl": "src",
|
9
|
+
"jsx": "react",
|
10
|
+
"extendedDiagnostics": true,
|
11
|
+
"strict": true,
|
12
|
+
"target": "ES6",
|
13
|
+
"moduleResolution": "node",
|
14
|
+
"skipLibCheck": true,
|
15
|
+
"declaration": true,
|
16
|
+
"esModuleInterop": true,
|
17
|
+
"paths": {
|
18
|
+
"@letta-ai/letta-react": ["../../../src/index.ts"]
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { LettaClient, type Letta } from '@letta-ai/letta-client';
|
2
|
+
import type { LettaRequest } from '@letta-ai/letta-client/api/types/LettaRequest';
|
3
|
+
import type { MessagesListRequest } from '@letta-ai/letta-client/api/resources/agents';
|
4
|
+
interface UseAgentOptions {
|
5
|
+
client?: LettaClient.Options;
|
6
|
+
agentId: string;
|
7
|
+
limit?: number;
|
8
|
+
messageOptions?: Omit<MessagesListRequest, 'after' | 'before' | 'limit'>;
|
9
|
+
method?: 'stream' | 'basic';
|
10
|
+
}
|
11
|
+
interface SendMessagePayload {
|
12
|
+
messages: LettaRequest['messages'];
|
13
|
+
}
|
14
|
+
export declare function useAgentMessages(options: UseAgentOptions): {
|
15
|
+
messages: Letta.LettaMessageUnion[];
|
16
|
+
isLoading: boolean;
|
17
|
+
isFetching: boolean;
|
18
|
+
isLoadingError: boolean;
|
19
|
+
loadingError: unknown;
|
20
|
+
sendMessage: (payload: SendMessagePayload) => Promise<void> | undefined;
|
21
|
+
isSending: boolean;
|
22
|
+
sendingError: unknown;
|
23
|
+
isSendingError: boolean;
|
24
|
+
fetchOlderMessages: () => Promise<void>;
|
25
|
+
hasOlderMessages: string | undefined;
|
26
|
+
};
|
27
|
+
export {};
|
@@ -0,0 +1,217 @@
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8
|
+
});
|
9
|
+
};
|
10
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
11
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
12
|
+
var m = o[Symbol.asyncIterator], i;
|
13
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
14
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
15
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
16
|
+
};
|
17
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
18
|
+
import { useCachedState } from '../useCachedState/useCachedState';
|
19
|
+
import { useLettaClient } from '../useLettaClient/useLettaClient';
|
20
|
+
function extendContent(content, nextContent) {
|
21
|
+
if (typeof content === 'string') {
|
22
|
+
if (typeof nextContent === 'string') {
|
23
|
+
return `${content}${nextContent}`;
|
24
|
+
}
|
25
|
+
else {
|
26
|
+
return `${content}${nextContent.join('')}`;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
else {
|
30
|
+
if (typeof nextContent === 'string') {
|
31
|
+
return [...content, { text: nextContent, type: 'text' }];
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
return [...content, ...nextContent];
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
export function useAgentMessages(options) {
|
39
|
+
const { client = {}, method = 'stream', messageOptions = {}, limit = 20, agentId, } = options;
|
40
|
+
const localClient = useLettaClient(client);
|
41
|
+
const [localMessages, setLocalMessages] = useCachedState(`messages-${agentId}`, {
|
42
|
+
messages: [],
|
43
|
+
});
|
44
|
+
const hasInitialLoaded = useRef(false);
|
45
|
+
const [isLoading, setIsLoading] = useState(true);
|
46
|
+
const [isFetching, setIsFetching] = useState(false);
|
47
|
+
const [loadingError, setLoadingError] = useState(null);
|
48
|
+
const [isSending, setIsSending] = useState(false);
|
49
|
+
const [sendingError, setSendingError] = useState(null);
|
50
|
+
const sendNonStreamedMessage = useCallback(function sendNonStreamedMessage(sendMessagePayload) {
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
52
|
+
try {
|
53
|
+
setIsSending(true);
|
54
|
+
setSendingError(null);
|
55
|
+
const response = yield localClient.agents.messages.create(agentId, Object.assign(Object.assign({}, sendMessagePayload), messageOptions));
|
56
|
+
setLocalMessages((prevState) => (Object.assign(Object.assign({}, prevState), { messages: [...response.messages, ...prevState.messages] })));
|
57
|
+
}
|
58
|
+
catch (e) {
|
59
|
+
setSendingError(e);
|
60
|
+
}
|
61
|
+
finally {
|
62
|
+
setIsSending(false);
|
63
|
+
}
|
64
|
+
});
|
65
|
+
}, [localClient]);
|
66
|
+
const sendStreamedMessage = useCallback(function sendStreamedMessage(sendMessagePayload) {
|
67
|
+
return __awaiter(this, void 0, void 0, function* () {
|
68
|
+
var _a, e_1, _b, _c;
|
69
|
+
try {
|
70
|
+
setIsSending(true);
|
71
|
+
setSendingError(null);
|
72
|
+
const response = yield localClient.agents.messages.createStream(agentId, Object.assign(Object.assign(Object.assign({}, sendMessagePayload), messageOptions), { streamTokens: true }));
|
73
|
+
try {
|
74
|
+
for (var _d = true, response_1 = __asyncValues(response), response_1_1; response_1_1 = yield response_1.next(), _a = response_1_1.done, !_a; _d = true) {
|
75
|
+
_c = response_1_1.value;
|
76
|
+
_d = false;
|
77
|
+
const nextMessage = _c;
|
78
|
+
if (nextMessage.messageType === 'usage_statistics') {
|
79
|
+
return;
|
80
|
+
}
|
81
|
+
// @ts-expect-error - this is a correct lookup, id does exist
|
82
|
+
if (!(nextMessage === null || nextMessage === void 0 ? void 0 : nextMessage.id)) {
|
83
|
+
return;
|
84
|
+
}
|
85
|
+
setLocalMessages((prevState) => {
|
86
|
+
let hasExistingMessage = false;
|
87
|
+
const nextMessages = prevState.messages.map((prevMessage) => {
|
88
|
+
if (nextMessage.messageType === 'usage_statistics') {
|
89
|
+
return prevMessage;
|
90
|
+
}
|
91
|
+
// @ts-expect-error - this is a correct lookup, id does exist
|
92
|
+
if (!(prevMessage === null || prevMessage === void 0 ? void 0 : prevMessage.id) || !(nextMessage === null || nextMessage === void 0 ? void 0 : nextMessage.id)) {
|
93
|
+
return prevMessage;
|
94
|
+
}
|
95
|
+
// @ts-expect-error - this is a correct lookup, id does exist
|
96
|
+
if (prevMessage.id !== nextMessage.id) {
|
97
|
+
return prevMessage;
|
98
|
+
}
|
99
|
+
if (nextMessage.messageType === 'reasoning_message' &&
|
100
|
+
prevMessage.messageType === 'reasoning_message') {
|
101
|
+
hasExistingMessage = true;
|
102
|
+
return Object.assign(Object.assign(Object.assign({}, prevMessage), nextMessage), { reasoning: `${prevMessage.reasoning}${nextMessage.reasoning}` });
|
103
|
+
}
|
104
|
+
if (nextMessage.messageType === 'system_message' &&
|
105
|
+
prevMessage.messageType === 'system_message') {
|
106
|
+
hasExistingMessage = true;
|
107
|
+
return Object.assign(Object.assign(Object.assign({}, prevMessage), nextMessage), { content: extendContent(prevMessage.content, nextMessage.content) });
|
108
|
+
}
|
109
|
+
if (nextMessage.messageType === 'user_message' &&
|
110
|
+
prevMessage.messageType === 'user_message') {
|
111
|
+
hasExistingMessage = true;
|
112
|
+
return Object.assign(Object.assign(Object.assign({}, prevMessage), nextMessage), { content: extendContent(prevMessage.content, nextMessage.content) });
|
113
|
+
}
|
114
|
+
if (nextMessage.messageType === 'assistant_message' &&
|
115
|
+
prevMessage.messageType === 'assistant_message') {
|
116
|
+
hasExistingMessage = true;
|
117
|
+
return Object.assign(Object.assign(Object.assign({}, prevMessage), nextMessage), { content: extendContent(prevMessage.content, nextMessage.content) });
|
118
|
+
}
|
119
|
+
if (nextMessage.messageType === 'tool_call_message' &&
|
120
|
+
prevMessage.messageType === 'tool_call_message') {
|
121
|
+
hasExistingMessage = true;
|
122
|
+
return Object.assign(Object.assign({}, prevMessage), nextMessage);
|
123
|
+
}
|
124
|
+
if (nextMessage.messageType === 'tool_return_message' &&
|
125
|
+
prevMessage.messageType === 'tool_return_message') {
|
126
|
+
hasExistingMessage = true;
|
127
|
+
return Object.assign(Object.assign({}, prevMessage), nextMessage);
|
128
|
+
}
|
129
|
+
return prevMessage;
|
130
|
+
});
|
131
|
+
return Object.assign(Object.assign({}, prevState), { messages: [
|
132
|
+
...nextMessages,
|
133
|
+
...(!hasExistingMessage
|
134
|
+
? [
|
135
|
+
Object.assign({}, nextMessage),
|
136
|
+
]
|
137
|
+
: []),
|
138
|
+
] });
|
139
|
+
});
|
140
|
+
}
|
141
|
+
}
|
142
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
143
|
+
finally {
|
144
|
+
try {
|
145
|
+
if (!_d && !_a && (_b = response_1.return)) yield _b.call(response_1);
|
146
|
+
}
|
147
|
+
finally { if (e_1) throw e_1.error; }
|
148
|
+
}
|
149
|
+
}
|
150
|
+
catch (e) {
|
151
|
+
setSendingError(e);
|
152
|
+
}
|
153
|
+
finally {
|
154
|
+
setIsSending(false);
|
155
|
+
}
|
156
|
+
});
|
157
|
+
}, [localClient]);
|
158
|
+
const sendMessage = useCallback(function sendMessage(payload) {
|
159
|
+
if (isSending) {
|
160
|
+
return;
|
161
|
+
}
|
162
|
+
if (method === 'stream') {
|
163
|
+
return sendStreamedMessage(payload);
|
164
|
+
}
|
165
|
+
return sendNonStreamedMessage(payload);
|
166
|
+
}, [method, isSending, sendStreamedMessage, sendNonStreamedMessage]);
|
167
|
+
const getMessages = useCallback(function getMessages(before) {
|
168
|
+
return __awaiter(this, void 0, void 0, function* () {
|
169
|
+
try {
|
170
|
+
if (isFetching) {
|
171
|
+
return;
|
172
|
+
}
|
173
|
+
setLoadingError(null);
|
174
|
+
setIsFetching(true);
|
175
|
+
const messages = yield localClient.agents.messages.list(agentId, Object.assign({ before, limit: limit + 1 }, messageOptions));
|
176
|
+
const messagesToAdd = messages.slice(1, messages.length);
|
177
|
+
const nextCursor = messages.length > limit ? messages[0] : undefined;
|
178
|
+
setLocalMessages((prevState) => (Object.assign(Object.assign({}, prevState), { messages: [...messagesToAdd, ...prevState.messages], nextCursor: nextCursor === null || nextCursor === void 0 ? void 0 : nextCursor.id })));
|
179
|
+
}
|
180
|
+
catch (e) {
|
181
|
+
setLoadingError(e);
|
182
|
+
}
|
183
|
+
finally {
|
184
|
+
setIsFetching(false);
|
185
|
+
setIsLoading(false);
|
186
|
+
}
|
187
|
+
});
|
188
|
+
}, [localClient, isFetching]);
|
189
|
+
const fetchOlderMessages = useCallback(() => __awaiter(this, void 0, void 0, function* () {
|
190
|
+
const nextCursor = localMessages.nextCursor;
|
191
|
+
if (!nextCursor) {
|
192
|
+
return;
|
193
|
+
}
|
194
|
+
return getMessages(nextCursor);
|
195
|
+
}), [getMessages]);
|
196
|
+
useEffect(() => {
|
197
|
+
if (hasInitialLoaded.current) {
|
198
|
+
return;
|
199
|
+
}
|
200
|
+
hasInitialLoaded.current = true;
|
201
|
+
setIsLoading(true);
|
202
|
+
getMessages();
|
203
|
+
}, []);
|
204
|
+
return {
|
205
|
+
messages: localMessages.messages,
|
206
|
+
isLoading,
|
207
|
+
isFetching,
|
208
|
+
isLoadingError: !!loadingError,
|
209
|
+
loadingError,
|
210
|
+
sendMessage,
|
211
|
+
isSending,
|
212
|
+
sendingError,
|
213
|
+
isSendingError: !!sendingError,
|
214
|
+
fetchOlderMessages,
|
215
|
+
hasOlderMessages: localMessages.nextCursor,
|
216
|
+
};
|
217
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import type { LettaClient } from '@letta-ai/letta-client';
|
2
|
+
import type { AgentState } from '@letta-ai/letta-client/api';
|
3
|
+
interface UseAgentStateOptions {
|
4
|
+
client?: LettaClient.Options;
|
5
|
+
agentId: string;
|
6
|
+
}
|
7
|
+
export declare function useAgentState(options: UseAgentStateOptions): {
|
8
|
+
isLoading: boolean;
|
9
|
+
error: unknown;
|
10
|
+
agentState: AgentState | undefined;
|
11
|
+
refresh: () => Promise<void>;
|
12
|
+
};
|
13
|
+
export {};
|
@@ -0,0 +1,46 @@
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8
|
+
});
|
9
|
+
};
|
10
|
+
import { useLettaClient } from '../useLettaClient/useLettaClient';
|
11
|
+
import { useCachedState } from '../useCachedState/useCachedState';
|
12
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
13
|
+
export function useAgentState(options) {
|
14
|
+
const { client, agentId } = options;
|
15
|
+
const localClient = useLettaClient(client);
|
16
|
+
const [localState, setLocalState] = useCachedState(`agent-state-${agentId}`, undefined);
|
17
|
+
const [isLoading, setIsLoading] = useState(true);
|
18
|
+
const [loadingError, setLoadingError] = useState(null);
|
19
|
+
const hasInitialLoaded = useRef(false);
|
20
|
+
const getAgentState = useCallback(() => __awaiter(this, void 0, void 0, function* () {
|
21
|
+
try {
|
22
|
+
const state = yield localClient.agents.retrieve(agentId);
|
23
|
+
setLocalState(state);
|
24
|
+
}
|
25
|
+
catch (error) {
|
26
|
+
setLoadingError(error);
|
27
|
+
}
|
28
|
+
finally {
|
29
|
+
setIsLoading(false);
|
30
|
+
}
|
31
|
+
}), [agentId, localClient, setLocalState]);
|
32
|
+
useEffect(() => {
|
33
|
+
if (hasInitialLoaded.current) {
|
34
|
+
return;
|
35
|
+
}
|
36
|
+
hasInitialLoaded.current = true;
|
37
|
+
setIsLoading(true);
|
38
|
+
getAgentState();
|
39
|
+
}, []);
|
40
|
+
return {
|
41
|
+
isLoading,
|
42
|
+
error: loadingError,
|
43
|
+
agentState: localState,
|
44
|
+
refresh: getAgentState,
|
45
|
+
};
|
46
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { useGlobalLettaConfig } from '../useGlobalLettaConfig/useGlobalLettaConfig';
|
2
|
+
import { useState } from 'react';
|
3
|
+
export function useCachedState(key, defaultValue) {
|
4
|
+
const { isProviderSet, cachedData, updateCache } = useGlobalLettaConfig();
|
5
|
+
const [localState, setLocalState] = useState(defaultValue);
|
6
|
+
if (!isProviderSet) {
|
7
|
+
return [localState, setLocalState];
|
8
|
+
}
|
9
|
+
return [
|
10
|
+
(cachedData === null || cachedData === void 0 ? void 0 : cachedData[key]) || defaultValue,
|
11
|
+
(value) => {
|
12
|
+
updateCache((prevState) => {
|
13
|
+
return Object.assign(Object.assign({}, prevState), { [key]: typeof value === 'function'
|
14
|
+
? value((prevState === null || prevState === void 0 ? void 0 : prevState[key]) || defaultValue)
|
15
|
+
: value });
|
16
|
+
});
|
17
|
+
},
|
18
|
+
];
|
19
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import React, { Dispatch, SetStateAction } from 'react';
|
2
|
+
import { LettaClient } from '@letta-ai/letta-client';
|
3
|
+
import { type ReactNode } from 'react';
|
4
|
+
type CachedData = Record<string, any>;
|
5
|
+
interface LettaProviderState {
|
6
|
+
isProviderSet?: boolean;
|
7
|
+
state: LettaClient.Options;
|
8
|
+
cachedData: CachedData;
|
9
|
+
updateCache: Dispatch<SetStateAction<CachedData>>;
|
10
|
+
}
|
11
|
+
interface LettaProviderProps {
|
12
|
+
options: LettaClient.Options;
|
13
|
+
children: ReactNode;
|
14
|
+
}
|
15
|
+
export declare function LettaProvider(props: LettaProviderProps): React.JSX.Element;
|
16
|
+
export declare function useGlobalLettaConfig(): LettaProviderState;
|
17
|
+
export {};
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { createContext, useMemo, useContext } from 'react';
|
3
|
+
const LettaContext = createContext({
|
4
|
+
state: {},
|
5
|
+
cachedData: {},
|
6
|
+
updateCache: () => {
|
7
|
+
return;
|
8
|
+
},
|
9
|
+
});
|
10
|
+
export function LettaProvider(props) {
|
11
|
+
const [cachedData, updateCache] = useState({});
|
12
|
+
const state = useMemo(() => ({
|
13
|
+
state: props.options,
|
14
|
+
cachedData,
|
15
|
+
updateCache,
|
16
|
+
isProviderSet: true,
|
17
|
+
}), [props.options, updateCache, cachedData]);
|
18
|
+
return (React.createElement(LettaContext.Provider, { value: state }, props.children));
|
19
|
+
}
|
20
|
+
export function useGlobalLettaConfig() {
|
21
|
+
return useContext(LettaContext);
|
22
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { useState } from 'react';
|
2
|
+
import { LettaClient } from '@letta-ai/letta-client';
|
3
|
+
import { useGlobalLettaConfig } from '../useGlobalLettaConfig/useGlobalLettaConfig';
|
4
|
+
export function useLettaClient(localOptions = {}) {
|
5
|
+
const globalClient = useGlobalLettaConfig();
|
6
|
+
const [localClient] = useState(() => new LettaClient(Object.assign(Object.assign({}, globalClient.state), localOptions)));
|
7
|
+
return localClient;
|
8
|
+
}
|
package/index.d.ts
ADDED
package/index.js
ADDED
package/jest.config.ts
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
export default {
|
2
|
+
displayName: 'letta-react',
|
3
|
+
preset: '../../jest.preset.js',
|
4
|
+
testEnvironment: 'node',
|
5
|
+
transform: {
|
6
|
+
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
7
|
+
},
|
8
|
+
moduleFileExtensions: ['ts', 'js', 'html'],
|
9
|
+
coverageDirectory: '../../coverage/packages/letta-react',
|
10
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
{
|
2
|
+
"name": "@letta-ai/letta-react",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"description": "Letta's react library",
|
5
|
+
"private": false,
|
6
|
+
"main": "./index.js",
|
7
|
+
"types": "./index.d.ts",
|
8
|
+
"scripts": {
|
9
|
+
"build": "tsc",
|
10
|
+
"test": "echo \"Pls set up tests\" && exit 0",
|
11
|
+
"prepack": "cp -rv dist/. ."
|
12
|
+
},
|
13
|
+
"repository": {
|
14
|
+
"type": "git",
|
15
|
+
"url": "git+https://github.com/letta-ai/letta-typescript-web-clients.git"
|
16
|
+
},
|
17
|
+
"author": "Letta (https://letta.com)",
|
18
|
+
"license": "MIT",
|
19
|
+
"bugs": {
|
20
|
+
"url": "https://github.com/letta-ai/letta-typescript-web-clients/issues"
|
21
|
+
},
|
22
|
+
"homepage": "https://github.com/letta-ai/letta-typescript-web-clients#readme",
|
23
|
+
"peerDependencies": {
|
24
|
+
"@letta-ai/letta-client": ">=0.1.48",
|
25
|
+
"react": ">=16.8.0 <20.0.0",
|
26
|
+
"react-dom": ">=16.8.0 <20.0.0"
|
27
|
+
},
|
28
|
+
"resolutions": {
|
29
|
+
"@types/react": "18.0.5"
|
30
|
+
},
|
31
|
+
"devDependencies": {
|
32
|
+
"@testing-library/dom": "^10.4.0",
|
33
|
+
"@testing-library/react": "^16.2.0",
|
34
|
+
"@types/react": "^19.0.10",
|
35
|
+
"@types/react-dom": "^19.0.4",
|
36
|
+
"jest-mock-extended": "^4.0.0-beta1",
|
37
|
+
"typescript": "^5.8.2"
|
38
|
+
}
|
39
|
+
}
|