@ridit/lens 0.3.7 → 0.3.9
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/index.mjs +105368 -274002
- package/package.json +13 -19
- package/src/colors.ts +15 -15
- package/src/commands/chat.tsx +32 -23
- package/src/commands/provider.tsx +11 -238
- package/src/commands/repo.tsx +66 -120
- package/src/commands/timeline.tsx +11 -22
- package/src/components/ChatView.tsx +238 -0
- package/src/components/Message.tsx +46 -0
- package/src/components/ToolCall.tsx +67 -0
- package/src/components/chat/ChatView.tsx +550 -0
- package/src/components/chat/Message.tsx +152 -0
- package/src/components/chat/StatusBar.tsx +214 -0
- package/src/components/chat/TextArea.tsx +173 -176
- package/src/components/provider/ApiKeyStep.tsx +207 -199
- package/src/components/provider/ModelStep.tsx +90 -88
- package/src/components/provider/ProviderSetup.tsx +331 -0
- package/src/components/provider/ProviderTypeStep.tsx +53 -61
- package/src/components/repo/StepRow.tsx +68 -69
- package/src/components/timeline/TimelineView.tsx +840 -0
- package/src/components/toolcall-utils.ts +103 -0
- package/src/components/watch/RunView.tsx +497 -0
- package/src/hooks/useChatInput.ts +49 -0
- package/src/hooks/useCommandHandler.ts +117 -0
- package/src/index.tsx +386 -139
- package/src/utils/git.ts +149 -155
- package/src/utils/repo.ts +62 -69
- package/src/utils/thinking.tsx +64 -0
- package/src/utils/watch.ts +165 -307
- package/tests/message.test.ts +38 -0
- package/tests/toolcall-utils.test.ts +111 -0
- package/tsconfig.json +8 -24
- package/CLAUDE.md +0 -50
- package/LENS.md +0 -48
- package/LICENSE +0 -21
- package/README.md +0 -93
- package/addons/README.md +0 -55
- package/addons/clean-cache.js +0 -48
- package/addons/generate-readme.js +0 -67
- package/addons/git-stats.js +0 -29
- package/addons/run-tests.js +0 -127
- package/src/commands/commit.tsx +0 -668
- package/src/commands/review.tsx +0 -294
- package/src/commands/run.tsx +0 -56
- package/src/commands/task.tsx +0 -36
- package/src/components/chat/ChatMessage.tsx +0 -195
- package/src/components/chat/ChatOverlays.tsx +0 -399
- package/src/components/chat/ChatRunner.tsx +0 -517
- package/src/components/chat/hooks/useChat.ts +0 -631
- package/src/components/chat/hooks/useChatInput.ts +0 -79
- package/src/components/chat/hooks/useCommandHandlers.ts +0 -327
- package/src/components/provider/ProviderPicker.tsx +0 -76
- package/src/components/provider/RemoveProviderStep.tsx +0 -82
- package/src/components/repo/DiffViewer.tsx +0 -175
- package/src/components/repo/FileReviewer.tsx +0 -70
- package/src/components/repo/FileViewer.tsx +0 -60
- package/src/components/repo/IssueFixer.tsx +0 -666
- package/src/components/repo/LensFileMenu.tsx +0 -115
- package/src/components/repo/NoProviderPrompt.tsx +0 -28
- package/src/components/repo/PreviewRunner.tsx +0 -217
- package/src/components/repo/RepoAnalysis.tsx +0 -534
- package/src/components/task/TaskRunner.tsx +0 -396
- package/src/components/timeline/CommitDetail.tsx +0 -272
- package/src/components/timeline/CommitList.tsx +0 -162
- package/src/components/timeline/TimelineChat.tsx +0 -166
- package/src/components/timeline/TimelineRunner.tsx +0 -1285
- package/src/components/watch/RunRunner.tsx +0 -929
- package/src/prompts/fewshot.ts +0 -252
- package/src/prompts/index.ts +0 -2
- package/src/prompts/system.ts +0 -285
- package/src/tools/chart.ts +0 -202
- package/src/tools/convert-image.ts +0 -312
- package/src/tools/files.ts +0 -253
- package/src/tools/git.ts +0 -603
- package/src/tools/index.ts +0 -17
- package/src/tools/pdf.ts +0 -164
- package/src/tools/shell.ts +0 -96
- package/src/tools/view-image.ts +0 -335
- package/src/tools/web.ts +0 -212
- package/src/types/chat.ts +0 -86
- package/src/types/config.ts +0 -20
- package/src/types/repo.ts +0 -54
- package/src/utils/addons/loadAddons.ts +0 -34
- package/src/utils/ai.ts +0 -321
- package/src/utils/chat.ts +0 -326
- package/src/utils/chatHistory.ts +0 -121
- package/src/utils/config.ts +0 -61
- package/src/utils/files.ts +0 -105
- package/src/utils/intentClassifier.ts +0 -58
- package/src/utils/lensfile.ts +0 -142
- package/src/utils/llm.ts +0 -81
- package/src/utils/memory.ts +0 -209
- package/src/utils/preview.ts +0 -119
- package/src/utils/stats.ts +0 -174
- package/src/utils/tools/builtins.ts +0 -377
- package/src/utils/tools/registry.ts +0 -105
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import figures from "figures";
|
|
4
|
+
import {
|
|
5
|
+
addProvider,
|
|
6
|
+
setActiveProvider,
|
|
7
|
+
removeProvider,
|
|
8
|
+
getConfiguredProviders,
|
|
9
|
+
loadConfig,
|
|
10
|
+
type Provider,
|
|
11
|
+
} from "@ridit/lens-core";
|
|
12
|
+
import { ProviderTypeStep } from "./ProviderTypeStep";
|
|
13
|
+
import { ApiKeyStep } from "./ApiKeyStep";
|
|
14
|
+
import { ModelStep } from "./ModelStep";
|
|
15
|
+
import { ACCENT, GREEN, RED } from "../../colors";
|
|
16
|
+
|
|
17
|
+
type Stage =
|
|
18
|
+
| { type: "menu" }
|
|
19
|
+
| { type: "provider-type" }
|
|
20
|
+
| { type: "credentials"; provider: Provider }
|
|
21
|
+
| { type: "model"; provider: Provider; apiKey: string; baseURL?: string }
|
|
22
|
+
| { type: "remove-pick" }
|
|
23
|
+
| { type: "remove-confirm"; provider: Provider }
|
|
24
|
+
| { type: "switch-pick" }
|
|
25
|
+
| { type: "done"; message: string };
|
|
26
|
+
|
|
27
|
+
type MenuAction = "provider-type" | "remove-pick" | "switch-pick";
|
|
28
|
+
|
|
29
|
+
const MENU_OPTIONS: { label: string; action: MenuAction }[] = [
|
|
30
|
+
{ label: "Add / update a provider", action: "provider-type" },
|
|
31
|
+
{ label: "Remove a provider", action: "remove-pick" },
|
|
32
|
+
{ label: "Switch active provider", action: "switch-pick" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
function CompletedStep({ label }: { label: string }) {
|
|
36
|
+
return (
|
|
37
|
+
<Text color={GREEN}>
|
|
38
|
+
{figures.tick} {label}
|
|
39
|
+
</Text>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function Menu({
|
|
44
|
+
completedSteps,
|
|
45
|
+
onSelect,
|
|
46
|
+
}: {
|
|
47
|
+
completedSteps: string[];
|
|
48
|
+
onSelect: (action: MenuAction) => void;
|
|
49
|
+
}) {
|
|
50
|
+
const config = loadConfig();
|
|
51
|
+
const configured = getConfiguredProviders();
|
|
52
|
+
const [index, setIndex] = useState(0);
|
|
53
|
+
|
|
54
|
+
useInput((_, key) => {
|
|
55
|
+
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
56
|
+
if (key.downArrow) setIndex((i) => Math.min(MENU_OPTIONS.length - 1, i + 1));
|
|
57
|
+
if (key.return) onSelect(MENU_OPTIONS[index]!.action);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Box flexDirection="column" gap={1}>
|
|
62
|
+
{completedSteps.map((s, i) => <CompletedStep key={i} label={s} />)}
|
|
63
|
+
<Text bold color={ACCENT}>Lens — provider setup</Text>
|
|
64
|
+
{configured.length > 0 && (
|
|
65
|
+
<Text color="gray" dimColor>
|
|
66
|
+
{figures.info} active: <Text color="white">{config.activeProvider}</Text>
|
|
67
|
+
{" "}({configured.length} configured)
|
|
68
|
+
</Text>
|
|
69
|
+
)}
|
|
70
|
+
{MENU_OPTIONS.map((opt, i) => (
|
|
71
|
+
<Box key={opt.action} marginLeft={1}>
|
|
72
|
+
<Text color={i === index ? ACCENT : "white"}>
|
|
73
|
+
{i === index ? figures.arrowRight : " "}{" "}
|
|
74
|
+
<Text bold={i === index}>{opt.label}</Text>
|
|
75
|
+
</Text>
|
|
76
|
+
</Box>
|
|
77
|
+
))}
|
|
78
|
+
<Text color="gray" dimColor>↑↓ navigate · enter to select</Text>
|
|
79
|
+
</Box>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function RemovePick({
|
|
84
|
+
completedSteps,
|
|
85
|
+
onSelect,
|
|
86
|
+
onBack,
|
|
87
|
+
}: {
|
|
88
|
+
completedSteps: string[];
|
|
89
|
+
onSelect: (provider: Provider) => void;
|
|
90
|
+
onBack: () => void;
|
|
91
|
+
}) {
|
|
92
|
+
const configured = getConfiguredProviders();
|
|
93
|
+
const [index, setIndex] = useState(0);
|
|
94
|
+
|
|
95
|
+
useInput((_, key) => {
|
|
96
|
+
if (key.escape) { onBack(); return; }
|
|
97
|
+
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
98
|
+
if (key.downArrow) setIndex((i) => Math.min(configured.length - 1, i + 1));
|
|
99
|
+
if (key.return && configured.length > 0) onSelect(configured[index]!);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (configured.length === 0) {
|
|
103
|
+
return (
|
|
104
|
+
<Box flexDirection="column" gap={1}>
|
|
105
|
+
{completedSteps.map((s, i) => <CompletedStep key={i} label={s} />)}
|
|
106
|
+
<Text color="gray">{figures.info} No providers configured.</Text>
|
|
107
|
+
<Text color="gray" dimColor>esc to go back</Text>
|
|
108
|
+
</Box>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<Box flexDirection="column" gap={1}>
|
|
114
|
+
{completedSteps.map((s, i) => <CompletedStep key={i} label={s} />)}
|
|
115
|
+
<Text bold color={ACCENT}>Remove a provider</Text>
|
|
116
|
+
{configured.map((p, i) => (
|
|
117
|
+
<Box key={p} marginLeft={1}>
|
|
118
|
+
<Text color={i === index ? RED : "white"}>
|
|
119
|
+
{i === index ? figures.arrowRight : " "}{" "}
|
|
120
|
+
<Text bold={i === index}>{p}</Text>
|
|
121
|
+
</Text>
|
|
122
|
+
</Box>
|
|
123
|
+
))}
|
|
124
|
+
<Text color="gray" dimColor>↑↓ navigate · enter to select · esc to cancel</Text>
|
|
125
|
+
</Box>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function RemoveConfirm({
|
|
130
|
+
provider,
|
|
131
|
+
completedSteps,
|
|
132
|
+
onConfirm,
|
|
133
|
+
onBack,
|
|
134
|
+
}: {
|
|
135
|
+
provider: Provider;
|
|
136
|
+
completedSteps: string[];
|
|
137
|
+
onConfirm: () => void;
|
|
138
|
+
onBack: () => void;
|
|
139
|
+
}) {
|
|
140
|
+
useInput((input, key) => {
|
|
141
|
+
if (input === "y" || input === "Y") onConfirm();
|
|
142
|
+
else if (key.escape || input === "n" || input === "N") onBack();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<Box flexDirection="column" gap={1}>
|
|
147
|
+
{completedSteps.map((s, i) => <CompletedStep key={i} label={s} />)}
|
|
148
|
+
<Text color={RED}>
|
|
149
|
+
{figures.warning} Remove <Text bold>{provider}</Text>? (y/n)
|
|
150
|
+
</Text>
|
|
151
|
+
</Box>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function SwitchPick({
|
|
156
|
+
completedSteps,
|
|
157
|
+
onSelect,
|
|
158
|
+
onBack,
|
|
159
|
+
}: {
|
|
160
|
+
completedSteps: string[];
|
|
161
|
+
onSelect: (provider: Provider) => void;
|
|
162
|
+
onBack: () => void;
|
|
163
|
+
}) {
|
|
164
|
+
const config = loadConfig();
|
|
165
|
+
const configured = getConfiguredProviders();
|
|
166
|
+
const [index, setIndex] = useState(0);
|
|
167
|
+
|
|
168
|
+
useInput((_, key) => {
|
|
169
|
+
if (key.escape) { onBack(); return; }
|
|
170
|
+
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
171
|
+
if (key.downArrow) setIndex((i) => Math.min(configured.length - 1, i + 1));
|
|
172
|
+
if (key.return && configured.length > 0) onSelect(configured[index]!);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (configured.length === 0) {
|
|
176
|
+
return (
|
|
177
|
+
<Box flexDirection="column" gap={1}>
|
|
178
|
+
{completedSteps.map((s, i) => <CompletedStep key={i} label={s} />)}
|
|
179
|
+
<Text color="gray">{figures.info} No providers configured.</Text>
|
|
180
|
+
<Text color="gray" dimColor>esc to go back</Text>
|
|
181
|
+
</Box>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<Box flexDirection="column" gap={1}>
|
|
187
|
+
{completedSteps.map((s, i) => <CompletedStep key={i} label={s} />)}
|
|
188
|
+
<Text bold color={ACCENT}>Switch active provider</Text>
|
|
189
|
+
{configured.map((p, i) => {
|
|
190
|
+
const isActive = p === config.activeProvider;
|
|
191
|
+
const isSelected = i === index;
|
|
192
|
+
return (
|
|
193
|
+
<Box key={p} marginLeft={1}>
|
|
194
|
+
<Text color={isSelected ? ACCENT : "white"}>
|
|
195
|
+
{isSelected ? figures.arrowRight : " "}{" "}
|
|
196
|
+
<Text bold={isSelected}>{p}</Text>
|
|
197
|
+
{isActive && <Text color="gray">{" "}active</Text>}
|
|
198
|
+
</Text>
|
|
199
|
+
</Box>
|
|
200
|
+
);
|
|
201
|
+
})}
|
|
202
|
+
<Text color="gray" dimColor>↑↓ navigate · enter to select · esc to cancel</Text>
|
|
203
|
+
</Box>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function DoneScreen({ message, onDone }: { message: string; onDone: () => void }) {
|
|
208
|
+
useInput((_: string, key: { return: boolean; escape: boolean }) => {
|
|
209
|
+
if (key.return || key.escape) onDone();
|
|
210
|
+
});
|
|
211
|
+
return (
|
|
212
|
+
<Box flexDirection="column" gap={1}>
|
|
213
|
+
<Text color={GREEN}>{figures.tick} {message}</Text>
|
|
214
|
+
<Text color="gray" dimColor>press enter to continue</Text>
|
|
215
|
+
</Box>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function ProviderSetup({ onDone }: { onDone: () => void }) {
|
|
220
|
+
const [stage, setStage] = useState<Stage>({ type: "menu" });
|
|
221
|
+
const [completedSteps, setCompletedSteps] = useState<string[]>([]);
|
|
222
|
+
|
|
223
|
+
const pushStep = (label: string) => setCompletedSteps((s) => [...s, label]);
|
|
224
|
+
const goMenu = () => setStage({ type: "menu" });
|
|
225
|
+
|
|
226
|
+
if (stage.type === "menu") {
|
|
227
|
+
return (
|
|
228
|
+
<Menu
|
|
229
|
+
completedSteps={completedSteps}
|
|
230
|
+
onSelect={(action) => setStage({ type: action } as Stage)}
|
|
231
|
+
/>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (stage.type === "provider-type") {
|
|
236
|
+
return (
|
|
237
|
+
<Box flexDirection="column" gap={1}>
|
|
238
|
+
{completedSteps.map((s, i) => <CompletedStep key={i} label={s} />)}
|
|
239
|
+
<ProviderTypeStep
|
|
240
|
+
onSelect={(provider) => {
|
|
241
|
+
pushStep(`Provider: ${provider}`);
|
|
242
|
+
setStage({ type: "credentials", provider });
|
|
243
|
+
}}
|
|
244
|
+
/>
|
|
245
|
+
</Box>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (stage.type === "credentials") {
|
|
250
|
+
return (
|
|
251
|
+
<Box flexDirection="column" gap={1}>
|
|
252
|
+
{completedSteps.map((s, i) => <CompletedStep key={i} label={s} />)}
|
|
253
|
+
<ApiKeyStep
|
|
254
|
+
providerType={stage.provider}
|
|
255
|
+
onSubmit={(apiKey, baseURL) => {
|
|
256
|
+
if (stage.provider === "ollama") {
|
|
257
|
+
pushStep(`Base URL: ${baseURL ?? "http://localhost:11434"}`);
|
|
258
|
+
} else {
|
|
259
|
+
pushStep("API key saved");
|
|
260
|
+
if (baseURL) pushStep(`Base URL: ${baseURL}`);
|
|
261
|
+
}
|
|
262
|
+
setStage({ type: "model", provider: stage.provider, apiKey, baseURL });
|
|
263
|
+
}}
|
|
264
|
+
/>
|
|
265
|
+
</Box>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (stage.type === "model") {
|
|
270
|
+
return (
|
|
271
|
+
<Box flexDirection="column" gap={1}>
|
|
272
|
+
{completedSteps.map((s, i) => <CompletedStep key={i} label={s} />)}
|
|
273
|
+
<ModelStep
|
|
274
|
+
providerType={stage.provider}
|
|
275
|
+
onSelect={(model) => {
|
|
276
|
+
addProvider(stage.provider, {
|
|
277
|
+
apiKey: stage.apiKey,
|
|
278
|
+
model,
|
|
279
|
+
baseURL: stage.baseURL,
|
|
280
|
+
});
|
|
281
|
+
setActiveProvider(stage.provider);
|
|
282
|
+
pushStep(`Model: ${model}`);
|
|
283
|
+
setStage({ type: "done", message: `Provider set to ${stage.provider} (${model})` });
|
|
284
|
+
}}
|
|
285
|
+
/>
|
|
286
|
+
</Box>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (stage.type === "remove-pick") {
|
|
291
|
+
return (
|
|
292
|
+
<RemovePick
|
|
293
|
+
completedSteps={completedSteps}
|
|
294
|
+
onSelect={(provider) => setStage({ type: "remove-confirm", provider })}
|
|
295
|
+
onBack={goMenu}
|
|
296
|
+
/>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (stage.type === "remove-confirm") {
|
|
301
|
+
return (
|
|
302
|
+
<RemoveConfirm
|
|
303
|
+
provider={stage.provider}
|
|
304
|
+
completedSteps={completedSteps}
|
|
305
|
+
onConfirm={() => {
|
|
306
|
+
removeProvider(stage.provider);
|
|
307
|
+
pushStep(`Removed: ${stage.provider}`);
|
|
308
|
+
setStage({ type: "done", message: `Provider ${stage.provider} removed` });
|
|
309
|
+
}}
|
|
310
|
+
onBack={goMenu}
|
|
311
|
+
/>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (stage.type === "switch-pick") {
|
|
316
|
+
return (
|
|
317
|
+
<SwitchPick
|
|
318
|
+
completedSteps={completedSteps}
|
|
319
|
+
onSelect={(provider) => {
|
|
320
|
+
setActiveProvider(provider);
|
|
321
|
+
pushStep(`Switched to: ${provider}`);
|
|
322
|
+
setStage({ type: "done", message: `Now using ${provider}` });
|
|
323
|
+
}}
|
|
324
|
+
onBack={goMenu}
|
|
325
|
+
/>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// done
|
|
330
|
+
return <DoneScreen message={stage.message} onDone={onDone} />;
|
|
331
|
+
}
|
|
@@ -1,61 +1,53 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
5
|
-
import { ACCENT
|
|
6
|
-
|
|
7
|
-
const OPTIONS: { type:
|
|
8
|
-
{ type: "anthropic", label: "Anthropic", description: "Claude models" },
|
|
9
|
-
{ type: "openai", label: "OpenAI", description: "GPT models" },
|
|
10
|
-
{ type: "
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
);
|
|
55
|
-
})}
|
|
56
|
-
<Text color="gray">
|
|
57
|
-
↑↓ navigate · enter to select{onBack ? " · esc back" : ""}
|
|
58
|
-
</Text>
|
|
59
|
-
</Box>
|
|
60
|
-
);
|
|
61
|
-
};
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import figures from "figures";
|
|
4
|
+
import type { Provider } from "@ridit/lens-core";
|
|
5
|
+
import { ACCENT } from "../../colors";
|
|
6
|
+
|
|
7
|
+
const OPTIONS: { type: Provider; label: string; description: string }[] = [
|
|
8
|
+
{ type: "anthropic", label: "Anthropic", description: "Claude models" },
|
|
9
|
+
{ type: "openai", label: "OpenAI", description: "GPT models" },
|
|
10
|
+
{ type: "google", label: "Google", description: "Gemini models" },
|
|
11
|
+
{ type: "groq", label: "Groq", description: "Fast open-source models" },
|
|
12
|
+
{ type: "openrouter", label: "OpenRouter", description: "Multi-provider gateway" },
|
|
13
|
+
{ type: "ollama", label: "Ollama", description: "Local models" },
|
|
14
|
+
{ type: "custom", label: "Custom", description: "Any OpenAI-compatible API" },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export function ProviderTypeStep({
|
|
18
|
+
onSelect,
|
|
19
|
+
}: {
|
|
20
|
+
onSelect: (type: Provider) => void;
|
|
21
|
+
}) {
|
|
22
|
+
const [index, setIndex] = useState(0);
|
|
23
|
+
|
|
24
|
+
useInput((_, key) => {
|
|
25
|
+
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
26
|
+
if (key.downArrow) setIndex((i) => Math.min(OPTIONS.length - 1, i + 1));
|
|
27
|
+
if (key.return) onSelect(OPTIONS[index]!.type);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Box flexDirection="column" gap={1}>
|
|
32
|
+
<Text bold color={ACCENT}>
|
|
33
|
+
Select a provider
|
|
34
|
+
</Text>
|
|
35
|
+
{OPTIONS.map((opt, i) => {
|
|
36
|
+
const selected = i === index;
|
|
37
|
+
return (
|
|
38
|
+
<Box key={opt.type} marginLeft={1}>
|
|
39
|
+
<Text color={selected ? ACCENT : "white"}>
|
|
40
|
+
{selected ? figures.arrowRight : " "}
|
|
41
|
+
{" "}
|
|
42
|
+
<Text bold={selected}>{opt.label}</Text>
|
|
43
|
+
<Text color="gray">{" "}{opt.description}</Text>
|
|
44
|
+
</Text>
|
|
45
|
+
</Box>
|
|
46
|
+
);
|
|
47
|
+
})}
|
|
48
|
+
<Text color="gray" dimColor>
|
|
49
|
+
↑↓ navigate · enter to select
|
|
50
|
+
</Text>
|
|
51
|
+
</Box>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -1,69 +1,68 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<Text color="red"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<Text color="
|
|
48
|
-
<Text color="
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<Text color="
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
};
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import Spinner from "ink-spinner";
|
|
4
|
+
import { ACCENT } from "../../colors";
|
|
5
|
+
import { useThinkingPhrase, type ThinkingKind } from "../../utils/thinking";
|
|
6
|
+
|
|
7
|
+
export type Step =
|
|
8
|
+
| { type: "cloning"; status: "pending" | "done" }
|
|
9
|
+
| { type: "folder-exists"; status: "pending"; repoPath: string }
|
|
10
|
+
| { type: "error"; message: string };
|
|
11
|
+
|
|
12
|
+
const LABELS: Record<string, string> = {
|
|
13
|
+
cloning: "cloning repository",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const kindMap: Record<string, ThinkingKind> = {
|
|
17
|
+
cloning: "cloning",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function ActiveStep({ type }: { type: string }) {
|
|
21
|
+
const phrase = useThinkingPhrase(true, kindMap[type] ?? "general", 4321);
|
|
22
|
+
const label = LABELS[type] ?? type;
|
|
23
|
+
return (
|
|
24
|
+
<Box gap={1}>
|
|
25
|
+
<Text color={ACCENT}>
|
|
26
|
+
<Spinner />
|
|
27
|
+
</Text>
|
|
28
|
+
<Text color={ACCENT}>{phrase}</Text>
|
|
29
|
+
</Box>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function StepRow({ step }: { step: Step }) {
|
|
34
|
+
if (step.type === "error") {
|
|
35
|
+
return (
|
|
36
|
+
<Box gap={1}>
|
|
37
|
+
<Text color="red">✗</Text>
|
|
38
|
+
<Text color="red">{step.message}</Text>
|
|
39
|
+
</Box>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (step.type === "folder-exists") {
|
|
44
|
+
return (
|
|
45
|
+
<Box flexDirection="column">
|
|
46
|
+
<Box gap={1}>
|
|
47
|
+
<Text color="yellow">!</Text>
|
|
48
|
+
<Text color="gray">folder already exists at </Text>
|
|
49
|
+
<Text color="white">{step.repoPath}</Text>
|
|
50
|
+
</Box>
|
|
51
|
+
<Box gap={1} marginLeft={2}>
|
|
52
|
+
<Text color="gray">y re-clone · n use existing</Text>
|
|
53
|
+
</Box>
|
|
54
|
+
</Box>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (step.status === "done") {
|
|
59
|
+
return (
|
|
60
|
+
<Box gap={1}>
|
|
61
|
+
<Text color="green">✓</Text>
|
|
62
|
+
<Text color="gray">{LABELS[step.type] ?? step.type}</Text>
|
|
63
|
+
</Box>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return <ActiveStep type={step.type} />;
|
|
68
|
+
}
|