@tanstack/ai-devtools-core 0.4.24 → 0.5.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 +15 -1
- package/dist/Shell/{3ITTEC6O.js → DRZSZBFE.js} +706 -95
- package/dist/Shell/{54VAGOHT.js → G2SFOZV2.js} +513 -92
- package/dist/{production/Shell/SPYBPW3X.js → Shell/WD22ACIB.js} +706 -95
- package/dist/dev.js +1 -1
- package/dist/index.js +1 -1
- package/dist/production/Shell/{3ITTEC6O.js → DRZSZBFE.js} +706 -95
- package/dist/production/Shell/{54VAGOHT.js → G2SFOZV2.js} +513 -92
- package/dist/{Shell/SPYBPW3X.js → production/Shell/WD22ACIB.js} +706 -95
- package/dist/production/dev.js +1 -1
- package/dist/production/index.js +1 -1
- package/dist/production/server.js +1 -1
- package/dist/server.js +1 -1
- package/package.json +6 -6
- package/src/components/hooks/HookDashboard.tsx +2 -3
- package/src/components/hooks/HookDetails.tsx +37 -8
- package/src/components/hooks/MemoryPanel.tsx +296 -0
- package/src/components/hooks/hook-dashboard-model.ts +26 -0
- package/src/components/hooks/index.ts +1 -0
- package/src/store/ai-context.tsx +62 -0
- package/src/store/memory-registry.ts +228 -0
- package/src/styles/use-styles.ts +112 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MemoryErrorEvent,
|
|
3
|
+
MemoryPersistCompletedEvent,
|
|
4
|
+
MemoryPersistStartedEvent,
|
|
5
|
+
MemoryRetrieveCompletedEvent,
|
|
6
|
+
MemoryRetrieveStartedEvent,
|
|
7
|
+
MemoryScopeLite,
|
|
8
|
+
MemorySnapshotEvent,
|
|
9
|
+
} from '@tanstack/ai-event-client'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* DevTools-side accumulator for the `memory:*` event stream. Kept as a set of
|
|
13
|
+
* pure reducers (mirroring `hook-registry.ts`) so the mapping from events →
|
|
14
|
+
* view state is unit-testable in isolation, without a Solid store.
|
|
15
|
+
*
|
|
16
|
+
* Memory is keyed by composite scope (`tenantId`/`userId`/`threadId`), NOT by
|
|
17
|
+
* hook — several hooks can share one scope. The `MemoryPanel` reads a single
|
|
18
|
+
* `MemoryScopeState` by key; the per-hook tab just resolves which key to show.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** One row in a scope's operations timeline. */
|
|
22
|
+
export interface MemoryEventRecord {
|
|
23
|
+
id: string
|
|
24
|
+
type:
|
|
25
|
+
| 'retrieve:started'
|
|
26
|
+
| 'retrieve:completed'
|
|
27
|
+
| 'persist:started'
|
|
28
|
+
| 'persist:completed'
|
|
29
|
+
| 'error'
|
|
30
|
+
timestamp: number
|
|
31
|
+
adapter: string
|
|
32
|
+
/** recall:started — the recall query (last user text). */
|
|
33
|
+
query?: string
|
|
34
|
+
/** recall:completed. */
|
|
35
|
+
fragmentCount?: number
|
|
36
|
+
hasTools?: boolean
|
|
37
|
+
systemPromptChars?: number
|
|
38
|
+
/** persist:completed. */
|
|
39
|
+
receiptCount?: number
|
|
40
|
+
okCount?: number
|
|
41
|
+
/** recall:completed / persist:completed. */
|
|
42
|
+
durationMs?: number
|
|
43
|
+
/** error. */
|
|
44
|
+
phase?: 'recall' | 'save'
|
|
45
|
+
error?: { name: string; message: string }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A flat fact row, mirroring `MemoryFact` from `@tanstack/ai-memory`. */
|
|
49
|
+
export interface MemoryFactRecord {
|
|
50
|
+
id: string
|
|
51
|
+
text: string
|
|
52
|
+
source?: string
|
|
53
|
+
createdAt?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Latest `inspect()` + `listFacts()` snapshot pushed via `memory:snapshot`. */
|
|
57
|
+
export interface MemorySnapshotRecord {
|
|
58
|
+
takenAt: string
|
|
59
|
+
data: unknown
|
|
60
|
+
facts: Array<MemoryFactRecord>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Everything known about memory for a single composite scope. */
|
|
64
|
+
export interface MemoryScopeState {
|
|
65
|
+
key: string
|
|
66
|
+
threadId: string
|
|
67
|
+
userId?: string
|
|
68
|
+
tenantId?: string
|
|
69
|
+
namespace?: string
|
|
70
|
+
/** Most recent adapter id seen for this scope. */
|
|
71
|
+
adapter?: string
|
|
72
|
+
events: Array<MemoryEventRecord>
|
|
73
|
+
snapshot?: MemorySnapshotRecord
|
|
74
|
+
lastActivity: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface MemoryRegistryState {
|
|
78
|
+
scopes: Record<string, MemoryScopeState>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createMemoryRegistryState(): MemoryRegistryState {
|
|
82
|
+
return { scopes: {} }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Escape `:` / `\` / `_` so composite keys cannot collide when a dim contains
|
|
87
|
+
* the separator or the unset sentinel (mirrors redis scope-key hardening).
|
|
88
|
+
*/
|
|
89
|
+
function escapeScopeDim(value: string): string {
|
|
90
|
+
return value.replace(/[\\:_]/g, '\\$&')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Unset optional dims serialize as `_` (same convention as the redis adapter). */
|
|
94
|
+
function scopeDim(value: string | undefined): string {
|
|
95
|
+
return value != null && value.length > 0 ? escapeScopeDim(value) : '_'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Stable scope key:
|
|
100
|
+
* `{tenantId|_}:{userId|_}:{threadId}:{namespace|_}`.
|
|
101
|
+
* Absent scope (e.g. `memory:error` before resolve) buckets to `(unknown)`.
|
|
102
|
+
*/
|
|
103
|
+
export function memoryScopeKey(scope: MemoryScopeLite | undefined): string {
|
|
104
|
+
if (!scope) return '(unknown)'
|
|
105
|
+
return `${scopeDim(scope.tenantId)}:${scopeDim(scope.userId)}:${escapeScopeDim(scope.threadId)}:${scopeDim(scope.namespace)}`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Human-readable label for the Memory panel scope picker. */
|
|
109
|
+
export function memoryScopeLabel(entry: MemoryScopeState): string {
|
|
110
|
+
if (entry.key === '(unknown)' || !entry.threadId) return '(unknown)'
|
|
111
|
+
const parts = [
|
|
112
|
+
entry.tenantId,
|
|
113
|
+
entry.userId,
|
|
114
|
+
entry.threadId,
|
|
115
|
+
entry.namespace,
|
|
116
|
+
].filter((p): p is string => p != null && p.length > 0)
|
|
117
|
+
return parts.join(' · ')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const MAX_EVENTS_PER_SCOPE = 200
|
|
121
|
+
|
|
122
|
+
function ensureScope(
|
|
123
|
+
state: MemoryRegistryState,
|
|
124
|
+
scope: MemoryScopeLite | undefined,
|
|
125
|
+
): MemoryScopeState {
|
|
126
|
+
const key = memoryScopeKey(scope)
|
|
127
|
+
let entry = state.scopes[key]
|
|
128
|
+
if (!entry) {
|
|
129
|
+
entry = {
|
|
130
|
+
key,
|
|
131
|
+
threadId: scope?.threadId ?? '',
|
|
132
|
+
userId: scope?.userId,
|
|
133
|
+
tenantId: scope?.tenantId,
|
|
134
|
+
namespace: scope?.namespace,
|
|
135
|
+
events: [],
|
|
136
|
+
lastActivity: 0,
|
|
137
|
+
}
|
|
138
|
+
state.scopes[key] = entry
|
|
139
|
+
}
|
|
140
|
+
// Only merge metadata when we have a real scope (not the unscoped error bucket).
|
|
141
|
+
if (scope) {
|
|
142
|
+
if (scope.userId) entry.userId = scope.userId
|
|
143
|
+
if (scope.tenantId) entry.tenantId = scope.tenantId
|
|
144
|
+
if (scope.namespace) entry.namespace = scope.namespace
|
|
145
|
+
}
|
|
146
|
+
return entry
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let fallbackCounter = 0
|
|
150
|
+
|
|
151
|
+
function eventId(
|
|
152
|
+
payload: { eventId?: string },
|
|
153
|
+
type: string,
|
|
154
|
+
ts: number,
|
|
155
|
+
): string {
|
|
156
|
+
if (payload.eventId && payload.eventId.length > 0) return payload.eventId
|
|
157
|
+
return `${type}:${ts}:${fallbackCounter++}`
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
type MemoryEventPayload =
|
|
161
|
+
| ({ type: 'retrieve:started' } & MemoryRetrieveStartedEvent)
|
|
162
|
+
| ({ type: 'retrieve:completed' } & MemoryRetrieveCompletedEvent)
|
|
163
|
+
| ({ type: 'persist:started' } & MemoryPersistStartedEvent)
|
|
164
|
+
| ({ type: 'persist:completed' } & MemoryPersistCompletedEvent)
|
|
165
|
+
| ({ type: 'error' } & MemoryErrorEvent)
|
|
166
|
+
|
|
167
|
+
/** Append one `memory:*` operation event to its scope's timeline. */
|
|
168
|
+
export function applyMemoryEvent(
|
|
169
|
+
state: MemoryRegistryState,
|
|
170
|
+
event: MemoryEventPayload,
|
|
171
|
+
): void {
|
|
172
|
+
const entry = ensureScope(state, event.scope)
|
|
173
|
+
entry.adapter = event.adapter
|
|
174
|
+
entry.lastActivity = Math.max(entry.lastActivity, event.timestamp)
|
|
175
|
+
|
|
176
|
+
const record: MemoryEventRecord = {
|
|
177
|
+
id: eventId(event, event.type, event.timestamp),
|
|
178
|
+
type: event.type,
|
|
179
|
+
timestamp: event.timestamp,
|
|
180
|
+
adapter: event.adapter,
|
|
181
|
+
}
|
|
182
|
+
switch (event.type) {
|
|
183
|
+
case 'retrieve:started':
|
|
184
|
+
record.query = event.query
|
|
185
|
+
break
|
|
186
|
+
case 'retrieve:completed':
|
|
187
|
+
record.fragmentCount = event.fragmentCount
|
|
188
|
+
record.hasTools = event.hasTools
|
|
189
|
+
record.systemPromptChars = event.systemPromptChars
|
|
190
|
+
record.durationMs = event.durationMs
|
|
191
|
+
break
|
|
192
|
+
case 'persist:completed':
|
|
193
|
+
record.receiptCount = event.receiptCount
|
|
194
|
+
record.okCount = event.okCount
|
|
195
|
+
record.durationMs = event.durationMs
|
|
196
|
+
break
|
|
197
|
+
case 'error':
|
|
198
|
+
record.phase = event.phase
|
|
199
|
+
record.error = event.error
|
|
200
|
+
break
|
|
201
|
+
case 'persist:started':
|
|
202
|
+
break
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
entry.events.push(record)
|
|
206
|
+
if (entry.events.length > MAX_EVENTS_PER_SCOPE) {
|
|
207
|
+
entry.events.splice(0, entry.events.length - MAX_EVENTS_PER_SCOPE)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Replace a scope's stored-state snapshot from a `memory:snapshot` event. */
|
|
212
|
+
export function applyMemorySnapshot(
|
|
213
|
+
state: MemoryRegistryState,
|
|
214
|
+
event: MemorySnapshotEvent,
|
|
215
|
+
): void {
|
|
216
|
+
const entry = ensureScope(state, event.scope)
|
|
217
|
+
entry.adapter = event.adapter
|
|
218
|
+
entry.lastActivity = Math.max(entry.lastActivity, event.timestamp)
|
|
219
|
+
entry.snapshot = {
|
|
220
|
+
takenAt: event.takenAt,
|
|
221
|
+
data: event.data,
|
|
222
|
+
facts: event.facts,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function clearMemoryRegistry(state: MemoryRegistryState): void {
|
|
227
|
+
state.scopes = {}
|
|
228
|
+
}
|
package/src/styles/use-styles.ts
CHANGED
|
@@ -1898,6 +1898,118 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
1898
1898
|
padding: 0 ${size[3]};
|
|
1899
1899
|
`,
|
|
1900
1900
|
},
|
|
1901
|
+
// MemoryPanel component styles
|
|
1902
|
+
memoryPanel: {
|
|
1903
|
+
container: css`
|
|
1904
|
+
display: flex;
|
|
1905
|
+
flex: 1;
|
|
1906
|
+
min-height: 0;
|
|
1907
|
+
flex-direction: column;
|
|
1908
|
+
gap: ${size[4]};
|
|
1909
|
+
overflow-y: auto;
|
|
1910
|
+
padding: ${size[4]};
|
|
1911
|
+
`,
|
|
1912
|
+
toolbar: css`
|
|
1913
|
+
display: flex;
|
|
1914
|
+
align-items: center;
|
|
1915
|
+
justify-content: space-between;
|
|
1916
|
+
gap: ${size[3]};
|
|
1917
|
+
`,
|
|
1918
|
+
scopeControls: css`
|
|
1919
|
+
display: flex;
|
|
1920
|
+
align-items: center;
|
|
1921
|
+
gap: ${size[2]};
|
|
1922
|
+
min-width: 0;
|
|
1923
|
+
`,
|
|
1924
|
+
scopeSelect: css`
|
|
1925
|
+
max-width: 220px;
|
|
1926
|
+
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
|
|
1927
|
+
border-radius: ${border.radius.sm};
|
|
1928
|
+
background: ${t(colors.white, colors.darkGray[800])};
|
|
1929
|
+
color: ${t(colors.gray[900], colors.gray[100])};
|
|
1930
|
+
font-family: ${fontFamily.mono};
|
|
1931
|
+
font-size: ${fontSize.xs};
|
|
1932
|
+
padding: ${size[1]} ${size[2]};
|
|
1933
|
+
`,
|
|
1934
|
+
clearButton: css`
|
|
1935
|
+
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
|
|
1936
|
+
border-radius: ${border.radius.sm};
|
|
1937
|
+
background: ${t(colors.gray[50], colors.darkGray[700])};
|
|
1938
|
+
color: ${t(colors.gray[700], colors.gray[200])};
|
|
1939
|
+
cursor: pointer;
|
|
1940
|
+
font-size: ${fontSize.xs};
|
|
1941
|
+
padding: ${size[1]} ${size[2]};
|
|
1942
|
+
&:hover {
|
|
1943
|
+
border-color: ${t(colors.blue[300], colors.blue[700])};
|
|
1944
|
+
}
|
|
1945
|
+
`,
|
|
1946
|
+
empty: css`
|
|
1947
|
+
color: ${t(colors.gray[500], colors.gray[500])};
|
|
1948
|
+
font-size: ${fontSize.sm};
|
|
1949
|
+
padding: ${size[4]};
|
|
1950
|
+
text-align: center;
|
|
1951
|
+
`,
|
|
1952
|
+
section: css`
|
|
1953
|
+
display: flex;
|
|
1954
|
+
flex-direction: column;
|
|
1955
|
+
gap: ${size[2]};
|
|
1956
|
+
`,
|
|
1957
|
+
sectionTitle: css`
|
|
1958
|
+
color: ${t(colors.gray[600], colors.gray[300])};
|
|
1959
|
+
font-size: ${fontSize.xs};
|
|
1960
|
+
font-weight: ${font.weight.semibold};
|
|
1961
|
+
text-transform: uppercase;
|
|
1962
|
+
letter-spacing: 0.04em;
|
|
1963
|
+
`,
|
|
1964
|
+
sectionEmpty: css`
|
|
1965
|
+
color: ${t(colors.gray[500], colors.gray[500])};
|
|
1966
|
+
font-size: ${fontSize.xs};
|
|
1967
|
+
`,
|
|
1968
|
+
list: css`
|
|
1969
|
+
display: flex;
|
|
1970
|
+
flex-direction: column;
|
|
1971
|
+
gap: ${size[1]};
|
|
1972
|
+
`,
|
|
1973
|
+
row: css`
|
|
1974
|
+
display: flex;
|
|
1975
|
+
flex-direction: column;
|
|
1976
|
+
gap: 2px;
|
|
1977
|
+
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
|
|
1978
|
+
border-radius: ${border.radius.sm};
|
|
1979
|
+
background: ${t(colors.white, colors.darkGray[800])};
|
|
1980
|
+
padding: ${size[2]};
|
|
1981
|
+
`,
|
|
1982
|
+
rowError: css`
|
|
1983
|
+
border-color: ${t(colors.red[300], colors.red[700])};
|
|
1984
|
+
background: ${t(colors.red[50], colors.red[900] + '20')};
|
|
1985
|
+
`,
|
|
1986
|
+
rowHeader: css`
|
|
1987
|
+
display: flex;
|
|
1988
|
+
align-items: center;
|
|
1989
|
+
gap: ${size[2]};
|
|
1990
|
+
font-size: ${fontSize.xs};
|
|
1991
|
+
color: ${t(colors.gray[500], colors.gray[400])};
|
|
1992
|
+
`,
|
|
1993
|
+
badge: css`
|
|
1994
|
+
border-radius: ${border.radius.xs};
|
|
1995
|
+
background: ${t(colors.blue[50], colors.blue[900] + '30')};
|
|
1996
|
+
color: ${t(colors.blue[700], colors.blue[300])};
|
|
1997
|
+
font-family: ${fontFamily.mono};
|
|
1998
|
+
font-size: 10px;
|
|
1999
|
+
padding: 1px 6px;
|
|
2000
|
+
`,
|
|
2001
|
+
time: css`
|
|
2002
|
+
margin-left: auto;
|
|
2003
|
+
font-family: ${fontFamily.mono};
|
|
2004
|
+
font-size: 10px;
|
|
2005
|
+
`,
|
|
2006
|
+
rowText: css`
|
|
2007
|
+
color: ${t(colors.gray[900], colors.gray[100])};
|
|
2008
|
+
font-size: ${fontSize.sm};
|
|
2009
|
+
white-space: pre-wrap;
|
|
2010
|
+
word-break: break-word;
|
|
2011
|
+
`,
|
|
2012
|
+
},
|
|
1901
2013
|
// ConversationDetails component styles
|
|
1902
2014
|
conversationDetails: {
|
|
1903
2015
|
emptyState: css`
|