@adecore/agents-react 0.21.1 → 0.22.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.
- package/dist/agents/model-requirement.d.ts +6 -0
- package/dist/agents/model-requirement.js +26 -0
- package/dist/agents/status-look.js +2 -3
- package/dist/chat/chat-client.d.ts +3 -2
- package/dist/chat/chat-client.js +4 -2
- package/dist/chat/drafts.d.ts +2 -1
- package/dist/chat/drafts.js +21 -17
- package/dist/chat/logic/ansi.js +5 -1
- package/dist/chat/logic/fork.js +8 -9
- package/dist/chat/logic/limit.js +17 -14
- package/dist/chat/logic/picker-entries.d.ts +10 -0
- package/dist/chat/logic/picker-entries.js +19 -0
- package/dist/chat/logic/resume-compaction.js +2 -11
- package/dist/chat/logic/scrubber.js +9 -18
- package/dist/chat/logic/timeline.d.ts +5 -0
- package/dist/chat/logic/timeline.js +25 -11
- package/dist/chat/logic/tool-catalog.js +3 -4
- package/dist/chat/mentions.js +5 -6
- package/dist/chat/prompt-hold.d.ts +8 -0
- package/dist/chat/prompt-hold.js +16 -0
- package/dist/chat/quote.d.ts +4 -0
- package/dist/chat/quote.js +19 -0
- package/dist/chat/runtime-modes.js +2 -4
- package/dist/chat/stash.d.ts +8 -2
- package/dist/chat/stash.js +22 -8
- package/dist/chat/subagent-list.d.ts +7 -1
- package/dist/chat/subagent-list.js +13 -1
- package/dist/chat/thumbnails.js +3 -4
- package/dist/chat/timeline-scroll.d.ts +6 -2
- package/dist/chat/timeline-scroll.js +45 -46
- package/dist/chat/ui/BookmarkSubmenu.js +1 -5
- package/dist/chat/ui/ChatActivity.js +22 -8
- package/dist/chat/ui/CodeBlock.js +2 -3
- package/dist/chat/ui/Composer.js +160 -88
- package/dist/chat/ui/ComposerInput.d.ts +3 -1
- package/dist/chat/ui/ComposerInput.js +13 -4
- package/dist/chat/ui/Markdown.js +11 -11
- package/dist/chat/ui/Pickers.js +31 -27
- package/dist/chat/ui/PromptComposer.d.ts +8 -1
- package/dist/chat/ui/PromptComposer.js +18 -6
- package/dist/chat/ui/QuoteButton.js +4 -5
- package/dist/chat/ui/RunSettings.js +8 -2
- package/dist/chat/ui/Scrubber.js +2 -3
- package/dist/chat/ui/SubagentControls.d.ts +4 -2
- package/dist/chat/ui/SubagentControls.js +13 -11
- package/dist/chat/ui/SubagentInfo.d.ts +1 -1
- package/dist/chat/ui/SubagentInfo.js +9 -15
- package/dist/chat/ui/SubagentStopButton.js +2 -1
- package/dist/chat/ui/Timeline.js +39 -21
- package/dist/chat/ui/TimelineMenu.js +3 -9
- package/dist/chat/ui/UnifiedDiff.js +3 -5
- package/dist/chat/ui/VisualFrame.js +3 -4
- package/dist/chat/ui/composer/keys.js +19 -11
- package/dist/chat/ui/composer/menu-aria.d.ts +10 -0
- package/dist/chat/ui/composer/menu-aria.js +18 -0
- package/dist/chat/ui/intelligent-ui/render-context.d.ts +1 -1
- package/dist/chat/ui/quote-selection.d.ts +5 -2
- package/dist/chat/ui/quote-selection.js +3 -2
- package/dist/chat/ui/rows/MessageRows.js +26 -8
- package/dist/chat/ui/rows/Rows.js +1 -1
- package/dist/chat/ui/rows/WorkRows.d.ts +7 -4
- package/dist/chat/ui/rows/WorkRows.js +22 -17
- package/dist/chat/visual-viewport.d.ts +1 -1
- package/dist/chat/visual-viewport.js +9 -8
- package/dist/host.d.ts +1 -0
- package/dist/host.js +1 -0
- package/dist/locales/en/agent-chat.json +30 -7
- package/dist/locales/nl/agent-chat.json +30 -7
- package/dist/port-transport.js +4 -8
- package/dist/state/chats.d.ts +2 -1
- package/dist/state/chats.js +29 -15
- package/dist/state/usage.js +44 -49
- package/dist/storage.js +7 -7
- package/dist/theme.css +6 -14
- package/dist/usage/LimitsList.js +1 -2
- package/dist/usage/UsageBreakdown.js +1 -3
- package/dist/usage/UsageDialog.js +2 -4
- package/dist/usage/format.js +6 -6
- package/dist/usage/money.js +1 -5
- package/dist/usage/summary.js +2 -4
- package/package.json +24 -4
- package/src/agents/model-requirement.ts +36 -0
- package/src/agents/status-look.ts +2 -3
- package/src/chat/chat-client.ts +7 -3
- package/src/chat/drafts.ts +33 -21
- package/src/chat/logic/ansi.ts +5 -1
- package/src/chat/logic/fork.ts +8 -9
- package/src/chat/logic/limit.ts +17 -16
- package/src/chat/logic/picker-entries.ts +25 -0
- package/src/chat/logic/resume-compaction.ts +2 -11
- package/src/chat/logic/scrubber.ts +13 -17
- package/src/chat/logic/timeline.ts +39 -13
- package/src/chat/logic/tool-catalog.ts +7 -6
- package/src/chat/mentions.ts +5 -6
- package/src/chat/prompt-hold.ts +27 -0
- package/src/chat/quote.ts +20 -0
- package/src/chat/runtime-modes.ts +2 -4
- package/src/chat/stash.ts +32 -8
- package/src/chat/subagent-list.ts +19 -5
- package/src/chat/thumbnails.ts +3 -4
- package/src/chat/timeline-scroll.ts +58 -53
- package/src/chat/ui/BookmarkSubmenu.tsx +1 -5
- package/src/chat/ui/ChatActivity.tsx +35 -9
- package/src/chat/ui/CodeBlock.tsx +2 -3
- package/src/chat/ui/Composer.tsx +263 -138
- package/src/chat/ui/ComposerInput.tsx +18 -3
- package/src/chat/ui/Markdown.tsx +13 -17
- package/src/chat/ui/Pickers.tsx +56 -39
- package/src/chat/ui/PromptComposer.tsx +37 -8
- package/src/chat/ui/QuoteButton.tsx +5 -5
- package/src/chat/ui/RunSettings.tsx +9 -2
- package/src/chat/ui/Scrubber.tsx +2 -3
- package/src/chat/ui/SubagentControls.tsx +23 -22
- package/src/chat/ui/SubagentInfo.tsx +24 -42
- package/src/chat/ui/SubagentStopButton.tsx +2 -1
- package/src/chat/ui/SubagentTimeline.tsx +1 -2
- package/src/chat/ui/Timeline.tsx +44 -24
- package/src/chat/ui/TimelineMenu.tsx +3 -9
- package/src/chat/ui/UnifiedDiff.tsx +3 -5
- package/src/chat/ui/VisualFrame.tsx +3 -4
- package/src/chat/ui/composer/keys.ts +21 -11
- package/src/chat/ui/composer/menu-aria.ts +29 -0
- package/src/chat/ui/intelligent-ui/render-context.ts +2 -2
- package/src/chat/ui/quote-selection.ts +6 -4
- package/src/chat/ui/rehype-chips.ts +1 -1
- package/src/chat/ui/rehype-fade.ts +1 -1
- package/src/chat/ui/remark-html-as-text.ts +1 -1
- package/src/chat/ui/rows/MessageRows.tsx +47 -9
- package/src/chat/ui/rows/Rows.tsx +3 -1
- package/src/chat/ui/rows/WorkRows.tsx +101 -70
- package/src/chat/visual-viewport.ts +10 -8
- package/src/host.ts +4 -3
- package/src/locales/en/agent-chat.json +30 -7
- package/src/locales/nl/agent-chat.json +30 -7
- package/src/port-transport.ts +4 -8
- package/src/state/chats.ts +42 -16
- package/src/state/usage.ts +49 -60
- package/src/storage.ts +7 -7
- package/src/theme.css +6 -14
- package/src/usage/LimitsList.tsx +2 -4
- package/src/usage/UsageBreakdown.tsx +2 -5
- package/src/usage/UsageDialog.tsx +2 -4
- package/src/usage/format.ts +6 -6
- package/src/usage/money.ts +1 -5
- package/src/usage/summary.ts +2 -4
package/src/state/chats.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
import { useContext, useMemo } from 'react';
|
|
1
2
|
import { create } from 'zustand';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
3
|
+
import type {
|
|
4
|
+
AgentKind,
|
|
5
|
+
ChatApprovalItem,
|
|
6
|
+
ChatBookmark,
|
|
7
|
+
ChatEvent,
|
|
8
|
+
ChatHistoryResult,
|
|
9
|
+
ChatInfo,
|
|
10
|
+
ChatItem,
|
|
11
|
+
ChatQuestionItem,
|
|
12
|
+
ChatVisual
|
|
13
|
+
} from '@adecore/agent-contracts';
|
|
14
|
+
import { ChatScopeContext, useChatScope } from '../scope';
|
|
4
15
|
|
|
5
16
|
export interface ChatState {
|
|
6
17
|
info: ChatInfo;
|
|
@@ -194,6 +205,15 @@ export function applyEvent(state: ChatState, event: ChatEvent): ChatState {
|
|
|
194
205
|
}
|
|
195
206
|
}
|
|
196
207
|
|
|
208
|
+
/* Fields of a held chat that are not part of its thread; a chat this client does not hold stays as it is. */
|
|
209
|
+
function withFields(store: ChatsStore, key: string, fields: Pick<ChatState, 'bookmarks' | 'visuals'>): Partial<ChatsStore> {
|
|
210
|
+
const current = store.byKey[key];
|
|
211
|
+
if (!current) {
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
return { byKey: { ...store.byKey, [key]: { ...current, ...fields } } };
|
|
215
|
+
}
|
|
216
|
+
|
|
197
217
|
export const useChats = create<ChatsStore>((set) => ({
|
|
198
218
|
byKey: {},
|
|
199
219
|
statusByKey: {},
|
|
@@ -247,22 +267,10 @@ export const useChats = create<ChatsStore>((set) => ({
|
|
|
247
267
|
});
|
|
248
268
|
},
|
|
249
269
|
bookmarks(key, bookmarks) {
|
|
250
|
-
set((s) => {
|
|
251
|
-
const current = s.byKey[key];
|
|
252
|
-
if (!current) {
|
|
253
|
-
return {};
|
|
254
|
-
}
|
|
255
|
-
return { byKey: { ...s.byKey, [key]: { ...current, bookmarks } } };
|
|
256
|
-
});
|
|
270
|
+
set((s) => withFields(s, key, { bookmarks }));
|
|
257
271
|
},
|
|
258
272
|
visuals(key, visuals) {
|
|
259
|
-
set((s) => {
|
|
260
|
-
const current = s.byKey[key];
|
|
261
|
-
if (!current) {
|
|
262
|
-
return {};
|
|
263
|
-
}
|
|
264
|
-
return { byKey: { ...s.byKey, [key]: { ...current, visuals } } };
|
|
265
|
-
});
|
|
273
|
+
set((s) => withFields(s, key, { visuals }));
|
|
266
274
|
},
|
|
267
275
|
forget(key) {
|
|
268
276
|
set((s) => {
|
|
@@ -297,6 +305,24 @@ export function useChatRow<T>(chatId: string, select: (row: ChatState | undefine
|
|
|
297
305
|
return useChats((s) => select(s.byKey[keyOf(chatId)]));
|
|
298
306
|
}
|
|
299
307
|
|
|
308
|
+
/*
|
|
309
|
+
* The models the CLI of this provider reported running in the chats this client holds of the host.
|
|
310
|
+
* A model it ran is proof the CLI offers it, whatever the version says. Outside a scope there is none.
|
|
311
|
+
*/
|
|
312
|
+
export function useReportedModels(provider: AgentKind): ReadonlySet<string> {
|
|
313
|
+
const scope = useContext(ChatScopeContext);
|
|
314
|
+
const joined = useChats((s) => {
|
|
315
|
+
const names = new Set<string>();
|
|
316
|
+
for (const [key, state] of Object.entries(s.statusByKey)) {
|
|
317
|
+
if (scope?.owns(key) === true && state.info.provider === provider && state.info.model !== null) {
|
|
318
|
+
names.add(state.info.model);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return [...names].sort().join('\n');
|
|
322
|
+
});
|
|
323
|
+
return useMemo(() => new Set(joined === '' ? [] : joined.split('\n')), [joined]);
|
|
324
|
+
}
|
|
325
|
+
|
|
300
326
|
/* The item as the thread holds it now, for a row derived from the structure, which a delta leaves alone. */
|
|
301
327
|
export function useCurrentItem<T extends ChatItem>(chatId: string, derived: T): T {
|
|
302
328
|
return useChatRow(chatId, (row) => {
|
package/src/state/usage.ts
CHANGED
|
@@ -51,14 +51,6 @@ function parsePreferences(raw: string | null): Preferences {
|
|
|
51
51
|
|
|
52
52
|
const storage = persistedJson(storageKey, parsePreferences, DEFAULTS, () => chatStorageLegacyKeys('usage'));
|
|
53
53
|
|
|
54
|
-
function readPreferences(): Preferences {
|
|
55
|
-
return storage.read();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function persist(preferences: Preferences): void {
|
|
59
|
-
storage.write(preferences);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
54
|
/* The three stored choices out of the store, so a setter writes the other two back unchanged. */
|
|
63
55
|
function chosen(state: Preferences): Preferences {
|
|
64
56
|
return { period: state.period, metric: state.metric, currency: state.currency };
|
|
@@ -73,10 +65,7 @@ export function dayOf(date: Date): string {
|
|
|
73
65
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
74
66
|
}
|
|
75
67
|
|
|
76
|
-
/*
|
|
77
|
-
* What a period asks for. Today counts from local midnight in hours, the way the commit log already
|
|
78
|
-
* groups days, so every other period reads as one bar per calendar day.
|
|
79
|
-
*/
|
|
68
|
+
/* What a period asks for: today in hours from local midnight, every other period a bar per calendar day. */
|
|
80
69
|
export function windowFor(period: UsagePeriod, now = new Date()): UsageSummaryPayload {
|
|
81
70
|
const days = USAGE_PERIODS.find((entry) => entry.id === period)?.days ?? 7;
|
|
82
71
|
const from = new Date(now.getFullYear(), now.getMonth(), now.getDate() - (days - 1));
|
|
@@ -116,10 +105,8 @@ const EMPTY: UsageRow = { asked: null, summary: null, loading: false, failed: fa
|
|
|
116
105
|
interface UsageStore extends Preferences {
|
|
117
106
|
byScope: Record<string, UsageRow>;
|
|
118
107
|
/*
|
|
119
|
-
* The host the usage page
|
|
120
|
-
*
|
|
121
|
-
* this client's knowledge of it, and the numbers worth opening on are those of the host you work
|
|
122
|
-
* on. It does survive closing the page, which is what makes browsing two hosts bearable.
|
|
108
|
+
* The host the usage page shows, by scope id, or null for the one around it. Not stored: a
|
|
109
|
+
* remembered host outlives this client's knowledge of it. It does survive closing the page.
|
|
123
110
|
*/
|
|
124
111
|
chosen: string | null;
|
|
125
112
|
/* Which host's numbers the page shows; null hands it back to the one around it. */
|
|
@@ -137,56 +124,58 @@ interface UsageStore extends Preferences {
|
|
|
137
124
|
|
|
138
125
|
/* Reads the period, metric and currency again, once another window of the same origin wrote them. */
|
|
139
126
|
export function reloadUsagePreferences(): void {
|
|
140
|
-
useUsageStore.setState(
|
|
127
|
+
useUsageStore.setState(storage.read());
|
|
141
128
|
}
|
|
142
129
|
|
|
143
130
|
export const useUsageStore = createChatStore<UsageStore>(
|
|
144
|
-
(set, get) =>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
131
|
+
(set, get) => {
|
|
132
|
+
const choosePreference = (change: Partial<Preferences>): void => {
|
|
133
|
+
storage.write({ ...chosen(get()), ...change });
|
|
134
|
+
set(change);
|
|
135
|
+
};
|
|
136
|
+
const patchRow = (scopeId: string, change: Partial<UsageRow>): void => {
|
|
137
|
+
set({ byScope: { ...get().byScope, [scopeId]: { ...(get().byScope[scopeId] ?? EMPTY), ...change } } });
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
...DEFAULTS,
|
|
141
|
+
byScope: {},
|
|
142
|
+
chosen: null,
|
|
143
|
+
choose(scopeId) {
|
|
144
|
+
set({ chosen: scopeId });
|
|
145
|
+
},
|
|
146
|
+
setPeriod(period) {
|
|
147
|
+
choosePreference({ period });
|
|
148
|
+
},
|
|
149
|
+
setMetric(metric) {
|
|
150
|
+
choosePreference({ metric });
|
|
151
|
+
},
|
|
152
|
+
setCurrency(currency) {
|
|
153
|
+
choosePreference({ currency });
|
|
154
|
+
},
|
|
155
|
+
setLoading(scopeId, loading) {
|
|
156
|
+
patchRow(scopeId, { loading });
|
|
157
|
+
},
|
|
158
|
+
receive(scopeId, asked, summary) {
|
|
159
|
+
patchRow(scopeId, { asked, summary, loading: false, failed: false });
|
|
160
|
+
},
|
|
161
|
+
fail(scopeId) {
|
|
162
|
+
patchRow(scopeId, { loading: false, failed: true });
|
|
163
|
+
},
|
|
164
|
+
setLimits(scopeId, limits) {
|
|
165
|
+
patchRow(scopeId, { limits });
|
|
166
|
+
},
|
|
167
|
+
forget(scopeId) {
|
|
168
|
+
const { [scopeId]: _gone, ...rest } = get().byScope;
|
|
169
|
+
set({ byScope: rest });
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
(store) => store.setState(storage.read())
|
|
183
174
|
);
|
|
184
175
|
|
|
185
176
|
/*
|
|
186
|
-
* The preferences
|
|
187
|
-
*
|
|
188
|
-
* limit bars beside the work) reads the host around it, so browsing another host's spend never
|
|
189
|
-
* moves the bars that say how much of the plan the agents beside you have left.
|
|
177
|
+
* The viewer's preferences over the numbers of the host in scope. The usage page renders its picked
|
|
178
|
+
* host as a scope of its own, so browsing another host's spend never moves the limit bars outside it.
|
|
190
179
|
*/
|
|
191
180
|
export type UsageView = Preferences & UsageRow;
|
|
192
181
|
|
package/src/storage.ts
CHANGED
|
@@ -88,25 +88,25 @@ export function createChatStore<T>(initial: StateCreator<T>, hydrate: (store: St
|
|
|
88
88
|
hydrate(store);
|
|
89
89
|
};
|
|
90
90
|
hydrators.push(hydrateOnce);
|
|
91
|
-
const
|
|
91
|
+
const ready = (): void => {
|
|
92
92
|
hydrateChatStorage();
|
|
93
93
|
hydrateOnce();
|
|
94
|
+
};
|
|
95
|
+
const useStore = ((...args: Parameters<typeof store>) => {
|
|
96
|
+
ready();
|
|
94
97
|
return store(...args);
|
|
95
98
|
}) as typeof store;
|
|
96
99
|
Object.assign(useStore, store);
|
|
97
100
|
useStore.getState = () => {
|
|
98
|
-
|
|
99
|
-
hydrateOnce();
|
|
101
|
+
ready();
|
|
100
102
|
return store.getState();
|
|
101
103
|
};
|
|
102
104
|
useStore.subscribe = (...args) => {
|
|
103
|
-
|
|
104
|
-
hydrateOnce();
|
|
105
|
+
ready();
|
|
105
106
|
return store.subscribe(...args);
|
|
106
107
|
};
|
|
107
108
|
useStore.setState = ((...args: Parameters<typeof store.setState>) => {
|
|
108
|
-
|
|
109
|
-
hydrateOnce();
|
|
109
|
+
ready();
|
|
110
110
|
store.setState(...args);
|
|
111
111
|
}) as typeof store.setState;
|
|
112
112
|
return useStore;
|
package/src/theme.css
CHANGED
|
@@ -76,8 +76,6 @@
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
@layer components {
|
|
79
|
-
/* ---------------------------------------------------------------- Chat */
|
|
80
|
-
|
|
81
79
|
/* Text in a chat is there to be read and copied, so the thread selects even though the app does
|
|
82
80
|
not. Its chrome stays out of it: a drag on a fold header or a chip would select the row.
|
|
83
81
|
The pretty wrap from `body` would be recomputed on the growing paragraph every frame a reply
|
|
@@ -160,18 +158,12 @@
|
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
/*
|
|
163
|
-
* Markdown, outside every layer on purpose
|
|
164
|
-
* and
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
* Markdown anywhere in the app: the chat timeline, a note node and the file preview all render
|
|
170
|
-
* the same `Markdown` component, which carries `prose prose-sm max-w-none text-sm` next to this
|
|
171
|
-
* class. Prose brings the rhythm; everything here is what the app decides instead of the plugin.
|
|
172
|
-
* The semantic tokens flip with the theme on their own, so the prose variables read from them
|
|
173
|
-
* and `prose-invert` is a second source of truth the app does not need. The size comes from the
|
|
174
|
-
* `text-sm` utility, which a node, a view and the file preview each move.
|
|
161
|
+
* Markdown, outside every layer on purpose: the typography plugin registers `.prose` as a utility,
|
|
162
|
+
* and only unlayered styles are sure to land on top of it.
|
|
163
|
+
*
|
|
164
|
+
* Every `Markdown` carries `prose prose-sm max-w-none text-sm` next to this class. Prose brings the
|
|
165
|
+
* rhythm; everything here is what the app decides instead. The semantic tokens flip with the theme on
|
|
166
|
+
* their own, so the prose variables read from them and `prose-invert` is not needed.
|
|
175
167
|
*/
|
|
176
168
|
.prose.chat-markdown {
|
|
177
169
|
--tw-prose-body: var(--text);
|
package/src/usage/LimitsList.tsx
CHANGED
|
@@ -10,8 +10,7 @@ import { ProviderLogo } from '../agents/ProviderLogo';
|
|
|
10
10
|
import { PROVIDER_COLORS, PROVIDER_LABELS } from './format';
|
|
11
11
|
import { accountNote, explain, isSignedOut, nextReset, type LimitAccount, type LimitGroup } from './limit-groups';
|
|
12
12
|
|
|
13
|
-
/* Red where a window is nearly spent, amber where it is worth knowing
|
|
14
|
-
is not news, so it takes the text color rather than a hue that competes with the two that are. */
|
|
13
|
+
/* Red where a window is nearly spent, amber where it is worth knowing; room to spare is not news and takes the text color. */
|
|
15
14
|
function toneOf(used: number): string {
|
|
16
15
|
return used >= 0.9 ? 'bg-status-error' : used >= 0.7 ? 'bg-status-needs-you' : 'bg-text';
|
|
17
16
|
}
|
|
@@ -93,8 +92,7 @@ export function WindowBar({ window, now, compact }: { window: UsageWindow; now:
|
|
|
93
92
|
{bar}
|
|
94
93
|
</Tooltip>
|
|
95
94
|
)}
|
|
96
|
-
{/* Under the bar
|
|
97
|
-
share that line, and a reset that wrapped would read as two of them. */}
|
|
95
|
+
{/* Under the bar: a weekday and a countdown are too long to share the label's line. */}
|
|
98
96
|
{resetsAt !== null && (
|
|
99
97
|
<p className="text-xs whitespace-nowrap text-text-faint">
|
|
100
98
|
{t('limits.resets', { at: resetsAt })}
|
|
@@ -16,13 +16,11 @@ import { deriveDays } from './summary';
|
|
|
16
16
|
|
|
17
17
|
type Breakdown = 'models' | 'projects' | 'day';
|
|
18
18
|
|
|
19
|
-
/* The row of every table, the same height and the same hover as a commit row in the git panel. */
|
|
20
19
|
const ROW = 'flex h-8 items-center gap-2 rounded-md px-2 text-xs hover:bg-surface-hover';
|
|
21
20
|
|
|
22
21
|
const HEAD = 'flex h-6 items-center gap-2 px-2 text-xs text-text-faint';
|
|
23
22
|
|
|
24
|
-
/*
|
|
25
|
-
again. The first column then starts on the same line as every other section of the page. */
|
|
23
|
+
/* The table pulls the padding that gives a row's hover room back off, so its first column lines up with the page. */
|
|
26
24
|
const TABLE = '-mx-2 flex flex-col';
|
|
27
25
|
|
|
28
26
|
const TABS: readonly Breakdown[] = ['models', 'projects', 'day'];
|
|
@@ -102,8 +100,7 @@ function ProjectRow({ project, top, money }: { project: UsageProject; top: numbe
|
|
|
102
100
|
// The usage on screen is one host's, so the look of a project is that host's too.
|
|
103
101
|
const look = chatHost().useProjectLook(useChatScope().id, project.projectId);
|
|
104
102
|
return (
|
|
105
|
-
/* The mark sits on the title rather than between the two lines, so
|
|
106
|
-
up with the names beside it and not with the paths under them. */
|
|
103
|
+
/* The mark sits on the title rather than between the two lines, so the icons line up with the names. */
|
|
107
104
|
<div className={`${ROW} h-auto items-start py-1.5`}>
|
|
108
105
|
<span className="mt-px flex">
|
|
109
106
|
{look?.mark ?? (
|
|
@@ -3,10 +3,8 @@ import i18next from 'i18next';
|
|
|
3
3
|
import { Dialog } from '@adecore/ui';
|
|
4
4
|
|
|
5
5
|
/*
|
|
6
|
-
* The frame of the usage page
|
|
7
|
-
*
|
|
8
|
-
* open, so nothing is scanned behind a closed dialog. It is a dialog and not a view, since none of it
|
|
9
|
-
* belongs to one project.
|
|
6
|
+
* The frame of the usage page, wider than the settings for the chart and the breakdown. What goes in
|
|
7
|
+
* it mounts only while it is open, so nothing is scanned behind a closed dialog.
|
|
10
8
|
*/
|
|
11
9
|
export function UsageDialog({ open, onOpenChange, children }: { open: boolean; onOpenChange(open: boolean): void; children: ReactNode }) {
|
|
12
10
|
return (
|
package/src/usage/format.ts
CHANGED
|
@@ -5,8 +5,7 @@ export { formatClock } from '@adecore/ui/format';
|
|
|
5
5
|
/* Dollars are what a price table is in; euros are what the page can be set to. */
|
|
6
6
|
export type UsageCurrency = 'USD' | 'EUR';
|
|
7
7
|
|
|
8
|
-
/*
|
|
9
|
-
would be: `$5,480.96` in the US, `$ 5.480,96` in the Netherlands. The labels stay English. */
|
|
8
|
+
/* Read in the region of the machine looking at it: `$5,480.96` in the US, `$ 5.480,96` in the Netherlands. */
|
|
10
9
|
export function formatCount(value: number): string {
|
|
11
10
|
return formatNumber(value);
|
|
12
11
|
}
|
|
@@ -33,8 +32,10 @@ export const PROVIDER_LABELS: Record<UsageProvider, string> = { claude: 'Claude
|
|
|
33
32
|
|
|
34
33
|
export const PROVIDER_COLORS: Record<UsageProvider, string> = { claude: 'var(--chart-claude)', codex: 'var(--chart-codex)' };
|
|
35
34
|
|
|
36
|
-
/*
|
|
37
|
-
|
|
35
|
+
/*
|
|
36
|
+
* A slot is `2026-09-10` or `2026-09-10T14`, already in the viewer's own zone, so it is read as plain
|
|
37
|
+
* wall clock and never handed to a parser that would shift it back.
|
|
38
|
+
*/
|
|
38
39
|
function dateOfSlot(slot: string): Date {
|
|
39
40
|
return new Date(Number(slot.slice(0, 4)), Number(slot.slice(5, 7)) - 1, Number(slot.slice(8, 10)));
|
|
40
41
|
}
|
|
@@ -63,8 +64,7 @@ export function formatDate(at: number): string {
|
|
|
63
64
|
return formatDay(at);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
/*
|
|
67
|
-
same repository apart, without the home directory nobody needs to read again. */
|
|
67
|
+
/* Enough of a path's tail to tell two checkouts of the same repository apart, under a row that already carries the name. */
|
|
68
68
|
export function shortPath(path: string): string {
|
|
69
69
|
const parts = path.split('/').filter(Boolean);
|
|
70
70
|
return parts.length <= 3 ? path : `…/${parts.slice(-3).join('/')}`;
|
package/src/usage/money.ts
CHANGED
|
@@ -2,11 +2,7 @@ import { useMemo } from 'react';
|
|
|
2
2
|
import { useUsage } from '../state/usage';
|
|
3
3
|
import { moneyFormat } from './format';
|
|
4
4
|
|
|
5
|
-
/*
|
|
6
|
-
* The formatter every amount on the page goes through: the currency that was chosen, at the rate
|
|
7
|
-
* the summary on screen carries. It is a hook rather than a prop because every row wants it and
|
|
8
|
-
* none of them wants to know where the rate came from.
|
|
9
|
-
*/
|
|
5
|
+
/* The formatter every amount on the page goes through: the chosen currency, at the rate the summary on screen carries. */
|
|
10
6
|
export function useMoney(): (usd: number) => string {
|
|
11
7
|
const currency = useUsage((s) => s.currency);
|
|
12
8
|
const rate = useUsage((s) => s.summary?.rate ?? null);
|
package/src/usage/summary.ts
CHANGED
|
@@ -117,10 +117,8 @@ export interface UsageDay {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/*
|
|
120
|
-
* The
|
|
121
|
-
*
|
|
122
|
-
* happened in is left out, since the chart already shows the gap, and a quiet fortnight would otherwise
|
|
123
|
-
* be fourteen rows of zeroes between the days worth reading.
|
|
120
|
+
* The chart's buckets as a table of calendar days, newest first; an hour slot folds onto the date it
|
|
121
|
+
* names. A day nothing happened in is left out, since the chart already shows the gap.
|
|
124
122
|
*/
|
|
125
123
|
export function deriveDays(summary: UsageSummaryResult): UsageDay[] {
|
|
126
124
|
const rows = new Map<string, UsageDay>();
|