sncommit 1.0.1 → 1.0.3
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.js +0 -9
- package/package.json +4 -1
- package/bun.lock +0 -705
- package/eslint.config.mjs +0 -34
- package/src/components/App.tsx +0 -325
- package/src/components/CommitSuggestions.tsx +0 -157
- package/src/components/ConfigApp.tsx +0 -291
- package/src/components/CustomInputPrompt.tsx +0 -82
- package/src/components/StagedFiles.tsx +0 -52
- package/src/components/TuiDialog.tsx +0 -177
- package/src/index.tsx +0 -150
- package/src/services/git.ts +0 -128
- package/src/services/groq.ts +0 -400
- package/src/suppress-warnings.ts +0 -18
- package/src/types/index.ts +0 -36
- package/src/utils/config.ts +0 -66
- package/tsconfig.json +0 -19
package/eslint.config.mjs
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import js from "@eslint/js";
|
|
2
|
-
import globals from "globals";
|
|
3
|
-
import reactHooks from "eslint-plugin-react-hooks";
|
|
4
|
-
import react from "eslint-plugin-react";
|
|
5
|
-
import tseslint from "typescript-eslint";
|
|
6
|
-
import { defineConfig } from "eslint/config";
|
|
7
|
-
|
|
8
|
-
export default defineConfig(
|
|
9
|
-
{ ignores: ["dist", "eslint.config.mjs"] },
|
|
10
|
-
{
|
|
11
|
-
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
12
|
-
files: ["**/*.{ts,tsx,js,jsx}"],
|
|
13
|
-
languageOptions: {
|
|
14
|
-
ecmaVersion: 2020,
|
|
15
|
-
globals: globals.node,
|
|
16
|
-
},
|
|
17
|
-
plugins: {
|
|
18
|
-
"react-hooks": reactHooks,
|
|
19
|
-
react,
|
|
20
|
-
},
|
|
21
|
-
rules: {
|
|
22
|
-
...reactHooks.configs.recommended.rules,
|
|
23
|
-
"react/react-in-jsx-scope": "off",
|
|
24
|
-
// Allow explicit any for now as we might have some generic types or old code
|
|
25
|
-
"@typescript-eslint/no-explicit-any": "warn",
|
|
26
|
-
"@typescript-eslint/no-unused-vars": [
|
|
27
|
-
"warn",
|
|
28
|
-
{ argsIgnorePattern: "^_" },
|
|
29
|
-
],
|
|
30
|
-
// Suppress punycode warning in index.tsx requires process manipulation which is hacky but fine
|
|
31
|
-
"@typescript-eslint/ban-ts-comment": "off",
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
);
|
package/src/components/App.tsx
DELETED
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { Box, Text, useApp, useInput, useStdout } from "ink";
|
|
3
|
-
import { GitService } from "../services/git";
|
|
4
|
-
import { GroqService } from "../services/groq";
|
|
5
|
-
import { configManager } from "../utils/config";
|
|
6
|
-
import { StagedFiles } from "./StagedFiles";
|
|
7
|
-
import { CommitSuggestions } from "./CommitSuggestions";
|
|
8
|
-
import { CustomInputPrompt } from "./CustomInputPrompt";
|
|
9
|
-
import { AppState, GitFile } from "../types";
|
|
10
|
-
|
|
11
|
-
interface AppProps {
|
|
12
|
-
addAll?: boolean;
|
|
13
|
-
onExit: (message?: string) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const BetterCommitApp: React.FC<AppProps> = ({
|
|
17
|
-
addAll: _addAll = false,
|
|
18
|
-
onExit,
|
|
19
|
-
}) => {
|
|
20
|
-
const { exit } = useApp();
|
|
21
|
-
const { write } = useStdout();
|
|
22
|
-
const [state, setState] = useState<AppState>({
|
|
23
|
-
stagedFiles: [],
|
|
24
|
-
suggestions: [],
|
|
25
|
-
selectedIndex: 0,
|
|
26
|
-
isLoading: false,
|
|
27
|
-
error: undefined,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const [isUsingFallback, setIsUsingFallback] = useState(false);
|
|
31
|
-
|
|
32
|
-
const [successMessage, setSuccessMessage] = useState<string | undefined>(
|
|
33
|
-
undefined,
|
|
34
|
-
);
|
|
35
|
-
const [customInput, setCustomInput] = useState("");
|
|
36
|
-
const [isCustomInputMode, setIsCustomInputMode] = useState(false);
|
|
37
|
-
|
|
38
|
-
// Auto-exit when showing success message
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
if (successMessage) {
|
|
41
|
-
write("\x1B[2J\x1B[0f");
|
|
42
|
-
const timer = setTimeout(() => {
|
|
43
|
-
exit();
|
|
44
|
-
}, 1500);
|
|
45
|
-
return () => clearTimeout(timer);
|
|
46
|
-
}
|
|
47
|
-
}, [successMessage, exit, write]);
|
|
48
|
-
|
|
49
|
-
// Auto-exit when error occurs
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (state.error) {
|
|
52
|
-
exit();
|
|
53
|
-
}
|
|
54
|
-
}, [state.error, exit]);
|
|
55
|
-
|
|
56
|
-
// Handle global exit keys (disabled when custom input is active)
|
|
57
|
-
useInput(
|
|
58
|
-
(input, key) => {
|
|
59
|
-
if (key.escape || (key.ctrl && input === "c")) {
|
|
60
|
-
onExit("Operation cancelled");
|
|
61
|
-
exit();
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
{ isActive: !isCustomInputMode },
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const config = configManager.getConfig();
|
|
68
|
-
const gitService = new GitService();
|
|
69
|
-
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
const initializeApp = async () => {
|
|
72
|
-
try {
|
|
73
|
-
const stagedFiles = await gitService.getStagedFiles();
|
|
74
|
-
// No need to check for empty staged files here as index.tsx handles it
|
|
75
|
-
|
|
76
|
-
await gitService.getDiff();
|
|
77
|
-
|
|
78
|
-
await fetchSuggestions(stagedFiles);
|
|
79
|
-
|
|
80
|
-
setState((prev) => ({
|
|
81
|
-
...prev,
|
|
82
|
-
stagedFiles,
|
|
83
|
-
}));
|
|
84
|
-
} catch (error) {
|
|
85
|
-
setState((prev) => ({
|
|
86
|
-
...prev,
|
|
87
|
-
error: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
88
|
-
}));
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
initializeApp();
|
|
93
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
94
|
-
}, []);
|
|
95
|
-
|
|
96
|
-
const fetchSuggestions = async (
|
|
97
|
-
stagedFiles: GitFile[],
|
|
98
|
-
customPrompt?: string,
|
|
99
|
-
) => {
|
|
100
|
-
if (!config.groqApiKey || config.groqApiKey.trim() === "") {
|
|
101
|
-
setState((prev) => ({
|
|
102
|
-
...prev,
|
|
103
|
-
error:
|
|
104
|
-
'Groq API key not configured. Run "better-commit config" to set it up.',
|
|
105
|
-
isLoading: false,
|
|
106
|
-
}));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const groqService = new GroqService(config.groqApiKey, config);
|
|
112
|
-
const diff = await gitService.getDiff();
|
|
113
|
-
const diffStats = await gitService.getDiffStats();
|
|
114
|
-
const recentCommits = await gitService.getRecentCommits(
|
|
115
|
-
config.maxHistoryCommits || 50,
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
let suggestions;
|
|
119
|
-
if (customPrompt) {
|
|
120
|
-
suggestions =
|
|
121
|
-
await groqService.generateCommitSuggestionsFromCustomInput(
|
|
122
|
-
stagedFiles,
|
|
123
|
-
diff,
|
|
124
|
-
customPrompt,
|
|
125
|
-
recentCommits,
|
|
126
|
-
diffStats,
|
|
127
|
-
);
|
|
128
|
-
} else {
|
|
129
|
-
suggestions = await groqService.generateCommitSuggestions(
|
|
130
|
-
stagedFiles,
|
|
131
|
-
diff,
|
|
132
|
-
recentCommits,
|
|
133
|
-
diffStats,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const hasFallback = suggestions.some((s) => s.isFallback);
|
|
138
|
-
setState((prev) => ({
|
|
139
|
-
...prev,
|
|
140
|
-
suggestions,
|
|
141
|
-
isLoading: false,
|
|
142
|
-
error: undefined,
|
|
143
|
-
}));
|
|
144
|
-
setIsUsingFallback(hasFallback);
|
|
145
|
-
} catch (error) {
|
|
146
|
-
setState((prev) => ({
|
|
147
|
-
...prev,
|
|
148
|
-
error: `Failed to generate suggestions: ${error instanceof Error ? error.message : String(error)}`,
|
|
149
|
-
isLoading: false,
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const handleSelect = (index: number) => {
|
|
155
|
-
setState((prev) => ({ ...prev, selectedIndex: index }));
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const handleCommit = async (index: number) => {
|
|
159
|
-
const selectedSuggestion = state.suggestions[index];
|
|
160
|
-
if (selectedSuggestion) {
|
|
161
|
-
try {
|
|
162
|
-
await gitService.commit(selectedSuggestion.message);
|
|
163
|
-
setSuccessMessage(selectedSuggestion.message);
|
|
164
|
-
} catch (error) {
|
|
165
|
-
setState((prev) => ({
|
|
166
|
-
...prev,
|
|
167
|
-
error: `Failed to commit: ${error instanceof Error ? error.message : String(error)}`,
|
|
168
|
-
}));
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const handleTryAgain = () => {
|
|
174
|
-
setState((prev) => ({ ...prev, isLoading: true, suggestions: [] }));
|
|
175
|
-
fetchSuggestions(state.stagedFiles);
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const handleCustomInput = () => {
|
|
179
|
-
setIsCustomInputMode(true);
|
|
180
|
-
setCustomInput("");
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const handleCustomInputCancel = () => {
|
|
184
|
-
setIsCustomInputMode(false);
|
|
185
|
-
onExit("Custom input cancelled");
|
|
186
|
-
exit();
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const handleCustomInputSubmit = async () => {
|
|
190
|
-
if (!customInput.trim()) {
|
|
191
|
-
setIsCustomInputMode(false);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
setIsCustomInputMode(false);
|
|
196
|
-
setState((prev) => ({ ...prev, isLoading: true, suggestions: [] }));
|
|
197
|
-
await fetchSuggestions(state.stagedFiles, customInput.trim());
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
// Error State
|
|
201
|
-
if (state.error) {
|
|
202
|
-
return (
|
|
203
|
-
<Box
|
|
204
|
-
borderStyle="single"
|
|
205
|
-
borderColor="#ef4444"
|
|
206
|
-
padding={2}
|
|
207
|
-
marginBottom={1}
|
|
208
|
-
>
|
|
209
|
-
<Text color="#ef4444" bold>
|
|
210
|
-
Error
|
|
211
|
-
</Text>
|
|
212
|
-
<Box marginTop={1}>
|
|
213
|
-
<Text color="#e5e7eb">{state.error}</Text>
|
|
214
|
-
</Box>
|
|
215
|
-
</Box>
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Success State
|
|
220
|
-
if (successMessage) {
|
|
221
|
-
return (
|
|
222
|
-
<Box
|
|
223
|
-
flexDirection="column"
|
|
224
|
-
padding={2}
|
|
225
|
-
justifyContent="center"
|
|
226
|
-
alignItems="center"
|
|
227
|
-
flexGrow={1}
|
|
228
|
-
width="100%"
|
|
229
|
-
height="100%"
|
|
230
|
-
>
|
|
231
|
-
<Box
|
|
232
|
-
flexDirection="column"
|
|
233
|
-
borderStyle="single"
|
|
234
|
-
borderColor="#10b981"
|
|
235
|
-
padding={2}
|
|
236
|
-
width={60}
|
|
237
|
-
>
|
|
238
|
-
<Box marginBottom={1} justifyContent="center">
|
|
239
|
-
<Text color="#10b981" bold>
|
|
240
|
-
Commit Successful
|
|
241
|
-
</Text>
|
|
242
|
-
</Box>
|
|
243
|
-
<Box marginBottom={1} justifyContent="center">
|
|
244
|
-
<Text color="#e5e7eb">"{successMessage}"</Text>
|
|
245
|
-
</Box>
|
|
246
|
-
<Box justifyContent="center">
|
|
247
|
-
<Text color="#10b981">
|
|
248
|
-
Changes have been committed successfully!
|
|
249
|
-
</Text>
|
|
250
|
-
</Box>
|
|
251
|
-
</Box>
|
|
252
|
-
</Box>
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Loading State
|
|
257
|
-
if (state.stagedFiles.length === 0 && !state.error) {
|
|
258
|
-
return (
|
|
259
|
-
<Box flexDirection="column" padding={2} justifyContent="center">
|
|
260
|
-
<Text bold color="#8b5cf6">
|
|
261
|
-
Better-Commit
|
|
262
|
-
</Text>
|
|
263
|
-
<Box marginTop={1}>
|
|
264
|
-
<Text color="#6b7280">Loading staged files...</Text>
|
|
265
|
-
</Box>
|
|
266
|
-
</Box>
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return (
|
|
271
|
-
<Box flexDirection="column" padding={1}>
|
|
272
|
-
{/* Header */}
|
|
273
|
-
<Box marginBottom={1}>
|
|
274
|
-
<Text bold color="#8b5cf6">
|
|
275
|
-
Better-Commit
|
|
276
|
-
</Text>
|
|
277
|
-
<Text color="#6b7280"> • AI-Powered Commit Suggestions</Text>
|
|
278
|
-
</Box>
|
|
279
|
-
|
|
280
|
-
{/* Staged Files */}
|
|
281
|
-
<StagedFiles files={state.stagedFiles} />
|
|
282
|
-
|
|
283
|
-
{/* Commit Suggestions or Custom Input */}
|
|
284
|
-
{isCustomInputMode ? (
|
|
285
|
-
<CustomInputPrompt
|
|
286
|
-
value={customInput}
|
|
287
|
-
onChange={setCustomInput}
|
|
288
|
-
onSubmit={handleCustomInputSubmit}
|
|
289
|
-
onCancel={handleCustomInputCancel}
|
|
290
|
-
/>
|
|
291
|
-
) : (
|
|
292
|
-
<CommitSuggestions
|
|
293
|
-
suggestions={state.suggestions}
|
|
294
|
-
selectedIndex={state.selectedIndex}
|
|
295
|
-
onSelect={handleSelect}
|
|
296
|
-
onCommit={handleCommit}
|
|
297
|
-
onTryAgain={handleTryAgain}
|
|
298
|
-
onCustomInput={handleCustomInput}
|
|
299
|
-
isLoading={state.isLoading}
|
|
300
|
-
isUsingFallback={isUsingFallback}
|
|
301
|
-
/>
|
|
302
|
-
)}
|
|
303
|
-
|
|
304
|
-
{/* Footer - hide when custom input is open */}
|
|
305
|
-
{!isCustomInputMode && (
|
|
306
|
-
<Box
|
|
307
|
-
borderTop={true}
|
|
308
|
-
borderStyle="single"
|
|
309
|
-
borderColor="#374151"
|
|
310
|
-
paddingTop={1}
|
|
311
|
-
paddingLeft={1}
|
|
312
|
-
paddingRight={1}
|
|
313
|
-
>
|
|
314
|
-
<Text color="#6b7280">
|
|
315
|
-
<Text color="#60a5fa">↑↓</Text> navigate{" "}
|
|
316
|
-
<Text color="#10b981">Enter</Text> select{" "}
|
|
317
|
-
<Text color="#ef4444">Esc</Text> exit
|
|
318
|
-
</Text>
|
|
319
|
-
</Box>
|
|
320
|
-
)}
|
|
321
|
-
</Box>
|
|
322
|
-
);
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
export const App = BetterCommitApp;
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import { CommitSuggestion } from "../types";
|
|
4
|
-
|
|
5
|
-
interface CommitSuggestionsProps {
|
|
6
|
-
suggestions: CommitSuggestion[];
|
|
7
|
-
selectedIndex: number;
|
|
8
|
-
onSelect: (index: number) => void;
|
|
9
|
-
onCommit: (index: number) => void;
|
|
10
|
-
onTryAgain: () => void;
|
|
11
|
-
onCustomInput: () => void;
|
|
12
|
-
isLoading: boolean;
|
|
13
|
-
isUsingFallback?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const CommitSuggestions: React.FC<CommitSuggestionsProps> = ({
|
|
17
|
-
suggestions,
|
|
18
|
-
selectedIndex,
|
|
19
|
-
onSelect,
|
|
20
|
-
onCommit,
|
|
21
|
-
onTryAgain,
|
|
22
|
-
onCustomInput,
|
|
23
|
-
isLoading,
|
|
24
|
-
isUsingFallback = false,
|
|
25
|
-
}) => {
|
|
26
|
-
const totalOptions = isUsingFallback
|
|
27
|
-
? suggestions.length
|
|
28
|
-
: suggestions.length + 2;
|
|
29
|
-
const [frame, setFrame] = useState(0);
|
|
30
|
-
|
|
31
|
-
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
32
|
-
// Alternative spinners:
|
|
33
|
-
// const spinnerFrames = ['◐', '◓', '◑', '◒'];
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (isLoading) {
|
|
37
|
-
const timer = setInterval(() => {
|
|
38
|
-
setFrame((prev) => (prev + 1) % spinnerFrames.length);
|
|
39
|
-
}, 80);
|
|
40
|
-
return () => clearInterval(timer);
|
|
41
|
-
}
|
|
42
|
-
}, [isLoading, spinnerFrames.length]);
|
|
43
|
-
|
|
44
|
-
useInput((input, key) => {
|
|
45
|
-
if (key.upArrow) {
|
|
46
|
-
const newIndex = selectedIndex > 0 ? selectedIndex - 1 : totalOptions - 1;
|
|
47
|
-
onSelect(newIndex);
|
|
48
|
-
} else if (key.downArrow) {
|
|
49
|
-
const newIndex = selectedIndex < totalOptions - 1 ? selectedIndex + 1 : 0;
|
|
50
|
-
onSelect(newIndex);
|
|
51
|
-
} else if (key.return) {
|
|
52
|
-
if (selectedIndex < suggestions.length) {
|
|
53
|
-
onCommit(selectedIndex);
|
|
54
|
-
} else if (!isUsingFallback) {
|
|
55
|
-
if (selectedIndex === suggestions.length) {
|
|
56
|
-
onTryAgain();
|
|
57
|
-
} else if (selectedIndex === suggestions.length + 1) {
|
|
58
|
-
onCustomInput();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
if (isLoading) {
|
|
65
|
-
return (
|
|
66
|
-
<Box borderStyle="round" borderColor="#374151" paddingX={1}>
|
|
67
|
-
<Text color="#6b7280">
|
|
68
|
-
{spinnerFrames[frame]} Generating commit suggestions...
|
|
69
|
-
</Text>
|
|
70
|
-
</Box>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (suggestions.length === 0) {
|
|
75
|
-
return (
|
|
76
|
-
<Box borderStyle="round" borderColor="#ef4444" paddingX={1}>
|
|
77
|
-
<Text color="#ef4444">No suggestions available</Text>
|
|
78
|
-
</Box>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<Box
|
|
84
|
-
flexDirection="column"
|
|
85
|
-
borderStyle="round"
|
|
86
|
-
borderColor="#374151"
|
|
87
|
-
paddingX={1}
|
|
88
|
-
>
|
|
89
|
-
<Box>
|
|
90
|
-
<Text bold color="#8b5cf6">
|
|
91
|
-
Commit Messages
|
|
92
|
-
</Text>
|
|
93
|
-
</Box>
|
|
94
|
-
|
|
95
|
-
{suggestions.map((suggestion, index) => {
|
|
96
|
-
const isSelected = index === selectedIndex;
|
|
97
|
-
const displayMsg =
|
|
98
|
-
suggestion.message.length > 65
|
|
99
|
-
? suggestion.message.substring(0, 62) + "..."
|
|
100
|
-
: suggestion.message;
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<Box key={index} marginLeft={1}>
|
|
104
|
-
<Text color={isSelected ? "#10b981" : "#6b7280"}>
|
|
105
|
-
{isSelected ? "❯" : " "}
|
|
106
|
-
</Text>
|
|
107
|
-
<Text color={isSelected ? "#ffffff" : "#9ca3af"} bold={isSelected}>
|
|
108
|
-
{" "}
|
|
109
|
-
{displayMsg}
|
|
110
|
-
</Text>
|
|
111
|
-
</Box>
|
|
112
|
-
);
|
|
113
|
-
})}
|
|
114
|
-
|
|
115
|
-
{!isUsingFallback && (
|
|
116
|
-
<Box marginTop={1} marginLeft={1} flexDirection="row">
|
|
117
|
-
<Box marginRight={3}>
|
|
118
|
-
<Text
|
|
119
|
-
color={
|
|
120
|
-
selectedIndex === suggestions.length ? "#10b981" : "#6b7280"
|
|
121
|
-
}
|
|
122
|
-
>
|
|
123
|
-
{selectedIndex === suggestions.length ? "❯" : " "}
|
|
124
|
-
</Text>
|
|
125
|
-
<Text
|
|
126
|
-
color={
|
|
127
|
-
selectedIndex === suggestions.length ? "#60a5fa" : "#6b7280"
|
|
128
|
-
}
|
|
129
|
-
bold={selectedIndex === suggestions.length}
|
|
130
|
-
>
|
|
131
|
-
{" "}
|
|
132
|
-
↻ Try again
|
|
133
|
-
</Text>
|
|
134
|
-
</Box>
|
|
135
|
-
<Box>
|
|
136
|
-
<Text
|
|
137
|
-
color={
|
|
138
|
-
selectedIndex === suggestions.length + 1 ? "#10b981" : "#6b7280"
|
|
139
|
-
}
|
|
140
|
-
>
|
|
141
|
-
{selectedIndex === suggestions.length + 1 ? "❯" : " "}
|
|
142
|
-
</Text>
|
|
143
|
-
<Text
|
|
144
|
-
color={
|
|
145
|
-
selectedIndex === suggestions.length + 1 ? "#f59e0b" : "#6b7280"
|
|
146
|
-
}
|
|
147
|
-
bold={selectedIndex === suggestions.length + 1}
|
|
148
|
-
>
|
|
149
|
-
{" "}
|
|
150
|
-
✎ Custom input
|
|
151
|
-
</Text>
|
|
152
|
-
</Box>
|
|
153
|
-
</Box>
|
|
154
|
-
)}
|
|
155
|
-
</Box>
|
|
156
|
-
);
|
|
157
|
-
};
|