@dexto/tui 1.14.4 → 1.15.0-beta.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/components/Footer.cjs +31 -0
- package/dist/components/Footer.d.ts.map +1 -1
- package/dist/components/Footer.js +31 -0
- package/dist/components/Footer.test.cjs +44 -0
- package/dist/components/Footer.test.js +44 -0
- package/dist/components/overlays/InsufficientCreditsOverlay.cjs +19 -4
- package/dist/components/overlays/InsufficientCreditsOverlay.d.ts +2 -0
- package/dist/components/overlays/InsufficientCreditsOverlay.d.ts.map +1 -1
- package/dist/components/overlays/InsufficientCreditsOverlay.js +19 -4
- package/dist/components/overlays/LoginOverlay.cjs +25 -61
- package/dist/components/overlays/LoginOverlay.d.ts +1 -3
- package/dist/components/overlays/LoginOverlay.d.ts.map +1 -1
- package/dist/components/overlays/LoginOverlay.js +26 -66
- package/dist/components/overlays/LogoutOverlay.cjs +8 -17
- package/dist/components/overlays/LogoutOverlay.d.ts +1 -0
- package/dist/components/overlays/LogoutOverlay.d.ts.map +1 -1
- package/dist/components/overlays/LogoutOverlay.js +10 -31
- package/dist/components/overlays/custom-model-wizard/provider-config.cjs +4 -4
- package/dist/components/overlays/custom-model-wizard/provider-config.js +4 -4
- package/dist/containers/OverlayContainer.cjs +30 -40
- package/dist/containers/OverlayContainer.d.ts.map +1 -1
- package/dist/containers/OverlayContainer.js +30 -40
- package/dist/host/index.cjs +22 -49
- package/dist/host/index.d.ts +18 -72
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +18 -42
- package/dist/host/index.test.cjs +21 -35
- package/dist/host/index.test.js +22 -40
- package/dist/index.d.cts +13 -52
- package/dist/services/processStream.cjs +2 -2
- package/dist/services/processStream.js +2 -2
- package/dist/services/processStream.test.cjs +1 -1
- package/dist/services/processStream.test.js +1 -1
- package/dist/utils/llm-provider-display.cjs +1 -1
- package/dist/utils/llm-provider-display.js +1 -1
- package/package.json +3 -3
|
@@ -40,6 +40,7 @@ var import_core = require("@dexto/core");
|
|
|
40
40
|
var import_llm_provider_display = require("../utils/llm-provider-display.js");
|
|
41
41
|
var import_chatgpt_rate_limit = require("../utils/chatgpt-rate-limit.js");
|
|
42
42
|
var import_agent_backend = require("../agent-backend.js");
|
|
43
|
+
var import_host = require("../host/index.js");
|
|
43
44
|
function Footer({
|
|
44
45
|
agent,
|
|
45
46
|
sessionId,
|
|
@@ -125,6 +126,31 @@ function Footer({
|
|
|
125
126
|
controller.abort();
|
|
126
127
|
};
|
|
127
128
|
}, [agent, sessionId]);
|
|
129
|
+
const [creditBalance, setCreditBalance] = (0, import_react.useState)(null);
|
|
130
|
+
const usesDextoCredits = provider === "dexto-nova";
|
|
131
|
+
(0, import_react.useEffect)(() => {
|
|
132
|
+
setCreditBalance(null);
|
|
133
|
+
if (!usesDextoCredits) return;
|
|
134
|
+
let cancelled = false;
|
|
135
|
+
const refresh = () => {
|
|
136
|
+
(0, import_host.dextoCreditBalance)().then((balance) => {
|
|
137
|
+
if (!cancelled) setCreditBalance(balance);
|
|
138
|
+
}).catch(() => void 0);
|
|
139
|
+
};
|
|
140
|
+
refresh();
|
|
141
|
+
const controller = new AbortController();
|
|
142
|
+
agent.on(
|
|
143
|
+
"llm:response",
|
|
144
|
+
(payload) => {
|
|
145
|
+
if (!payload.sessionId || payload.sessionId === sessionId) refresh();
|
|
146
|
+
},
|
|
147
|
+
{ signal: controller.signal }
|
|
148
|
+
);
|
|
149
|
+
return () => {
|
|
150
|
+
cancelled = true;
|
|
151
|
+
controller.abort();
|
|
152
|
+
};
|
|
153
|
+
}, [agent, sessionId, usesDextoCredits]);
|
|
128
154
|
const pathColor = isShellMode ? "yellow" : "blue";
|
|
129
155
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", paddingX: 1, children: [
|
|
130
156
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "row", justifyContent: "space-between", children: [
|
|
@@ -153,6 +179,11 @@ function Footer({
|
|
|
153
179
|
contextLeft.percentLeft,
|
|
154
180
|
"% context left"
|
|
155
181
|
] }) }),
|
|
182
|
+
creditBalance !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "gray", children: [
|
|
183
|
+
"$",
|
|
184
|
+
creditBalance.toFixed(2),
|
|
185
|
+
" credits"
|
|
186
|
+
] }) }),
|
|
156
187
|
chatGPTRateLimitHint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: chatGPTRateLimitColor, children: chatGPTRateLimitHint }) }),
|
|
157
188
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "gray", children: [
|
|
158
189
|
"approvals: ",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Footer.d.ts","sourceRoot":"","sources":["../../src/components/Footer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAM7E,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"Footer.d.ts","sourceRoot":"","sources":["../../src/components/Footer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAM7E,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGjF,UAAU,WAAW;IACjB,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sBAAsB,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAC1D;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,EACnB,KAAK,EACL,SAAS,EACT,SAAS,EACT,GAAG,EACH,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,sBAAsB,GACzB,EAAE,WAAW,2CAqNb"}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
shouldShowChatGPTRateLimitHint
|
|
11
11
|
} from "../utils/chatgpt-rate-limit.js";
|
|
12
12
|
import { supportsContextStats } from "../agent-backend.js";
|
|
13
|
+
import { dextoCreditBalance } from "../host/index.js";
|
|
13
14
|
function Footer({
|
|
14
15
|
agent,
|
|
15
16
|
sessionId,
|
|
@@ -95,6 +96,31 @@ function Footer({
|
|
|
95
96
|
controller.abort();
|
|
96
97
|
};
|
|
97
98
|
}, [agent, sessionId]);
|
|
99
|
+
const [creditBalance, setCreditBalance] = useState(null);
|
|
100
|
+
const usesDextoCredits = provider === "dexto-nova";
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
setCreditBalance(null);
|
|
103
|
+
if (!usesDextoCredits) return;
|
|
104
|
+
let cancelled = false;
|
|
105
|
+
const refresh = () => {
|
|
106
|
+
dextoCreditBalance().then((balance) => {
|
|
107
|
+
if (!cancelled) setCreditBalance(balance);
|
|
108
|
+
}).catch(() => void 0);
|
|
109
|
+
};
|
|
110
|
+
refresh();
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
agent.on(
|
|
113
|
+
"llm:response",
|
|
114
|
+
(payload) => {
|
|
115
|
+
if (!payload.sessionId || payload.sessionId === sessionId) refresh();
|
|
116
|
+
},
|
|
117
|
+
{ signal: controller.signal }
|
|
118
|
+
);
|
|
119
|
+
return () => {
|
|
120
|
+
cancelled = true;
|
|
121
|
+
controller.abort();
|
|
122
|
+
};
|
|
123
|
+
}, [agent, sessionId, usesDextoCredits]);
|
|
98
124
|
const pathColor = isShellMode ? "yellow" : "blue";
|
|
99
125
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
100
126
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [
|
|
@@ -123,6 +149,11 @@ function Footer({
|
|
|
123
149
|
contextLeft.percentLeft,
|
|
124
150
|
"% context left"
|
|
125
151
|
] }) }),
|
|
152
|
+
creditBalance !== null && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
153
|
+
"$",
|
|
154
|
+
creditBalance.toFixed(2),
|
|
155
|
+
" credits"
|
|
156
|
+
] }) }),
|
|
126
157
|
chatGPTRateLimitHint && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: chatGPTRateLimitColor, children: chatGPTRateLimitHint }) }),
|
|
127
158
|
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
128
159
|
"approvals: ",
|
|
@@ -4,6 +4,7 @@ var import_node_stream = require("node:stream");
|
|
|
4
4
|
var import_ink = require("ink");
|
|
5
5
|
var import_vitest = require("vitest");
|
|
6
6
|
var import_Footer = require("./Footer.js");
|
|
7
|
+
var import_host = require("../host/index.js");
|
|
7
8
|
(0, import_vitest.describe)("configured approval policy in the TUI footer", () => {
|
|
8
9
|
import_vitest.it.each(["manual", "auto-approve"])(
|
|
9
10
|
"shows %s independently of session shortcuts",
|
|
@@ -45,3 +46,46 @@ var import_Footer = require("./Footer.js");
|
|
|
45
46
|
}
|
|
46
47
|
);
|
|
47
48
|
});
|
|
49
|
+
(0, import_vitest.describe)("Dexto credits in the TUI footer", () => {
|
|
50
|
+
async function renderFooter(provider, balance) {
|
|
51
|
+
(0, import_host.setTuiRuntimeServices)({ dextoCreditBalance: async () => balance });
|
|
52
|
+
const stdout = Object.assign(new import_node_stream.PassThrough(), { columns: 120, rows: 40, isTTY: false });
|
|
53
|
+
let output = "";
|
|
54
|
+
stdout.on("data", (chunk) => {
|
|
55
|
+
output += String(chunk);
|
|
56
|
+
});
|
|
57
|
+
const agent = {
|
|
58
|
+
capabilities: { contextStats: false },
|
|
59
|
+
getEffectiveConfig: () => ({ permissions: { mode: "manual" } }),
|
|
60
|
+
getCurrentLLMConfig: () => ({ provider, model: "openai/gpt-5" }),
|
|
61
|
+
on: () => void 0
|
|
62
|
+
};
|
|
63
|
+
const view = (0, import_ink.render)(
|
|
64
|
+
(0, import_react.createElement)(import_Footer.Footer, { agent, sessionId: "session-1", modelName: "openai/gpt-5" }),
|
|
65
|
+
{
|
|
66
|
+
stdout,
|
|
67
|
+
debug: true,
|
|
68
|
+
patchConsole: false,
|
|
69
|
+
exitOnCtrlC: false
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
try {
|
|
73
|
+
await import_vitest.vi.waitFor(() => (0, import_vitest.expect)(output).toContain("approvals: manual"));
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
75
|
+
return output;
|
|
76
|
+
} finally {
|
|
77
|
+
view.unmount();
|
|
78
|
+
stdout.destroy();
|
|
79
|
+
(0, import_host.setTuiRuntimeServices)({});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
(0, import_vitest.it)("shows the balance while a Dexto model runs, under the name Dexto", async () => {
|
|
83
|
+
const output = await renderFooter("dexto-nova", 12.5);
|
|
84
|
+
(0, import_vitest.expect)(output).toContain("$12.50 credits");
|
|
85
|
+
(0, import_vitest.expect)(output).toContain("(Dexto)");
|
|
86
|
+
});
|
|
87
|
+
(0, import_vitest.it)("shows no balance for other providers", async () => {
|
|
88
|
+
const output = await renderFooter("openai", 12.5);
|
|
89
|
+
(0, import_vitest.expect)(output).not.toContain("credits");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -3,6 +3,7 @@ import { PassThrough } from "node:stream";
|
|
|
3
3
|
import { render } from "ink";
|
|
4
4
|
import { describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { Footer } from "./Footer.js";
|
|
6
|
+
import { setTuiRuntimeServices } from "../host/index.js";
|
|
6
7
|
describe("configured approval policy in the TUI footer", () => {
|
|
7
8
|
it.each(["manual", "auto-approve"])(
|
|
8
9
|
"shows %s independently of session shortcuts",
|
|
@@ -44,3 +45,46 @@ describe("configured approval policy in the TUI footer", () => {
|
|
|
44
45
|
}
|
|
45
46
|
);
|
|
46
47
|
});
|
|
48
|
+
describe("Dexto credits in the TUI footer", () => {
|
|
49
|
+
async function renderFooter(provider, balance) {
|
|
50
|
+
setTuiRuntimeServices({ dextoCreditBalance: async () => balance });
|
|
51
|
+
const stdout = Object.assign(new PassThrough(), { columns: 120, rows: 40, isTTY: false });
|
|
52
|
+
let output = "";
|
|
53
|
+
stdout.on("data", (chunk) => {
|
|
54
|
+
output += String(chunk);
|
|
55
|
+
});
|
|
56
|
+
const agent = {
|
|
57
|
+
capabilities: { contextStats: false },
|
|
58
|
+
getEffectiveConfig: () => ({ permissions: { mode: "manual" } }),
|
|
59
|
+
getCurrentLLMConfig: () => ({ provider, model: "openai/gpt-5" }),
|
|
60
|
+
on: () => void 0
|
|
61
|
+
};
|
|
62
|
+
const view = render(
|
|
63
|
+
createElement(Footer, { agent, sessionId: "session-1", modelName: "openai/gpt-5" }),
|
|
64
|
+
{
|
|
65
|
+
stdout,
|
|
66
|
+
debug: true,
|
|
67
|
+
patchConsole: false,
|
|
68
|
+
exitOnCtrlC: false
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
try {
|
|
72
|
+
await vi.waitFor(() => expect(output).toContain("approvals: manual"));
|
|
73
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
74
|
+
return output;
|
|
75
|
+
} finally {
|
|
76
|
+
view.unmount();
|
|
77
|
+
stdout.destroy();
|
|
78
|
+
setTuiRuntimeServices({});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
it("shows the balance while a Dexto model runs, under the name Dexto", async () => {
|
|
82
|
+
const output = await renderFooter("dexto-nova", 12.5);
|
|
83
|
+
expect(output).toContain("$12.50 credits");
|
|
84
|
+
expect(output).toContain("(Dexto)");
|
|
85
|
+
});
|
|
86
|
+
it("shows no balance for other providers", async () => {
|
|
87
|
+
const output = await renderFooter("openai", 12.5);
|
|
88
|
+
expect(output).not.toContain("credits");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -49,6 +49,11 @@ const TOP_UP_OPTIONS = [
|
|
|
49
49
|
id: "open-billing",
|
|
50
50
|
label: "Open billing page",
|
|
51
51
|
description: "View full billing dashboard"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "own-key",
|
|
55
|
+
label: "Use your own model key",
|
|
56
|
+
description: "Pick a model from a provider you have a key for"
|
|
52
57
|
}
|
|
53
58
|
];
|
|
54
59
|
const WAITING_OPTIONS = [
|
|
@@ -77,7 +82,7 @@ function formatBalance(balanceUsd) {
|
|
|
77
82
|
function getErrorMessage(error) {
|
|
78
83
|
return error instanceof Error ? error.message : String(error);
|
|
79
84
|
}
|
|
80
|
-
const InsufficientCreditsOverlay = (0, import_react.forwardRef)(function InsufficientCreditsOverlay2({ isVisible, initialBalanceUsd, onResolved, onClose }, ref) {
|
|
85
|
+
const InsufficientCreditsOverlay = (0, import_react.forwardRef)(function InsufficientCreditsOverlay2({ isVisible, initialBalanceUsd, onResolved, onClose, onChooseOwnModel }, ref) {
|
|
81
86
|
const selectorRef = (0, import_react.useRef)(null);
|
|
82
87
|
const isActiveRef = (0, import_react.useRef)(false);
|
|
83
88
|
const [step, setStep] = (0, import_react.useState)("select");
|
|
@@ -190,6 +195,9 @@ const InsufficientCreditsOverlay = (0, import_react.forwardRef)(function Insuffi
|
|
|
190
195
|
case "open-billing":
|
|
191
196
|
void openBillingDashboard();
|
|
192
197
|
return;
|
|
198
|
+
case "own-key":
|
|
199
|
+
onChooseOwnModel();
|
|
200
|
+
return;
|
|
193
201
|
case "reopen":
|
|
194
202
|
void reopenBilling();
|
|
195
203
|
return;
|
|
@@ -201,7 +209,14 @@ const InsufficientCreditsOverlay = (0, import_react.forwardRef)(function Insuffi
|
|
|
201
209
|
return;
|
|
202
210
|
}
|
|
203
211
|
},
|
|
204
|
-
[
|
|
212
|
+
[
|
|
213
|
+
onChooseOwnModel,
|
|
214
|
+
onResolved,
|
|
215
|
+
openBillingDashboard,
|
|
216
|
+
openPrefilledBilling,
|
|
217
|
+
reopenBilling,
|
|
218
|
+
resetToSelection
|
|
219
|
+
]
|
|
205
220
|
);
|
|
206
221
|
const formatItem = (0, import_react.useCallback)((option, isSelected) => {
|
|
207
222
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -228,9 +243,9 @@ const InsufficientCreditsOverlay = (0, import_react.forwardRef)(function Insuffi
|
|
|
228
243
|
return null;
|
|
229
244
|
}
|
|
230
245
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", children: [
|
|
231
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "yellow", bold: true, children: "Out of Dexto
|
|
246
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "yellow", bold: true, children: "Out of Dexto credits" }) }),
|
|
232
247
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginTop: 1, children: [
|
|
233
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "This request stopped because your Dexto
|
|
248
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "This request stopped because your Dexto credits ran out." }),
|
|
234
249
|
formatBalance(balanceUsd) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "gray", children: [
|
|
235
250
|
"Current balance: ",
|
|
236
251
|
formatBalance(balanceUsd)
|
|
@@ -5,6 +5,8 @@ export interface InsufficientCreditsOverlayProps {
|
|
|
5
5
|
initialBalanceUsd: number | null;
|
|
6
6
|
onResolved: () => void;
|
|
7
7
|
onClose: () => void;
|
|
8
|
+
/** Leaves credits for a model on the person's own key. */
|
|
9
|
+
onChooseOwnModel: () => void;
|
|
8
10
|
}
|
|
9
11
|
export interface InsufficientCreditsOverlayHandle {
|
|
10
12
|
handleInput: (input: string, key: Key) => boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InsufficientCreditsOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/InsufficientCreditsOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"InsufficientCreditsOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/InsufficientCreditsOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAmF/D,MAAM,WAAW,+BAA+B;IAC5C,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,0DAA0D;IAC1D,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,gCAAgC;IAC7C,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,QAAA,MAAM,0BAA0B,0HA4O9B,CAAC;AAEH,eAAe,0BAA0B,CAAC"}
|
|
@@ -34,6 +34,11 @@ const TOP_UP_OPTIONS = [
|
|
|
34
34
|
id: "open-billing",
|
|
35
35
|
label: "Open billing page",
|
|
36
36
|
description: "View full billing dashboard"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "own-key",
|
|
40
|
+
label: "Use your own model key",
|
|
41
|
+
description: "Pick a model from a provider you have a key for"
|
|
37
42
|
}
|
|
38
43
|
];
|
|
39
44
|
const WAITING_OPTIONS = [
|
|
@@ -62,7 +67,7 @@ function formatBalance(balanceUsd) {
|
|
|
62
67
|
function getErrorMessage(error) {
|
|
63
68
|
return error instanceof Error ? error.message : String(error);
|
|
64
69
|
}
|
|
65
|
-
const InsufficientCreditsOverlay = forwardRef(function InsufficientCreditsOverlay2({ isVisible, initialBalanceUsd, onResolved, onClose }, ref) {
|
|
70
|
+
const InsufficientCreditsOverlay = forwardRef(function InsufficientCreditsOverlay2({ isVisible, initialBalanceUsd, onResolved, onClose, onChooseOwnModel }, ref) {
|
|
66
71
|
const selectorRef = useRef(null);
|
|
67
72
|
const isActiveRef = useRef(false);
|
|
68
73
|
const [step, setStep] = useState("select");
|
|
@@ -175,6 +180,9 @@ const InsufficientCreditsOverlay = forwardRef(function InsufficientCreditsOverla
|
|
|
175
180
|
case "open-billing":
|
|
176
181
|
void openBillingDashboard();
|
|
177
182
|
return;
|
|
183
|
+
case "own-key":
|
|
184
|
+
onChooseOwnModel();
|
|
185
|
+
return;
|
|
178
186
|
case "reopen":
|
|
179
187
|
void reopenBilling();
|
|
180
188
|
return;
|
|
@@ -186,7 +194,14 @@ const InsufficientCreditsOverlay = forwardRef(function InsufficientCreditsOverla
|
|
|
186
194
|
return;
|
|
187
195
|
}
|
|
188
196
|
},
|
|
189
|
-
[
|
|
197
|
+
[
|
|
198
|
+
onChooseOwnModel,
|
|
199
|
+
onResolved,
|
|
200
|
+
openBillingDashboard,
|
|
201
|
+
openPrefilledBilling,
|
|
202
|
+
reopenBilling,
|
|
203
|
+
resetToSelection
|
|
204
|
+
]
|
|
190
205
|
);
|
|
191
206
|
const formatItem = useCallback((option, isSelected) => {
|
|
192
207
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -213,9 +228,9 @@ const InsufficientCreditsOverlay = forwardRef(function InsufficientCreditsOverla
|
|
|
213
228
|
return null;
|
|
214
229
|
}
|
|
215
230
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
216
|
-
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "Out of Dexto
|
|
231
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "Out of Dexto credits" }) }),
|
|
217
232
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
218
|
-
/* @__PURE__ */ jsx(Text, { color: "gray", children: "This request stopped because your Dexto
|
|
233
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "This request stopped because your Dexto credits ran out." }),
|
|
219
234
|
formatBalance(balanceUsd) && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
220
235
|
"Current balance: ",
|
|
221
236
|
formatBalance(balanceUsd)
|
|
@@ -28,9 +28,8 @@ var import_host = require("../../host/index.js");
|
|
|
28
28
|
const LoginOverlay = (0, import_react.forwardRef)(function LoginOverlay2({ isVisible, onDone }, ref) {
|
|
29
29
|
const [step, setStep] = (0, import_react.useState)("checking");
|
|
30
30
|
const [existingUser, setExistingUser] = (0, import_react.useState)(null);
|
|
31
|
-
const [
|
|
32
|
-
const [
|
|
33
|
-
const [status, setStatus] = (0, import_react.useState)("Preparing login...");
|
|
31
|
+
const [signInNote, setSignInNote] = (0, import_react.useState)(null);
|
|
32
|
+
const [status, setStatus] = (0, import_react.useState)("Preparing sign-in...");
|
|
34
33
|
const [error, setError] = (0, import_react.useState)(null);
|
|
35
34
|
const abortControllerRef = (0, import_react.useRef)(null);
|
|
36
35
|
const isActiveRef = (0, import_react.useRef)(false);
|
|
@@ -42,47 +41,24 @@ const LoginOverlay = (0, import_react.forwardRef)(function LoginOverlay2({ isVis
|
|
|
42
41
|
abortControllerRef.current?.abort(new Error("Authentication cancelled"));
|
|
43
42
|
abortControllerRef.current = null;
|
|
44
43
|
}, []);
|
|
45
|
-
const runDeviceLogin = (0, import_react.useCallback)(
|
|
46
|
-
async (abortController) => {
|
|
47
|
-
setStep("starting");
|
|
48
|
-
safeSetStatus("Starting device code login...");
|
|
49
|
-
setAuthUrl(null);
|
|
50
|
-
setDevicePrompt(null);
|
|
51
|
-
return (0, import_host.performDeviceCodeLogin)({
|
|
52
|
-
signal: abortController.signal,
|
|
53
|
-
onPrompt: (prompt) => {
|
|
54
|
-
if (!isActiveRef.current || abortController.signal.aborted) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
setDevicePrompt(prompt);
|
|
58
|
-
setAuthUrl(prompt.verificationUrlComplete ?? prompt.verificationUrl);
|
|
59
|
-
setStep("waiting");
|
|
60
|
-
safeSetStatus("Open the URL below and approve login.");
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
},
|
|
64
|
-
[safeSetStatus]
|
|
65
|
-
);
|
|
66
44
|
const startLogin = (0, import_react.useCallback)(async () => {
|
|
67
45
|
cancelInFlight();
|
|
68
46
|
setError(null);
|
|
69
|
-
|
|
70
|
-
|
|
47
|
+
setSignInNote(null);
|
|
48
|
+
setStep("signing-in");
|
|
49
|
+
safeSetStatus("Signing in...");
|
|
71
50
|
const abortController = new AbortController();
|
|
72
51
|
abortControllerRef.current = abortController;
|
|
73
52
|
try {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
onDone({
|
|
81
|
-
outcome: "success",
|
|
82
|
-
email: persisted.email,
|
|
83
|
-
keyId: persisted.keyId,
|
|
84
|
-
hasDextoApiKey: persisted.hasDextoApiKey
|
|
53
|
+
const summary = await (0, import_host.signIn)({
|
|
54
|
+
signal: abortController.signal,
|
|
55
|
+
onNote: (text) => {
|
|
56
|
+
if (!isActiveRef.current || abortController.signal.aborted) return;
|
|
57
|
+
setSignInNote(text);
|
|
58
|
+
}
|
|
85
59
|
});
|
|
60
|
+
if (!isActiveRef.current || abortController.signal.aborted) return;
|
|
61
|
+
onDone({ outcome: "success", summary });
|
|
86
62
|
} catch (err) {
|
|
87
63
|
if (abortController.signal.aborted) {
|
|
88
64
|
return;
|
|
@@ -90,28 +66,27 @@ const LoginOverlay = (0, import_react.forwardRef)(function LoginOverlay2({ isVis
|
|
|
90
66
|
setStep("error");
|
|
91
67
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
92
68
|
setError(errorMessage);
|
|
93
|
-
safeSetStatus("
|
|
69
|
+
safeSetStatus("Sign-in failed");
|
|
94
70
|
} finally {
|
|
95
71
|
abortControllerRef.current = null;
|
|
96
72
|
}
|
|
97
|
-
}, [cancelInFlight, onDone,
|
|
73
|
+
}, [cancelInFlight, onDone, safeSetStatus]);
|
|
98
74
|
(0, import_react.useEffect)(() => {
|
|
99
75
|
if (!isVisible) return;
|
|
100
76
|
isActiveRef.current = true;
|
|
101
77
|
setStep("checking");
|
|
102
78
|
setExistingUser(null);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
setStatus("Preparing login...");
|
|
79
|
+
setSignInNote(null);
|
|
80
|
+
setStatus("Preparing sign-in...");
|
|
106
81
|
setError(null);
|
|
107
82
|
void (async () => {
|
|
108
83
|
try {
|
|
109
|
-
const
|
|
84
|
+
const signedInAs = await (0, import_host.currentSignIn)();
|
|
110
85
|
if (!isActiveRef.current) return;
|
|
111
|
-
if (
|
|
112
|
-
setExistingUser(
|
|
86
|
+
if (signedInAs !== null) {
|
|
87
|
+
setExistingUser(signedInAs);
|
|
113
88
|
setStep("already-authenticated");
|
|
114
|
-
setStatus("You are already
|
|
89
|
+
setStatus("You are already signed in");
|
|
115
90
|
return;
|
|
116
91
|
}
|
|
117
92
|
await startLogin();
|
|
@@ -151,7 +126,7 @@ const LoginOverlay = (0, import_react.forwardRef)(function LoginOverlay2({ isVis
|
|
|
151
126
|
[cancelInFlight, isVisible, onDone, startLogin, step]
|
|
152
127
|
);
|
|
153
128
|
if (!isVisible) return null;
|
|
154
|
-
const hint = step === "already-authenticated" ? "Enter to
|
|
129
|
+
const hint = step === "already-authenticated" ? "Enter to sign in again \u2022 Esc to close" : step === "error" ? "Enter to retry \u2022 Esc to close" : "Esc to cancel";
|
|
155
130
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
156
131
|
import_ink.Box,
|
|
157
132
|
{
|
|
@@ -161,27 +136,16 @@ const LoginOverlay = (0, import_react.forwardRef)(function LoginOverlay2({ isVis
|
|
|
161
136
|
paddingX: 1,
|
|
162
137
|
marginTop: 1,
|
|
163
138
|
children: [
|
|
164
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { bold: true, color: "cyan", children: "
|
|
139
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { bold: true, color: "cyan", children: "Sign in to Dexto" }) }),
|
|
165
140
|
existingUser && step === "already-authenticated" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "green", children: [
|
|
166
|
-
"
|
|
141
|
+
"Signed in as ",
|
|
167
142
|
existingUser
|
|
168
143
|
] }) }),
|
|
169
144
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { marginBottom: 1, children: [
|
|
170
145
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "Status: " }),
|
|
171
146
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { children: status })
|
|
172
147
|
] }),
|
|
173
|
-
|
|
174
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "Open this URL on any device:" }),
|
|
175
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "yellowBright", children: devicePrompt.verificationUrlComplete ?? devicePrompt.verificationUrl }),
|
|
176
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "gray", children: [
|
|
177
|
-
"Code: ",
|
|
178
|
-
devicePrompt.userCode
|
|
179
|
-
] })
|
|
180
|
-
] }),
|
|
181
|
-
authUrl && !devicePrompt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
182
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "If your browser didn't open, use this URL:" }),
|
|
183
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "yellowBright", children: authUrl })
|
|
184
|
-
] }),
|
|
148
|
+
signInNote && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { children: signInNote }) }),
|
|
185
149
|
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "red", children: [
|
|
186
150
|
"Error: ",
|
|
187
151
|
error
|
|
@@ -2,9 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import type { Key } from '../../hooks/useInputOrchestrator.js';
|
|
3
3
|
export type LoginOverlayOutcome = {
|
|
4
4
|
outcome: 'success';
|
|
5
|
-
|
|
6
|
-
keyId?: string | undefined;
|
|
7
|
-
hasDextoApiKey: boolean;
|
|
5
|
+
summary: string;
|
|
8
6
|
} | {
|
|
9
7
|
outcome: 'cancelled';
|
|
10
8
|
} | {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoginOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/LoginOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"LoginOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/LoginOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAG/D,MAAM,MAAM,mBAAmB,GACzB;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,GACxB;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE5B,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,kBAAkB;IAC/B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAID,QAAA,MAAM,YAAY,8FAiLhB,CAAC;AAEH,eAAe,YAAY,CAAC"}
|