@within-7/minto 0.3.9 → 0.3.10

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.
Files changed (141) hide show
  1. package/dist/commands/agents/AgentsCommand.js +459 -655
  2. package/dist/commands/agents/AgentsCommand.js.map +2 -2
  3. package/dist/commands/agents/types.js +1 -0
  4. package/dist/commands/agents/types.js.map +2 -2
  5. package/dist/commands/agents/utils/fileOperations.js +96 -36
  6. package/dist/commands/agents/utils/fileOperations.js.map +3 -3
  7. package/dist/commands/agents/utils/index.js +3 -1
  8. package/dist/commands/agents/utils/index.js.map +2 -2
  9. package/dist/commands/context.js +54 -23
  10. package/dist/commands/context.js.map +2 -2
  11. package/dist/commands/export.js +673 -93
  12. package/dist/commands/export.js.map +2 -2
  13. package/dist/commands/language.js +19 -46
  14. package/dist/commands/language.js.map +2 -2
  15. package/dist/commands/mcp-interactive.js +419 -217
  16. package/dist/commands/mcp-interactive.js.map +2 -2
  17. package/dist/commands/model.js +415 -66
  18. package/dist/commands/model.js.map +2 -2
  19. package/dist/commands/permissions.js +75 -49
  20. package/dist/commands/permissions.js.map +2 -2
  21. package/dist/commands/plugin.js +882 -185
  22. package/dist/commands/plugin.js.map +3 -3
  23. package/dist/commands/resume.js +1 -1
  24. package/dist/commands/resume.js.map +1 -1
  25. package/dist/commands/sandbox.js +168 -70
  26. package/dist/commands/sandbox.js.map +2 -2
  27. package/dist/commands/setup.js +593 -107
  28. package/dist/commands/setup.js.map +2 -2
  29. package/dist/commands/stats.js +188 -131
  30. package/dist/commands/stats.js.map +2 -2
  31. package/dist/commands/status.js +75 -13
  32. package/dist/commands/status.js.map +2 -2
  33. package/dist/commands/undo.js +138 -174
  34. package/dist/commands/undo.js.map +2 -2
  35. package/dist/commands.js.map +1 -1
  36. package/dist/components/Help.js +165 -32
  37. package/dist/components/Help.js.map +2 -2
  38. package/dist/components/InfoPanel/InfoPanel.js +123 -0
  39. package/dist/components/InfoPanel/InfoPanel.js.map +7 -0
  40. package/dist/components/InfoPanel/index.js +5 -0
  41. package/dist/components/InfoPanel/index.js.map +7 -0
  42. package/dist/components/InfoPanel/types.js +1 -0
  43. package/dist/components/InfoPanel/types.js.map +7 -0
  44. package/dist/components/ModelSelector/BrandTextInput.js +43 -0
  45. package/dist/components/ModelSelector/BrandTextInput.js.map +7 -0
  46. package/dist/components/ModelSelector/ModelSelector.js +419 -501
  47. package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
  48. package/dist/components/ModelSelector/WizardContainer.js +45 -0
  49. package/dist/components/ModelSelector/WizardContainer.js.map +7 -0
  50. package/dist/components/ModelSelector/index.js +1 -3
  51. package/dist/components/ModelSelector/index.js.map +2 -2
  52. package/dist/components/PromptInput.js +5 -5
  53. package/dist/components/PromptInput.js.map +2 -2
  54. package/dist/components/SimpleSelector/SimpleSelector.js +154 -0
  55. package/dist/components/SimpleSelector/SimpleSelector.js.map +7 -0
  56. package/dist/components/SimpleSelector/index.js +5 -0
  57. package/dist/components/SimpleSelector/index.js.map +7 -0
  58. package/dist/components/SimpleSelector/types.js +1 -0
  59. package/dist/components/SimpleSelector/types.js.map +7 -0
  60. package/dist/components/StatusOverlayContent.js +21 -0
  61. package/dist/components/StatusOverlayContent.js.map +7 -0
  62. package/dist/components/TabbedListView/ScrollableList.js +31 -5
  63. package/dist/components/TabbedListView/ScrollableList.js.map +2 -2
  64. package/dist/components/TabbedListView/TabbedListView.js +122 -47
  65. package/dist/components/TabbedListView/TabbedListView.js.map +2 -2
  66. package/dist/core/backupHook.js +29 -0
  67. package/dist/core/backupHook.js.map +7 -0
  68. package/dist/core/config/defaults.js +8 -2
  69. package/dist/core/config/defaults.js.map +2 -2
  70. package/dist/core/config/schema.js +14 -2
  71. package/dist/core/config/schema.js.map +2 -2
  72. package/dist/core/costTracker.js +0 -16
  73. package/dist/core/costTracker.js.map +2 -2
  74. package/dist/cost-tracker.js +0 -16
  75. package/dist/cost-tracker.js.map +2 -2
  76. package/dist/entrypoints/bootstrap.js +3 -1
  77. package/dist/entrypoints/bootstrap.js.map +2 -2
  78. package/dist/entrypoints/cli.js +32 -0
  79. package/dist/entrypoints/cli.js.map +2 -2
  80. package/dist/i18n/locales/en.js +300 -1
  81. package/dist/i18n/locales/en.js.map +2 -2
  82. package/dist/i18n/locales/zh-CN.js +301 -2
  83. package/dist/i18n/locales/zh-CN.js.map +2 -2
  84. package/dist/i18n/types.js.map +1 -1
  85. package/dist/services/customCommands.js +30 -8
  86. package/dist/services/customCommands.js.map +2 -2
  87. package/dist/services/plugins/lspServers.js +1 -1
  88. package/dist/services/plugins/lspServers.js.map +2 -2
  89. package/dist/services/plugins/pluginRuntime.js +2 -1
  90. package/dist/services/plugins/pluginRuntime.js.map +2 -2
  91. package/dist/services/plugins/pluginValidation.js +10 -3
  92. package/dist/services/plugins/pluginValidation.js.map +2 -2
  93. package/dist/services/plugins/skillMarketplace.js +16 -8
  94. package/dist/services/plugins/skillMarketplace.js.map +2 -2
  95. package/dist/services/systemReminder.js +17 -6
  96. package/dist/services/systemReminder.js.map +2 -2
  97. package/dist/tools/FileEditTool/FileEditTool.js +7 -0
  98. package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
  99. package/dist/tools/FileWriteTool/FileWriteTool.js +7 -0
  100. package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
  101. package/dist/tools/MultiEditTool/MultiEditTool.js +7 -0
  102. package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
  103. package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -0
  104. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  105. package/dist/tools/TaskTool/TaskTool.js +9 -6
  106. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  107. package/dist/types/PermissionMode.js.map +1 -1
  108. package/dist/types/plugin.js +2 -4
  109. package/dist/types/plugin.js.map +2 -2
  110. package/dist/utils/agentHookExecutor.js +1 -4
  111. package/dist/utils/agentHookExecutor.js.map +2 -2
  112. package/dist/utils/agentLoader.js +67 -13
  113. package/dist/utils/agentLoader.js.map +2 -2
  114. package/dist/utils/agentMemory.js.map +2 -2
  115. package/dist/utils/claudeCodeSync.js +439 -0
  116. package/dist/utils/claudeCodeSync.js.map +7 -0
  117. package/dist/utils/config.js +1 -23
  118. package/dist/utils/config.js.map +2 -2
  119. package/dist/utils/execFileNoThrow.js +2 -1
  120. package/dist/utils/execFileNoThrow.js.map +2 -2
  121. package/dist/utils/marketplaceManager.js +80 -43
  122. package/dist/utils/marketplaceManager.js.map +2 -2
  123. package/dist/utils/messages.js +2 -2
  124. package/dist/utils/messages.js.map +2 -2
  125. package/dist/utils/pluginInstaller.js +34 -24
  126. package/dist/utils/pluginInstaller.js.map +2 -2
  127. package/dist/utils/pluginLoader.js +48 -25
  128. package/dist/utils/pluginLoader.js.map +2 -2
  129. package/dist/utils/repoFetcher.js +110 -0
  130. package/dist/utils/repoFetcher.js.map +7 -0
  131. package/dist/utils/skillLoader.js +18 -6
  132. package/dist/utils/skillLoader.js.map +2 -2
  133. package/dist/utils/stringSubstitution.js +4 -5
  134. package/dist/utils/stringSubstitution.js.map +2 -2
  135. package/dist/utils/teamConfig.js +153 -13
  136. package/dist/utils/teamConfig.js.map +2 -2
  137. package/dist/utils/terminal.js +1 -1
  138. package/dist/utils/terminal.js.map +2 -2
  139. package/dist/version.js +2 -2
  140. package/dist/version.js.map +1 -1
  141. package/package.json +6 -6
@@ -1,7 +1,5 @@
1
1
  import React, { useState, useEffect } from "react";
2
- import { Box, Text, useInput, Newline } from "ink";
3
- import { getTheme } from "../../utils/theme.js";
4
- import { Select } from "../CustomSelect/select.js";
2
+ import { Text, useInput } from "ink";
5
3
  import { getModelManager } from "../../utils/model.js";
6
4
  import { useExitOnCtrlCD } from "../../hooks/useExitOnCtrlCD.js";
7
5
  import {
@@ -10,9 +8,7 @@ import {
10
8
  setModelPointer
11
9
  } from "../../utils/config.js";
12
10
  import models, { providers } from "../../constants/models.js";
13
- import TextInput from "../TextInput.js";
14
11
  import OpenAI from "openai";
15
- import chalk from "chalk";
16
12
  import { verifyApiKey } from "../../services/claude.js";
17
13
  import { fetchCustomModels } from "../../services/openai.js";
18
14
  import {
@@ -20,24 +16,23 @@ import {
20
16
  validateGPT5Config
21
17
  } from "../../services/gpt5ConnectionTest.js";
22
18
  import { SEMANTIC_COLORS } from "../../constants/colors.js";
23
- import { ScreenContainer } from "./ScreenContainer.js";
19
+ import { t } from "../../i18n/index.js";
20
+ import { SimpleSelector } from "../SimpleSelector/SimpleSelector.js";
21
+ import { InfoPanel } from "../InfoPanel/InfoPanel.js";
22
+ import { BrandTextInput } from "./BrandTextInput.js";
24
23
  import {
25
24
  CONTEXT_LENGTH_OPTIONS,
26
25
  DEFAULT_CONTEXT_LENGTH,
27
26
  MAX_TOKENS_OPTIONS,
28
27
  DEFAULT_MAX_TOKENS
29
28
  } from "./constants.js";
30
- import { useEscapeNavigation } from "./hooks/index.js";
31
29
  function printModelConfig() {
32
30
  const config = getGlobalConfig();
33
31
  const modelProfiles = config.modelProfiles || [];
34
32
  const activeProfiles = modelProfiles.filter((p) => p.isActive);
35
33
  if (activeProfiles.length === 0) {
36
- console.log(chalk.gray(" \u23BF No active model profiles configured"));
37
34
  return;
38
35
  }
39
- const profileSummary = activeProfiles.map((p) => `${p.name} (${p.provider}: ${p.modelName})`).join(" | ");
40
- console.log(chalk.gray(` \u23BF ${profileSummary}`));
41
36
  }
42
37
  function ModelSelector({
43
38
  onDone: onDoneProp,
@@ -48,12 +43,11 @@ function ModelSelector({
48
43
  skipModelType = false
49
44
  }) {
50
45
  const config = getGlobalConfig();
51
- const theme = getTheme();
52
46
  const onDone = () => {
53
47
  printModelConfig();
54
48
  onDoneProp();
55
49
  };
56
- const exitState = useExitOnCtrlCD(() => process.exit(0));
50
+ useExitOnCtrlCD(() => process.exit(0));
57
51
  const getInitialScreen = () => {
58
52
  return "provider";
59
53
  };
@@ -305,10 +299,6 @@ function ModelSelector({
305
299
  }));
306
300
  } catch (error) {
307
301
  lastError = error;
308
- console.log(
309
- `Anthropic API failed for ${provider}, trying OpenAI format:`,
310
- error
311
- );
312
302
  }
313
303
  try {
314
304
  const models2 = await fetchCustomModels(baseURL, apiKey);
@@ -322,10 +312,6 @@ function ModelSelector({
322
312
  }));
323
313
  } catch (error) {
324
314
  lastError = error;
325
- console.log(
326
- `OpenAI API failed for ${provider}, falling back to manual input:`,
327
- error
328
- );
329
315
  }
330
316
  let errorMessage = `Failed to fetch ${provider} models using both Anthropic and OpenAI API formats`;
331
317
  if (lastError) {
@@ -763,7 +749,6 @@ function ModelSelector({
763
749
  } else {
764
750
  setModelLoadError(`Error loading Ollama models: ${errorMessage}`);
765
751
  }
766
- console.error("Error fetching Ollama models:", error);
767
752
  return [];
768
753
  }
769
754
  }
@@ -787,7 +772,6 @@ function ModelSelector({
787
772
  return models2;
788
773
  } catch (error) {
789
774
  lastError = error instanceof Error ? error : new Error(String(error));
790
- console.log(`Model fetch attempt ${attempt} failed:`, lastError.message);
791
775
  if (attempt === MAX_RETRIES) {
792
776
  break;
793
777
  }
@@ -915,7 +899,6 @@ function ModelSelector({
915
899
  navigateTo("model");
916
900
  return fetchedModels;
917
901
  } catch (error) {
918
- console.error("Error fetching models:", error);
919
902
  throw error;
920
903
  } finally {
921
904
  setIsLoadingModels(false);
@@ -927,8 +910,7 @@ function ModelSelector({
927
910
  navigateTo("resourceName");
928
911
  return;
929
912
  }
930
- fetchModelsWithRetry().catch((error) => {
931
- console.error("Final error after retries:", error);
913
+ fetchModelsWithRetry().catch(() => {
932
914
  });
933
915
  }
934
916
  function handleResourceNameSubmit(name) {
@@ -1057,9 +1039,6 @@ function ModelSelector({
1057
1039
  if (isOpenAICompatible) {
1058
1040
  const isGPT5 = selectedModel?.toLowerCase().includes("gpt-5");
1059
1041
  if (isGPT5) {
1060
- console.log(
1061
- `\u{1F680} Using specialized GPT-5 connection test for model: ${selectedModel}`
1062
- );
1063
1042
  const configValidation = validateGPT5Config({
1064
1043
  model: selectedModel,
1065
1044
  apiKey,
@@ -1153,18 +1132,11 @@ function ModelSelector({
1153
1132
  stream: false
1154
1133
  };
1155
1134
  if (selectedModel && selectedModel.toLowerCase().includes("gpt-5")) {
1156
- console.log(`Applying GPT-5 parameter fix for model: ${selectedModel}`);
1157
1135
  if (testPayload.max_tokens) {
1158
1136
  testPayload.max_completion_tokens = testPayload.max_tokens;
1159
1137
  delete testPayload.max_tokens;
1160
- console.log(
1161
- `Transformed max_tokens \u2192 max_completion_tokens: ${testPayload.max_completion_tokens}`
1162
- );
1163
1138
  }
1164
1139
  if (testPayload.temperature !== void 0 && testPayload.temperature !== 1) {
1165
- console.log(
1166
- `Adjusting temperature from ${testPayload.temperature} to 1 for GPT-5`
1167
- );
1168
1140
  testPayload.temperature = 1;
1169
1141
  }
1170
1142
  }
@@ -1184,10 +1156,6 @@ function ModelSelector({
1184
1156
  });
1185
1157
  if (response.ok) {
1186
1158
  const data = await response.json();
1187
- console.log(
1188
- "[DEBUG] Connection test response:",
1189
- JSON.stringify(data, null, 2)
1190
- );
1191
1159
  let responseContent = "";
1192
1160
  if (data.choices && data.choices.length > 0) {
1193
1161
  responseContent = data.choices[0]?.message?.content || "";
@@ -1196,7 +1164,6 @@ function ModelSelector({
1196
1164
  } else if (data.output) {
1197
1165
  responseContent = data.output?.text || data.output || "";
1198
1166
  }
1199
- console.log("[DEBUG] Extracted response content:", responseContent);
1200
1167
  const containsYes = responseContent.toLowerCase().includes("yes");
1201
1168
  if (containsYes) {
1202
1169
  return {
@@ -1251,9 +1218,6 @@ function ModelSelector({
1251
1218
  // Fast response for connection test
1252
1219
  }
1253
1220
  };
1254
- console.log(`\u{1F527} Testing GPT-5 Responses API for model: ${selectedModel}`);
1255
- console.log(`\u{1F527} Test URL: ${testURL}`);
1256
- console.log(`\u{1F527} Test payload:`, JSON.stringify(testPayload, null, 2));
1257
1221
  const headers = {
1258
1222
  "Content-Type": "application/json",
1259
1223
  Authorization: `Bearer ${apiKey}`
@@ -1266,17 +1230,12 @@ function ModelSelector({
1266
1230
  });
1267
1231
  if (response.ok) {
1268
1232
  const data = await response.json();
1269
- console.log(
1270
- "[DEBUG] Responses API connection test response:",
1271
- JSON.stringify(data, null, 2)
1272
- );
1273
1233
  let responseContent = "";
1274
1234
  if (data.output_text) {
1275
1235
  responseContent = data.output_text;
1276
1236
  } else if (data.output) {
1277
1237
  responseContent = typeof data.output === "string" ? data.output : data.output.text || "";
1278
1238
  }
1279
- console.log("[DEBUG] Extracted response content:", responseContent);
1280
1239
  const containsYes = responseContent.toLowerCase().includes("yes");
1281
1240
  if (containsYes) {
1282
1241
  return {
@@ -1296,10 +1255,6 @@ function ModelSelector({
1296
1255
  } else {
1297
1256
  const errorData = await response.json().catch(() => null);
1298
1257
  const errorMessage = errorData?.error?.message || errorData?.message || response.statusText;
1299
- console.log(
1300
- `\u{1F6A8} GPT-5 Responses API Error (${response.status}):`,
1301
- errorData
1302
- );
1303
1258
  let details = `Responses API Error: ${errorMessage}`;
1304
1259
  if (response.status === 400 && errorMessage.includes("max_tokens")) {
1305
1260
  details += "\n\u{1F527} Note: This appears to be a parameter compatibility issue. The fallback to Chat Completions should handle this.";
@@ -1327,9 +1282,6 @@ function ModelSelector({
1327
1282
  async function testProviderSpecificEndpoint(baseURL) {
1328
1283
  if (selectedProvider === "anthropic" || selectedProvider === "bigdream") {
1329
1284
  try {
1330
- console.log(
1331
- `[DEBUG] Testing ${selectedProvider} connection using official Anthropic SDK...`
1332
- );
1333
1285
  let testBaseURL = void 0;
1334
1286
  if (selectedProvider === "bigdream") {
1335
1287
  testBaseURL = baseURL || "https://api-key.info";
@@ -1357,7 +1309,6 @@ function ModelSelector({
1357
1309
  };
1358
1310
  }
1359
1311
  } catch (error) {
1360
- console.log(`[DEBUG] ${selectedProvider} connection test error:`, error);
1361
1312
  return {
1362
1313
  success: false,
1363
1314
  message: `\u274C ${selectedProvider} connection failed`,
@@ -1450,10 +1401,15 @@ function ModelSelector({
1450
1401
  onDone();
1451
1402
  }
1452
1403
  } else {
1404
+ const prevScreen = screenStack[screenStack.length - 2];
1405
+ if (prevScreen === "modelParams") {
1406
+ const formFields = getFormFieldsForModelParams();
1407
+ const submitIndex = formFields.findIndex((f) => f.name === "submit");
1408
+ setActiveFieldIndex(Math.max(0, submitIndex - 1));
1409
+ }
1453
1410
  setScreenStack((prev) => prev.slice(0, -1));
1454
1411
  }
1455
1412
  };
1456
- useEscapeNavigation(handleBack, abortController);
1457
1413
  function handleCursorOffsetChange(offset) {
1458
1414
  setCursorOffset(offset);
1459
1415
  }
@@ -1468,7 +1424,13 @@ function ModelSelector({
1468
1424
  function handleModelSearchCursorOffsetChange(offset) {
1469
1425
  setModelSearchCursorOffset(offset);
1470
1426
  }
1427
+ const textScreens = ["apiKey", "baseUrl", "resourceName", "modelInput"];
1471
1428
  useInput((input, key) => {
1429
+ if (!textScreens.includes(currentScreen)) return;
1430
+ if (key.escape) {
1431
+ handleBack();
1432
+ return;
1433
+ }
1472
1434
  if (currentScreen === "apiKey" && key.return) {
1473
1435
  if (apiKey) {
1474
1436
  handleApiKeySubmit(apiKey);
@@ -1480,8 +1442,7 @@ function ModelSelector({
1480
1442
  navigateTo("modelInput");
1481
1443
  return;
1482
1444
  }
1483
- fetchModelsWithRetry().catch((error) => {
1484
- console.error("Final error after retries:", error);
1445
+ fetchModelsWithRetry().catch(() => {
1485
1446
  });
1486
1447
  return;
1487
1448
  }
@@ -1505,76 +1466,6 @@ function ModelSelector({
1505
1466
  }
1506
1467
  return;
1507
1468
  }
1508
- if (currentScreen === "confirmation" && key.return) {
1509
- handleConfirmation().catch((error) => {
1510
- console.error("Error in handleConfirmation:", error);
1511
- setValidationError(
1512
- error instanceof Error ? error.message : "Unexpected error occurred"
1513
- );
1514
- });
1515
- return;
1516
- }
1517
- if (currentScreen === "connectionTest") {
1518
- if (key.return) {
1519
- if (!isTestingConnection && !connectionTestResult) {
1520
- handleConnectionTest();
1521
- } else if (connectionTestResult && connectionTestResult.success) {
1522
- navigateTo("confirmation");
1523
- } else if (connectionTestResult && !connectionTestResult.success) {
1524
- handleConnectionTest();
1525
- }
1526
- return;
1527
- }
1528
- }
1529
- if (currentScreen === "contextLength") {
1530
- if (key.return) {
1531
- handleContextLengthSubmit();
1532
- return;
1533
- }
1534
- if (key.upArrow) {
1535
- const currentIndex = CONTEXT_LENGTH_OPTIONS.findIndex(
1536
- (opt) => opt.value === contextLength
1537
- );
1538
- const newIndex = currentIndex > 0 ? currentIndex - 1 : currentIndex === -1 ? CONTEXT_LENGTH_OPTIONS.findIndex(
1539
- (opt) => opt.value === DEFAULT_CONTEXT_LENGTH
1540
- ) || 0 : CONTEXT_LENGTH_OPTIONS.length - 1;
1541
- setContextLength(CONTEXT_LENGTH_OPTIONS[newIndex].value);
1542
- return;
1543
- }
1544
- if (key.downArrow) {
1545
- const currentIndex = CONTEXT_LENGTH_OPTIONS.findIndex(
1546
- (opt) => opt.value === contextLength
1547
- );
1548
- const newIndex = currentIndex === -1 ? CONTEXT_LENGTH_OPTIONS.findIndex(
1549
- (opt) => opt.value === DEFAULT_CONTEXT_LENGTH
1550
- ) || 0 : (currentIndex + 1) % CONTEXT_LENGTH_OPTIONS.length;
1551
- setContextLength(CONTEXT_LENGTH_OPTIONS[newIndex].value);
1552
- return;
1553
- }
1554
- }
1555
- if (currentScreen === "apiKey" && (key.ctrl && input === "v" || key.meta && input === "v")) {
1556
- setModelLoadError(
1557
- "Please use your terminal's paste functionality or type the API key manually"
1558
- );
1559
- return;
1560
- }
1561
- if (currentScreen === "modelParams" && key.tab) {
1562
- const formFields = getFormFieldsForModelParams();
1563
- setActiveFieldIndex((current) => (current + 1) % formFields.length);
1564
- return;
1565
- }
1566
- if (currentScreen === "modelParams" && key.return) {
1567
- const formFields = getFormFieldsForModelParams();
1568
- const currentField = formFields[activeFieldIndex];
1569
- if (currentField.name === "submit" || activeFieldIndex === formFields.length - 1) {
1570
- handleModelParamsSubmit();
1571
- } else if (currentField.component === "select") {
1572
- setActiveFieldIndex(
1573
- (current) => Math.min(current + 1, formFields.length - 1)
1574
- );
1575
- }
1576
- return;
1577
- }
1578
1469
  });
1579
1470
  function getFormFieldsForModelParams() {
1580
1471
  return [
@@ -1607,433 +1498,460 @@ function ModelSelector({
1607
1498
  ];
1608
1499
  }
1609
1500
  if (currentScreen === "apiKey") {
1610
- const modelTypeText = "this model profile";
1611
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
1612
- Box,
1501
+ const providerLabel = getProviderLabel(selectedProvider, 0).split(" (")[0];
1502
+ const apiKeyUrls = {
1503
+ kimi: "https://platform.moonshot.cn/console/api-keys",
1504
+ deepseek: "https://platform.deepseek.com/api_keys",
1505
+ siliconflow: "https://cloud.siliconflow.cn/i/oJWsm6io",
1506
+ qwen: "https://bailian.console.aliyun.com/?tab=model#/api-key",
1507
+ glm: "https://open.bigmodel.cn (API Keys section)",
1508
+ minimax: "https://www.minimax.io/platform/user-center/basic-information",
1509
+ "baidu-qianfan": "https://console.bce.baidu.com/iam/#/iam/accesslist",
1510
+ openai: "https://platform.openai.com/api-keys"
1511
+ };
1512
+ let apiKeyUrl = apiKeyUrls[selectedProvider] || "";
1513
+ if (selectedProvider === "anthropic") {
1514
+ apiKeyUrl = anthropicProviderType === "official" ? "https://console.anthropic.com/settings/keys" : anthropicProviderType === "bigdream" ? "https://api-key.info/register?aff=MSl4" : anthropicProviderType === "opendev" ? "https://api.openai-next.com/register/?aff_code=4xo7" : "your custom API provider";
1515
+ }
1516
+ const supportsManualInput = selectedProvider === "anthropic" || selectedProvider === "kimi" || selectedProvider === "deepseek" || selectedProvider === "qwen" || selectedProvider === "glm" || selectedProvider === "minimax" || selectedProvider === "baidu-qianfan" || selectedProvider === "siliconflow" || selectedProvider === "custom-openai";
1517
+ const tabHintText = supportsManualInput ? t("modelSelector.apiKeyTabHintManual") : t("modelSelector.apiKeyTabHintSkip");
1518
+ const apiKeyHint = apiKeyUrl ? /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Get your API key from:", " ", /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.info }, apiKeyUrl)) : void 0;
1519
+ return /* @__PURE__ */ React.createElement(
1520
+ BrandTextInput,
1613
1521
  {
1614
- flexDirection: "column",
1615
- gap: 1,
1616
- borderStyle: "round",
1617
- borderColor: theme.secondaryBorder,
1618
- paddingX: 2,
1619
- paddingY: 1
1620
- },
1621
- /* @__PURE__ */ React.createElement(Text, { bold: true }, "API Key Setup", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
1622
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Enter your ", getProviderLabel(selectedProvider, 0).split(" (")[0], " ", "API key for ", modelTypeText, ":"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "This key will be stored locally and used to access the", " ", selectedProvider, " API.", /* @__PURE__ */ React.createElement(Newline, null), "Your key is never sent to our servers.", /* @__PURE__ */ React.createElement(Newline, null), /* @__PURE__ */ React.createElement(Newline, null), selectedProvider === "kimi" && /* @__PURE__ */ React.createElement(React.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "https://platform.moonshot.cn/console/api-keys")), selectedProvider === "deepseek" && /* @__PURE__ */ React.createElement(React.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "https://platform.deepseek.com/api_keys")), selectedProvider === "siliconflow" && /* @__PURE__ */ React.createElement(React.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "https://cloud.siliconflow.cn/i/oJWsm6io")), selectedProvider === "qwen" && /* @__PURE__ */ React.createElement(React.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "https://bailian.console.aliyun.com/?tab=model#/api-key")), selectedProvider === "glm" && /* @__PURE__ */ React.createElement(React.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "https://open.bigmodel.cn (API Keys section)")), selectedProvider === "minimax" && /* @__PURE__ */ React.createElement(React.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "https://www.minimax.io/platform/user-center/basic-information")), selectedProvider === "baidu-qianfan" && /* @__PURE__ */ React.createElement(React.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "https://console.bce.baidu.com/iam/#/iam/accesslist")), selectedProvider === "anthropic" && /* @__PURE__ */ React.createElement(React.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, anthropicProviderType === "official" ? "https://console.anthropic.com/settings/keys" : anthropicProviderType === "bigdream" ? "https://api-key.info/register?aff=MSl4" : anthropicProviderType === "opendev" ? "https://api.openai-next.com/register/?aff_code=4xo7" : "your custom API provider")), selectedProvider === "openai" && /* @__PURE__ */ React.createElement(React.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "https://platform.openai.com/api-keys")))), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
1623
- TextInput,
1624
- {
1625
- placeholder: "sk-...",
1626
- value: apiKey,
1627
- onChange: handleApiKeyChange,
1628
- onSubmit: handleApiKeySubmit,
1629
- mask: "*",
1630
- columns: 500,
1631
- cursorOffset,
1632
- onChangeCursorOffset: handleCursorOffsetChange,
1633
- showCursor: true
1634
- }
1635
- )), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: apiKey ? theme.suggestion : SEMANTIC_COLORS.dim }, "[Submit API Key]"), /* @__PURE__ */ React.createElement(Text, null, " ", "- Press Enter or click to continue with this API key"))), isLoadingModels && /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Loading available models...")), modelLoadError && /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.error }, "Error: ", modelLoadError)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Press ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Enter"), " to continue,", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Tab"), " to", " ", selectedProvider === "anthropic" || selectedProvider === "kimi" || selectedProvider === "deepseek" || selectedProvider === "qwen" || selectedProvider === "glm" || selectedProvider === "minimax" || selectedProvider === "baidu-qianfan" || selectedProvider === "siliconflow" || selectedProvider === "custom-openai" ? "skip to manual model input" : "skip using a key", ", or ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back")))
1636
- ));
1522
+ title: `${t("modelSelector.apiKeyTitle")} \u2014 ${providerLabel}`,
1523
+ description: t("modelSelector.apiKeyDesc"),
1524
+ placeholder: "sk-...",
1525
+ value: apiKey,
1526
+ onChange: handleApiKeyChange,
1527
+ onSubmit: handleApiKeySubmit,
1528
+ mask: "*",
1529
+ error: modelLoadError ? `Error: ${modelLoadError}` : null,
1530
+ isLoading: isLoadingModels,
1531
+ loadingText: "Loading available models...",
1532
+ hint: apiKeyHint,
1533
+ footerHint: `Enter continue \xB7 Tab ${tabHintText} \xB7 Esc back`,
1534
+ cursorOffset,
1535
+ onChangeCursorOffset: handleCursorOffsetChange
1536
+ }
1537
+ );
1637
1538
  }
1638
1539
  if (currentScreen === "model") {
1639
- const modelTypeText = "this model profile";
1640
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
1641
- Box,
1540
+ const providerLabel = getProviderLabel(
1541
+ selectedProvider,
1542
+ availableModels.length
1543
+ ).split(" (")[0];
1544
+ const modelSelectorItems = modelOptions.map((opt) => ({
1545
+ id: opt.value,
1546
+ label: opt.label
1547
+ }));
1548
+ const subtitle = modelOptions.length > 0 ? `${modelOptions.length}/${availableModels.length} models` : availableModels.length > 0 ? t("modelSelector.noModelsMatch") : t("modelSelector.noModelsAvailable");
1549
+ return /* @__PURE__ */ React.createElement(
1550
+ SimpleSelector,
1642
1551
  {
1643
- flexDirection: "column",
1644
- gap: 1,
1645
- borderStyle: "round",
1646
- borderColor: theme.secondaryBorder,
1647
- paddingX: 2,
1648
- paddingY: 1
1649
- },
1650
- /* @__PURE__ */ React.createElement(Text, { bold: true }, "Model Selection", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
1651
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Select a model from", " ", getProviderLabel(
1652
- selectedProvider,
1653
- availableModels.length
1654
- ).split(" (")[0], " ", "for ", modelTypeText, ":"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "This model profile can be assigned to different pointers (main, task, reasoning, quick) for various use cases.")), /* @__PURE__ */ React.createElement(Box, { marginY: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Search models:"), /* @__PURE__ */ React.createElement(
1655
- TextInput,
1656
- {
1657
- placeholder: "Type to filter models...",
1658
- value: modelSearchQuery,
1659
- onChange: handleModelSearchChange,
1660
- columns: 100,
1661
- cursorOffset: modelSearchCursorOffset,
1662
- onChangeCursorOffset: handleModelSearchCursorOffsetChange,
1663
- showCursor: true,
1664
- focus: true
1665
- }
1666
- )), modelOptions.length > 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1667
- Select,
1668
- {
1669
- options: modelOptions,
1670
- onChange: handleModelSelection
1671
- }
1672
- ), /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Showing ", modelOptions.length, " of ", availableModels.length, " ", "models")) : /* @__PURE__ */ React.createElement(Box, null, availableModels.length > 0 ? /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.running }, "No models match your search. Try a different query.") : /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.running }, "No models available for this provider.")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Press ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back to API key input")))
1673
- ));
1552
+ title: `${t("modelSelector.modelSelectionTitle")} \u2014 ${providerLabel}`,
1553
+ subtitle,
1554
+ items: modelSelectorItems,
1555
+ onSelect: (item) => handleModelSelection(item.id),
1556
+ onClose: handleBack
1557
+ }
1558
+ );
1674
1559
  }
1675
1560
  if (currentScreen === "modelParams") {
1676
1561
  const formFields = getFormFieldsForModelParams();
1677
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
1678
- Box,
1679
- {
1680
- flexDirection: "column",
1681
- gap: 1,
1682
- borderStyle: "round",
1683
- borderColor: theme.secondaryBorder,
1684
- paddingX: 2,
1685
- paddingY: 1
1686
- },
1687
- /* @__PURE__ */ React.createElement(Text, { bold: true }, "Model Parameters", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
1688
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Configure parameters for ", selectedModel, ":"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "Use ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Tab"), " to navigate between fields. Press", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Enter"), " to submit.")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, formFields.map((field, index) => /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginY: 1, key: field.name }, field.component !== "button" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1689
- Text,
1690
- {
1691
- bold: true,
1692
- color: activeFieldIndex === index ? theme.success : void 0
1693
- },
1694
- field.label
1695
- ), field.description && /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, field.description)) : /* @__PURE__ */ React.createElement(
1696
- Text,
1697
- {
1698
- bold: true,
1699
- color: activeFieldIndex === index ? theme.success : void 0
1700
- },
1701
- field.label
1702
- ), /* @__PURE__ */ React.createElement(Box, { marginY: 1 }, activeFieldIndex === index ? field.component === "select" ? field.name === "maxTokens" ? /* @__PURE__ */ React.createElement(
1703
- Select,
1562
+ const currentField = formFields[activeFieldIndex];
1563
+ if (currentField?.name === "maxTokens") {
1564
+ const maxTokensItems = MAX_TOKENS_OPTIONS.map((opt) => ({
1565
+ id: opt.value.toString(),
1566
+ label: opt.label,
1567
+ isCurrent: opt.value === parseInt(maxTokens)
1568
+ }));
1569
+ return /* @__PURE__ */ React.createElement(
1570
+ SimpleSelector,
1704
1571
  {
1705
- options: field.options || [],
1706
- onChange: (value) => {
1707
- const numValue = parseInt(value);
1572
+ title: `${t("modelSelector.modelParamsTitle")} \u2014 ${t("modelSelector.maxTokens")}`,
1573
+ subtitle: selectedModel,
1574
+ items: maxTokensItems,
1575
+ onSelect: (item) => {
1576
+ const numValue = parseInt(item.id);
1708
1577
  setMaxTokens(numValue.toString());
1709
1578
  setSelectedMaxTokensPreset(numValue);
1710
- setMaxTokensCursorOffset(
1711
- numValue.toString().length
1712
- );
1713
- setTimeout(() => {
1714
- setActiveFieldIndex(index + 1);
1715
- }, 100);
1579
+ setMaxTokensCursorOffset(numValue.toString().length);
1580
+ setActiveFieldIndex(activeFieldIndex + 1);
1716
1581
  },
1717
- defaultValue: field.defaultValue
1582
+ onClose: handleBack
1718
1583
  }
1719
- ) : /* @__PURE__ */ React.createElement(
1720
- Select,
1584
+ );
1585
+ }
1586
+ if (currentField?.name === "reasoningEffort") {
1587
+ const effortItems = reasoningEffortOptions.map((opt) => ({
1588
+ id: opt.value,
1589
+ label: opt.label,
1590
+ isCurrent: opt.value === reasoningEffort
1591
+ }));
1592
+ return /* @__PURE__ */ React.createElement(
1593
+ SimpleSelector,
1721
1594
  {
1722
- options: reasoningEffortOptions,
1723
- onChange: (value) => {
1724
- setReasoningEffort(value);
1725
- setTimeout(() => {
1726
- setActiveFieldIndex(index + 1);
1727
- }, 100);
1595
+ title: `${t("modelSelector.modelParamsTitle")} \u2014 ${t("modelSelector.reasoningEffort")}`,
1596
+ subtitle: selectedModel,
1597
+ items: effortItems,
1598
+ onSelect: (item) => {
1599
+ setReasoningEffort(item.id);
1600
+ setActiveFieldIndex(activeFieldIndex + 1);
1728
1601
  },
1729
- defaultValue: reasoningEffort
1602
+ onClose: () => {
1603
+ setActiveFieldIndex(0);
1604
+ }
1730
1605
  }
1731
- ) : null : field.name === "maxTokens" ? /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "Current:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, MAX_TOKENS_OPTIONS.find(
1732
- (opt) => opt.value === parseInt(maxTokens)
1733
- )?.label || `${maxTokens} tokens`)) : field.name === "reasoningEffort" ? /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "Current:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, reasoningEffort)) : null))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Press ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Tab"), " to navigate,", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Enter"), " to continue, or", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back"))))
1734
- ));
1606
+ );
1607
+ }
1608
+ handleModelParamsSubmit();
1609
+ return null;
1735
1610
  }
1736
1611
  if (currentScreen === "resourceName") {
1737
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
1738
- Box,
1612
+ return /* @__PURE__ */ React.createElement(
1613
+ BrandTextInput,
1739
1614
  {
1740
- flexDirection: "column",
1741
- gap: 1,
1742
- borderStyle: "round",
1743
- borderColor: theme.secondaryBorder,
1744
- paddingX: 2,
1745
- paddingY: 1
1746
- },
1747
- /* @__PURE__ */ React.createElement(Text, { bold: true }, "Azure Resource Setup", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
1748
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Enter your Azure OpenAI resource name:"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "This is the name of your Azure OpenAI resource (without the full domain).", /* @__PURE__ */ React.createElement(Newline, null), 'For example, if your endpoint is "https://myresource.openai.azure.com", enter "myresource".')), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
1749
- TextInput,
1750
- {
1751
- placeholder: "myazureresource",
1752
- value: resourceName,
1753
- onChange: setResourceName,
1754
- onSubmit: handleResourceNameSubmit,
1755
- columns: 100,
1756
- cursorOffset: resourceNameCursorOffset,
1757
- onChangeCursorOffset: setResourceNameCursorOffset,
1758
- showCursor: true
1759
- }
1760
- )), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(
1761
- Text,
1762
- {
1763
- color: resourceName ? theme.suggestion : SEMANTIC_COLORS.dim
1764
- },
1765
- "[Submit Resource Name]"
1766
- ), /* @__PURE__ */ React.createElement(Text, null, " - Press Enter or click to continue"))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Press ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Enter"), " to continue or", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back")))
1767
- ));
1615
+ title: t("modelSelector.resourceNameTitle"),
1616
+ description: t("modelSelector.resourceNameDesc"),
1617
+ placeholder: "myazureresource",
1618
+ value: resourceName,
1619
+ onChange: setResourceName,
1620
+ onSubmit: handleResourceNameSubmit,
1621
+ footerHint: t("modelSelector.footerEnterContinue"),
1622
+ cursorOffset: resourceNameCursorOffset,
1623
+ onChangeCursorOffset: setResourceNameCursorOffset
1624
+ }
1625
+ );
1768
1626
  }
1769
1627
  if (currentScreen === "baseUrl") {
1770
1628
  const isCustomOpenAI = selectedProvider === "custom-openai";
1771
1629
  if (isCustomOpenAI) {
1772
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
1773
- Box,
1630
+ return /* @__PURE__ */ React.createElement(
1631
+ BrandTextInput,
1774
1632
  {
1775
- flexDirection: "column",
1776
- gap: 1,
1777
- borderStyle: "round",
1778
- borderColor: theme.secondaryBorder,
1779
- paddingX: 2,
1780
- paddingY: 1
1781
- },
1782
- /* @__PURE__ */ React.createElement(Text, { bold: true }, "Custom API Server Setup", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
1783
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Enter your custom API URL:"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "This is the base URL for your OpenAI-compatible API.", /* @__PURE__ */ React.createElement(Newline, null), "For example: https://api.example.com/v1")), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
1784
- TextInput,
1785
- {
1786
- placeholder: "https://api.example.com/v1",
1787
- value: customBaseUrl,
1788
- onChange: setCustomBaseUrl,
1789
- onSubmit: handleCustomBaseUrlSubmit,
1790
- columns: 100,
1791
- cursorOffset: customBaseUrlCursorOffset,
1792
- onChangeCursorOffset: setCustomBaseUrlCursorOffset,
1793
- showCursor: !isLoadingModels,
1794
- focus: !isLoadingModels
1795
- }
1796
- )), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(
1797
- Text,
1798
- {
1799
- color: isLoadingModels ? theme.secondaryText : theme.suggestion
1800
- },
1801
- "[Submit Base URL]"
1802
- ), /* @__PURE__ */ React.createElement(Text, null, " - Press Enter or click to continue"))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Press ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Enter"), " to continue or ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back")))
1803
- ));
1633
+ title: t("modelSelector.customApiTitle"),
1634
+ description: t("modelSelector.customApiDesc"),
1635
+ placeholder: "https://api.example.com/v1",
1636
+ value: customBaseUrl,
1637
+ onChange: setCustomBaseUrl,
1638
+ onSubmit: handleCustomBaseUrlSubmit,
1639
+ isLoading: isLoadingModels,
1640
+ loadingText: "Connecting...",
1641
+ error: modelLoadError ? `Error: ${modelLoadError}` : null,
1642
+ footerHint: t("modelSelector.footerEnterContinue"),
1643
+ cursorOffset: customBaseUrlCursorOffset,
1644
+ onChangeCursorOffset: setCustomBaseUrlCursorOffset
1645
+ }
1646
+ );
1804
1647
  }
1805
1648
  const providerName = providers[selectedProvider]?.name || selectedProvider;
1806
1649
  const defaultUrl = providers[selectedProvider]?.baseURL || "";
1807
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
1808
- Box,
1650
+ const desc = selectedProvider === "ollama" ? t("modelSelector.baseUrlOllamaDesc") : `${t("modelSelector.baseUrlDesc")} You can modify this URL or press Enter to use the default.`;
1651
+ return /* @__PURE__ */ React.createElement(
1652
+ BrandTextInput,
1809
1653
  {
1810
- flexDirection: "column",
1811
- gap: 1,
1812
- borderStyle: "round",
1813
- borderColor: theme.secondaryBorder,
1814
- paddingX: 2,
1815
- paddingY: 1
1816
- },
1817
- /* @__PURE__ */ React.createElement(Text, { bold: true }, providerName, " API Configuration", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
1818
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Configure the API endpoint for ", providerName, ":"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, selectedProvider === "ollama" ? /* @__PURE__ */ React.createElement(React.Fragment, null, "This is the URL of your Ollama server.", /* @__PURE__ */ React.createElement(Newline, null), "Default is http://localhost:11434/v1 for local Ollama installations.") : /* @__PURE__ */ React.createElement(React.Fragment, null, "This is the base URL for the ", providerName, " API.", /* @__PURE__ */ React.createElement(Newline, null), "You can modify this URL or press Enter to use the default."))), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
1819
- TextInput,
1820
- {
1821
- placeholder: defaultUrl,
1822
- value: providerBaseUrl,
1823
- onChange: setProviderBaseUrl,
1824
- onSubmit: handleProviderBaseUrlSubmit,
1825
- columns: 100,
1826
- cursorOffset: providerBaseUrlCursorOffset,
1827
- onChangeCursorOffset: setProviderBaseUrlCursorOffset,
1828
- showCursor: !isLoadingModels,
1829
- focus: !isLoadingModels
1830
- }
1831
- )), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(
1832
- Text,
1833
- {
1834
- color: isLoadingModels ? theme.secondaryText : theme.suggestion
1835
- },
1836
- "[Submit Base URL]"
1837
- ), /* @__PURE__ */ React.createElement(Text, null, " - Press Enter or click to continue"))), isLoadingModels && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: theme.success }, selectedProvider === "ollama" ? "Connecting to Ollama server..." : `Connecting to ${providerName}...`)), modelLoadError && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.error }, "Error: ", modelLoadError)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Press ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Enter"), " to continue or", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back")))
1838
- ));
1654
+ title: `${t("modelSelector.baseUrlTitle")} \u2014 ${providerName}`,
1655
+ description: desc,
1656
+ placeholder: defaultUrl,
1657
+ value: providerBaseUrl,
1658
+ onChange: setProviderBaseUrl,
1659
+ onSubmit: handleProviderBaseUrlSubmit,
1660
+ isLoading: isLoadingModels,
1661
+ loadingText: selectedProvider === "ollama" ? "Connecting to Ollama server..." : `Connecting to ${providerName}...`,
1662
+ error: modelLoadError ? `Error: ${modelLoadError}` : null,
1663
+ footerHint: t("modelSelector.footerEnterContinue"),
1664
+ cursorOffset: providerBaseUrlCursorOffset,
1665
+ onChangeCursorOffset: setProviderBaseUrlCursorOffset
1666
+ }
1667
+ );
1839
1668
  }
1840
1669
  if (currentScreen === "modelInput") {
1841
- const modelTypeText = "this model profile";
1842
- let screenTitle = "Manual Model Setup";
1843
- let description = "Enter the model name manually";
1844
- let placeholder = "gpt-4";
1845
- let examples = 'For example: "gpt-4", "gpt-3.5-turbo", etc.';
1846
- if (selectedProvider === "azure") {
1847
- screenTitle = "Azure Model Setup";
1848
- description = `Enter your Azure OpenAI deployment name for ${modelTypeText}:`;
1849
- examples = 'For example: "gpt-4", "gpt-35-turbo", etc.';
1850
- placeholder = "gpt-4";
1851
- } else if (selectedProvider === "anthropic") {
1852
- screenTitle = "Claude Model Setup";
1853
- description = `Enter the Claude model name for ${modelTypeText}:`;
1854
- examples = 'For example: "claude-3-5-sonnet-latest", "claude-3-5-haiku-latest", etc.';
1855
- placeholder = "claude-3-5-sonnet-latest";
1856
- } else if (selectedProvider === "bigdream") {
1857
- screenTitle = "BigDream Model Setup";
1858
- description = `Enter the BigDream model name for ${modelTypeText}:`;
1859
- examples = 'For example: "claude-3-5-sonnet-latest", "claude-3-5-haiku-latest", etc.';
1860
- placeholder = "claude-3-5-sonnet-latest";
1861
- } else if (selectedProvider === "kimi") {
1862
- screenTitle = "Kimi Model Setup";
1863
- description = `Enter the Kimi model name for ${modelTypeText}:`;
1864
- examples = 'For example: "kimi-k2-0711-preview"';
1865
- placeholder = "kimi-k2-0711-preview";
1866
- } else if (selectedProvider === "deepseek") {
1867
- screenTitle = "DeepSeek Model Setup";
1868
- description = `Enter the DeepSeek model name for ${modelTypeText}:`;
1869
- examples = 'For example: "deepseek-chat", "deepseek-coder", "deepseek-reasoner", etc.';
1870
- placeholder = "deepseek-chat";
1871
- } else if (selectedProvider === "siliconflow") {
1872
- screenTitle = "SiliconFlow Model Setup";
1873
- description = `Enter the SiliconFlow model name for ${modelTypeText}:`;
1874
- examples = 'For example: "Qwen/Qwen2.5-72B-Instruct", "meta-llama/Meta-Llama-3.1-8B-Instruct", etc.';
1875
- placeholder = "Qwen/Qwen2.5-72B-Instruct";
1876
- } else if (selectedProvider === "qwen") {
1877
- screenTitle = "Qwen Model Setup";
1878
- description = `Enter the Qwen model name for ${modelTypeText}:`;
1879
- examples = 'For example: "qwen-plus", "qwen-turbo", "qwen-max", etc.';
1880
- placeholder = "qwen-plus";
1881
- } else if (selectedProvider === "glm") {
1882
- screenTitle = "GLM Model Setup";
1883
- description = `Enter the GLM model name for ${modelTypeText}:`;
1884
- examples = 'For example: "glm-4", "glm-4v", "glm-3-turbo", etc.';
1885
- placeholder = "glm-4";
1886
- } else if (selectedProvider === "minimax") {
1887
- screenTitle = "MiniMax Model Setup";
1888
- description = `Enter the MiniMax model name for ${modelTypeText}:`;
1889
- examples = 'For example: "abab6.5s-chat", "abab6.5g-chat", "abab5.5s-chat", etc.';
1890
- placeholder = "abab6.5s-chat";
1891
- } else if (selectedProvider === "baidu-qianfan") {
1892
- screenTitle = "Baidu Qianfan Model Setup";
1893
- description = `Enter the Baidu Qianfan model name for ${modelTypeText}:`;
1894
- examples = 'For example: "ERNIE-4.0-8K", "ERNIE-3.5-8K", "ERNIE-Speed-128K", etc.';
1895
- placeholder = "ERNIE-4.0-8K";
1896
- } else if (selectedProvider === "custom-openai") {
1897
- screenTitle = "Custom API Model Setup";
1898
- description = `Enter the model name for ${modelTypeText}:`;
1899
- examples = "Enter the exact model name as supported by your API endpoint.";
1900
- placeholder = "model-name";
1901
- }
1902
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
1903
- Box,
1904
- {
1905
- flexDirection: "column",
1906
- gap: 1,
1907
- borderStyle: "round",
1908
- borderColor: theme.secondaryBorder,
1909
- paddingX: 2,
1910
- paddingY: 1
1670
+ const modelInputConfig = {
1671
+ azure: {
1672
+ title: "Azure Model Setup",
1673
+ placeholder: "gpt-4",
1674
+ examples: 'e.g. "gpt-4", "gpt-35-turbo"'
1911
1675
  },
1912
- /* @__PURE__ */ React.createElement(Text, { bold: true }, screenTitle, " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
1913
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, description), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, selectedProvider === "azure" ? "This is the deployment name you configured in your Azure OpenAI resource." : selectedProvider === "anthropic" ? "This should be a valid Claude model identifier from Claude." : selectedProvider === "bigdream" ? "This should be a valid Claude model identifier supported by BigDream." : selectedProvider === "kimi" ? "This should be a valid Kimi model identifier from Moonshot AI." : selectedProvider === "deepseek" ? "This should be a valid DeepSeek model identifier." : selectedProvider === "siliconflow" ? "This should be a valid SiliconFlow model identifier." : selectedProvider === "qwen" ? "This should be a valid Qwen model identifier from Alibaba Cloud." : selectedProvider === "glm" ? "This should be a valid GLM model identifier from Zhipu AI." : selectedProvider === "minimax" ? "This should be a valid MiniMax model identifier." : selectedProvider === "baidu-qianfan" ? "This should be a valid Baidu Qianfan model identifier." : "This should match the model name supported by your API endpoint.", /* @__PURE__ */ React.createElement(Newline, null), examples)), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
1914
- TextInput,
1915
- {
1916
- placeholder,
1917
- value: customModelName,
1918
- onChange: setCustomModelName,
1919
- onSubmit: handleCustomModelSubmit,
1920
- columns: 100,
1921
- cursorOffset: customModelNameCursorOffset,
1922
- onChangeCursorOffset: setCustomModelNameCursorOffset,
1923
- showCursor: true
1924
- }
1925
- )), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(
1926
- Text,
1927
- {
1928
- color: customModelName ? theme.suggestion : SEMANTIC_COLORS.dim
1929
- },
1930
- "[Submit Model Name]"
1931
- ), /* @__PURE__ */ React.createElement(Text, null, " - Press Enter or click to continue"))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Press ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Enter"), " to continue or", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back")))
1932
- ));
1676
+ anthropic: {
1677
+ title: "Claude Model Setup",
1678
+ placeholder: "claude-3-5-sonnet-latest",
1679
+ examples: 'e.g. "claude-3-5-sonnet-latest", "claude-3-5-haiku-latest"'
1680
+ },
1681
+ bigdream: {
1682
+ title: "BigDream Model Setup",
1683
+ placeholder: "claude-3-5-sonnet-latest",
1684
+ examples: 'e.g. "claude-3-5-sonnet-latest", "claude-3-5-haiku-latest"'
1685
+ },
1686
+ kimi: {
1687
+ title: "Kimi Model Setup",
1688
+ placeholder: "kimi-k2-0711-preview",
1689
+ examples: 'e.g. "kimi-k2-0711-preview"'
1690
+ },
1691
+ deepseek: {
1692
+ title: "DeepSeek Model Setup",
1693
+ placeholder: "deepseek-chat",
1694
+ examples: 'e.g. "deepseek-chat", "deepseek-coder", "deepseek-reasoner"'
1695
+ },
1696
+ siliconflow: {
1697
+ title: "SiliconFlow Model Setup",
1698
+ placeholder: "Qwen/Qwen2.5-72B-Instruct",
1699
+ examples: 'e.g. "Qwen/Qwen2.5-72B-Instruct"'
1700
+ },
1701
+ qwen: {
1702
+ title: "Qwen Model Setup",
1703
+ placeholder: "qwen-plus",
1704
+ examples: 'e.g. "qwen-plus", "qwen-turbo", "qwen-max"'
1705
+ },
1706
+ glm: {
1707
+ title: "GLM Model Setup",
1708
+ placeholder: "glm-4",
1709
+ examples: 'e.g. "glm-4", "glm-4v", "glm-3-turbo"'
1710
+ },
1711
+ minimax: {
1712
+ title: "MiniMax Model Setup",
1713
+ placeholder: "abab6.5s-chat",
1714
+ examples: 'e.g. "abab6.5s-chat", "abab6.5g-chat"'
1715
+ },
1716
+ "baidu-qianfan": {
1717
+ title: "Baidu Qianfan Model Setup",
1718
+ placeholder: "ERNIE-4.0-8K",
1719
+ examples: 'e.g. "ERNIE-4.0-8K", "ERNIE-3.5-8K"'
1720
+ },
1721
+ "custom-openai": {
1722
+ title: "Custom API Model Setup",
1723
+ placeholder: "model-name",
1724
+ examples: "Enter the exact model name supported by your API endpoint."
1725
+ }
1726
+ };
1727
+ const cfg = modelInputConfig[selectedProvider] || {
1728
+ title: t("modelSelector.modelInputTitle"),
1729
+ placeholder: "gpt-4",
1730
+ examples: 'e.g. "gpt-4", "gpt-3.5-turbo"'
1731
+ };
1732
+ return /* @__PURE__ */ React.createElement(
1733
+ BrandTextInput,
1734
+ {
1735
+ title: cfg.title,
1736
+ description: cfg.examples,
1737
+ placeholder: cfg.placeholder,
1738
+ value: customModelName,
1739
+ onChange: setCustomModelName,
1740
+ onSubmit: handleCustomModelSubmit,
1741
+ footerHint: t("modelSelector.footerEnterContinue"),
1742
+ cursorOffset: customModelNameCursorOffset,
1743
+ onChangeCursorOffset: setCustomModelNameCursorOffset
1744
+ }
1745
+ );
1933
1746
  }
1934
1747
  if (currentScreen === "contextLength") {
1935
- const selectedOption = CONTEXT_LENGTH_OPTIONS.find((opt) => opt.value === contextLength) || CONTEXT_LENGTH_OPTIONS[2];
1936
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
1937
- Box,
1748
+ const contextItems = CONTEXT_LENGTH_OPTIONS.map((opt) => ({
1749
+ id: opt.value.toString(),
1750
+ label: opt.value === DEFAULT_CONTEXT_LENGTH ? `${opt.label} (${t("modelSelector.recommended")})` : opt.label,
1751
+ isCurrent: opt.value === contextLength
1752
+ }));
1753
+ return /* @__PURE__ */ React.createElement(
1754
+ SimpleSelector,
1938
1755
  {
1939
- flexDirection: "column",
1940
- gap: 1,
1941
- borderStyle: "round",
1942
- borderColor: theme.secondaryBorder,
1943
- paddingX: 2,
1944
- paddingY: 1
1945
- },
1946
- /* @__PURE__ */ React.createElement(Text, { bold: true }, "Context Length Configuration", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
1947
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Choose the context window length for your model:"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "This determines how much conversation history and context the model can process at once. Higher values allow for longer conversations but may increase costs.")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginY: 1 }, CONTEXT_LENGTH_OPTIONS.map((option, index) => {
1948
- const isSelected = option.value === contextLength;
1949
- return /* @__PURE__ */ React.createElement(Box, { key: option.value, flexDirection: "row" }, /* @__PURE__ */ React.createElement(Text, { color: isSelected ? "blue" : void 0 }, isSelected ? "\u2192 " : " ", option.label, option.value === DEFAULT_CONTEXT_LENGTH ? " (recommended)" : ""));
1950
- })), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Selected:", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, selectedOption.label))))
1951
- ), /* @__PURE__ */ React.createElement(Box, { marginLeft: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "\u2191/\u2193 to select \xB7 Enter to continue \xB7 Esc to go back")));
1756
+ title: t("modelSelector.contextLengthTitle"),
1757
+ subtitle: t("modelSelector.contextLengthDesc"),
1758
+ items: contextItems,
1759
+ onSelect: (item) => {
1760
+ setContextLength(parseInt(item.id));
1761
+ handleContextLengthSubmit();
1762
+ },
1763
+ onClose: handleBack
1764
+ }
1765
+ );
1952
1766
  }
1953
1767
  if (currentScreen === "connectionTest") {
1954
1768
  const providerDisplayName = getProviderLabel(selectedProvider, 0).split(
1955
1769
  " ("
1956
1770
  )[0];
1957
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
1958
- Box,
1771
+ const testSections = [
1959
1772
  {
1960
- flexDirection: "column",
1961
- gap: 1,
1962
- borderStyle: "round",
1963
- borderColor: theme.secondaryBorder,
1964
- paddingX: 2,
1965
- paddingY: 1
1966
- },
1967
- /* @__PURE__ */ React.createElement(Text, { bold: true }, "Connection Test", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
1968
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Testing connection to ", providerDisplayName, "..."), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "This will verify your configuration by sending a test request to the API.", selectedProvider === "minimax" && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Newline, null), "For MiniMax, we'll test both v2 and v1 endpoints to find the best one."))), !connectionTestResult && !isTestingConnection && /* @__PURE__ */ React.createElement(Box, { marginY: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Press Enter"), " to start the connection test")), isTestingConnection && /* @__PURE__ */ React.createElement(Box, { marginY: 1 }, /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "\u{1F504} Testing connection...")), connectionTestResult && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginY: 1, paddingX: 1 }, /* @__PURE__ */ React.createElement(
1969
- Text,
1970
- {
1971
- color: connectionTestResult.success ? theme.success : "red"
1972
- },
1973
- connectionTestResult.message
1974
- ), connectionTestResult.endpoint && /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "Endpoint: ", connectionTestResult.endpoint), connectionTestResult.details && /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "Details: ", connectionTestResult.details), connectionTestResult.success ? /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: theme.success }, "\u2705 Automatically proceeding to confirmation...")) : /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Press Enter"), " to retry test, or ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back"))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Press ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back to context length")))
1975
- ));
1773
+ title: t("modelSelector.connectionTestDesc"),
1774
+ items: [
1775
+ { label: "Provider", value: providerDisplayName },
1776
+ { label: "Model", value: selectedModel },
1777
+ ...connectionTestResult ? [
1778
+ {
1779
+ label: "Status",
1780
+ value: connectionTestResult.message,
1781
+ valueColor: connectionTestResult.success ? SEMANTIC_COLORS.success : SEMANTIC_COLORS.error
1782
+ },
1783
+ ...connectionTestResult.endpoint ? [
1784
+ {
1785
+ label: "Endpoint",
1786
+ value: connectionTestResult.endpoint
1787
+ }
1788
+ ] : [],
1789
+ ...connectionTestResult.details ? [
1790
+ {
1791
+ label: "Details",
1792
+ value: connectionTestResult.details
1793
+ }
1794
+ ] : []
1795
+ ] : []
1796
+ ]
1797
+ }
1798
+ ];
1799
+ const testActions = [];
1800
+ if (!isTestingConnection && !connectionTestResult) {
1801
+ testActions.push({
1802
+ key: "return",
1803
+ keyLabel: "Enter",
1804
+ description: t("modelSelector.connectionTestStart"),
1805
+ onPress: () => handleConnectionTest()
1806
+ });
1807
+ } else if (connectionTestResult && connectionTestResult.success) {
1808
+ testActions.push({
1809
+ key: "return",
1810
+ keyLabel: "Enter",
1811
+ description: t("modelSelector.continue"),
1812
+ onPress: () => navigateTo("confirmation")
1813
+ });
1814
+ } else if (connectionTestResult && !connectionTestResult.success) {
1815
+ testActions.push({
1816
+ key: "return",
1817
+ keyLabel: "Enter",
1818
+ description: t("modelSelector.connectionTestRetry"),
1819
+ onPress: () => handleConnectionTest()
1820
+ });
1821
+ }
1822
+ const testOverlay = isTestingConnection ? {
1823
+ type: "loading",
1824
+ message: t("modelSelector.connectionTestRunning")
1825
+ } : connectionTestResult?.success ? {
1826
+ type: "success",
1827
+ message: t("modelSelector.connectionTestAutoProceeding")
1828
+ } : null;
1829
+ return /* @__PURE__ */ React.createElement(
1830
+ InfoPanel,
1831
+ {
1832
+ title: `${t("modelSelector.connectionTestTitle")} \u2014 ${providerDisplayName}`,
1833
+ sections: testSections,
1834
+ actions: testActions,
1835
+ onClose: handleBack,
1836
+ statusOverlay: testOverlay
1837
+ }
1838
+ );
1976
1839
  }
1977
1840
  if (currentScreen === "confirmation") {
1978
1841
  const providerDisplayName = getProviderLabel(selectedProvider, 0).split(
1979
1842
  " ("
1980
1843
  )[0];
1981
1844
  const showsApiKey = selectedProvider !== "ollama";
1982
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
1983
- Box,
1845
+ const configItems = [
1846
+ { label: "Provider", value: providerDisplayName },
1847
+ ...selectedProvider === "azure" ? [{ label: "Resource Name", value: resourceName }] : [],
1848
+ ...selectedProvider === "ollama" ? [{ label: "Server URL", value: ollamaBaseUrl }] : [],
1849
+ ...selectedProvider === "custom-openai" ? [{ label: "API Base URL", value: customBaseUrl }] : [],
1850
+ { label: "Model", value: selectedModel },
1851
+ ...apiKey && showsApiKey ? [{ label: "API Key", value: `****${apiKey.slice(-4)}` }] : [],
1852
+ ...maxTokens ? [{ label: "Max Tokens", value: maxTokens }] : [],
1984
1853
  {
1985
- flexDirection: "column",
1986
- gap: 1,
1987
- borderStyle: "round",
1988
- borderColor: theme.secondaryBorder,
1989
- paddingX: 2,
1990
- paddingY: 1
1854
+ label: "Context Length",
1855
+ value: CONTEXT_LENGTH_OPTIONS.find((opt) => opt.value === contextLength)?.label || `${contextLength.toLocaleString()} tokens`
1991
1856
  },
1992
- /* @__PURE__ */ React.createElement(Text, { bold: true }, "Configuration Confirmation", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
1993
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Confirm your model configuration:"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "Please review your selections before saving.")), validationError && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginY: 1, paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: theme.error, bold: true }, "\u26A0 Configuration Error:"), /* @__PURE__ */ React.createElement(Text, { color: theme.error }, validationError)), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginY: 1, paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Provider: "), /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, providerDisplayName)), selectedProvider === "azure" && /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Resource Name: "), /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, resourceName)), selectedProvider === "ollama" && /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Server URL: "), /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, ollamaBaseUrl)), selectedProvider === "custom-openai" && /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "API Base URL: "), /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, customBaseUrl)), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Model: "), /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, selectedModel)), apiKey && showsApiKey && /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "API Key: "), /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "****", apiKey.slice(-4))), maxTokens && /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Max Tokens: "), /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, maxTokens)), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Context Length: "), /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, CONTEXT_LENGTH_OPTIONS.find(
1994
- (opt) => opt.value === contextLength
1995
- )?.label || `${contextLength.toLocaleString()} tokens`)), supportsReasoningEffort && /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Reasoning Effort: "), /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, reasoningEffort))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Press ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back to model parameters or ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Enter"), " ", "to save configuration")))
1996
- ));
1857
+ ...supportsReasoningEffort ? [{ label: "Reasoning Effort", value: reasoningEffort }] : []
1858
+ ];
1859
+ const confirmSections = [
1860
+ {
1861
+ title: t("modelSelector.confirmationDesc"),
1862
+ items: configItems
1863
+ }
1864
+ ];
1865
+ if (validationError) {
1866
+ confirmSections.unshift({
1867
+ title: t("modelSelector.configError"),
1868
+ items: [
1869
+ {
1870
+ label: "Error",
1871
+ value: validationError,
1872
+ valueColor: SEMANTIC_COLORS.error
1873
+ }
1874
+ ]
1875
+ });
1876
+ }
1877
+ const confirmActions = [
1878
+ {
1879
+ key: "return",
1880
+ keyLabel: "Enter",
1881
+ description: t("modelSelector.confirmationSave"),
1882
+ onPress: () => {
1883
+ handleConfirmation().catch((error) => {
1884
+ setValidationError(
1885
+ error instanceof Error ? error.message : "Unexpected error occurred"
1886
+ );
1887
+ });
1888
+ }
1889
+ }
1890
+ ];
1891
+ return /* @__PURE__ */ React.createElement(
1892
+ InfoPanel,
1893
+ {
1894
+ title: t("modelSelector.confirmationTitle"),
1895
+ sections: confirmSections,
1896
+ actions: confirmActions,
1897
+ onClose: handleBack
1898
+ }
1899
+ );
1997
1900
  }
1998
1901
  if (currentScreen === "anthropicSubMenu") {
1999
- const anthropicOptions = [
2000
- { label: "Official Anthropic API", value: "official" },
2001
- { label: "BigDream (Community Proxy)", value: "bigdream" },
2002
- { label: "OpenDev (Community Proxy)", value: "opendev" },
2003
- { label: "Custom Anthropic-Compatible API", value: "custom" }
2004
- ];
2005
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(
2006
- Box,
1902
+ const anthropicItems = [
2007
1903
  {
2008
- flexDirection: "column",
2009
- gap: 1,
2010
- borderStyle: "round",
2011
- borderColor: theme.secondaryBorder,
2012
- paddingX: 2,
2013
- paddingY: 1
1904
+ id: "official",
1905
+ label: t("modelSelector.anthropicOfficial"),
1906
+ description: t("modelSelector.anthropicOfficialDesc")
2014
1907
  },
2015
- /* @__PURE__ */ React.createElement(Text, { bold: true }, "Claude Provider Selection", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
2016
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Choose your Anthropic API access method for this model profile:"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "\u2022 ", /* @__PURE__ */ React.createElement(Text, { bold: true }, "Official Anthropic API:"), " Direct access to Anthropic's official API", /* @__PURE__ */ React.createElement(Newline, null), "\u2022 ", /* @__PURE__ */ React.createElement(Text, { bold: true }, "BigDream:"), " Community proxy providing Claude access", /* @__PURE__ */ React.createElement(Newline, null), "\u2022 ", /* @__PURE__ */ React.createElement(Text, { bold: true }, "Custom:"), " Your own Anthropic-compatible API endpoint")), /* @__PURE__ */ React.createElement(
2017
- Select,
2018
- {
2019
- options: anthropicOptions,
2020
- onChange: handleAnthropicProviderSelection
2021
- }
2022
- ), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Press ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "Esc"), " to go back to provider selection")))
2023
- ));
1908
+ {
1909
+ id: "bigdream",
1910
+ label: t("modelSelector.anthropicBigdream"),
1911
+ description: t("modelSelector.anthropicBigdreamDesc")
1912
+ },
1913
+ {
1914
+ id: "opendev",
1915
+ label: t("modelSelector.anthropicOpendev"),
1916
+ description: t("modelSelector.anthropicOpendevDesc")
1917
+ },
1918
+ {
1919
+ id: "custom",
1920
+ label: t("modelSelector.anthropicCustom"),
1921
+ description: t("modelSelector.anthropicCustomDesc")
1922
+ }
1923
+ ];
1924
+ return /* @__PURE__ */ React.createElement(
1925
+ SimpleSelector,
1926
+ {
1927
+ title: t("modelSelector.anthropicSubMenu"),
1928
+ subtitle: t("modelSelector.anthropicSubMenuDesc"),
1929
+ items: anthropicItems,
1930
+ onSelect: (item) => handleAnthropicProviderSelection(
1931
+ item.id
1932
+ ),
1933
+ onClose: handleBack
1934
+ }
1935
+ );
2024
1936
  }
1937
+ const providerSelectorItems = providerOptions.map((opt) => ({
1938
+ id: opt.value,
1939
+ label: opt.label
1940
+ }));
2025
1941
  return /* @__PURE__ */ React.createElement(
2026
- ScreenContainer,
1942
+ SimpleSelector,
2027
1943
  {
2028
- title: "Provider Selection",
2029
- exitState,
2030
- children: /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Select your preferred AI provider for this model profile:"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "Choose the provider you want to use for this model profile.", /* @__PURE__ */ React.createElement(Newline, null), "This will determine which models are available to you.")), /* @__PURE__ */ React.createElement(
2031
- Select,
2032
- {
2033
- options: providerOptions,
2034
- onChange: handleProviderSelection
1944
+ title: t("modelSelector.providerSelection"),
1945
+ subtitle: t("modelSelector.providerSelectionDesc"),
1946
+ items: providerSelectorItems,
1947
+ onSelect: (item) => handleProviderSelection(item.id),
1948
+ onClose: () => {
1949
+ if (onCancel) {
1950
+ onCancel();
1951
+ } else {
1952
+ onDone();
2035
1953
  }
2036
- ), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "You can change this later by running", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.suggestion }, "/model"), " again")))
1954
+ }
2037
1955
  }
2038
1956
  );
2039
1957
  }