bingocode 1.1.67 → 1.1.69

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bingocode",
3
- "version": "1.1.67",
3
+ "version": "1.1.69",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude": "bin/claude-win.cjs",
@@ -374,21 +374,45 @@ export const ProviderPanel: React.FC<{
374
374
  : `${pr.label || pr.name || pr.id}`,
375
375
  value: pr.id
376
376
  }));
377
+
378
+ // Add Custom option at the beginning
379
+ const finalItems = [
380
+ { label: 'Custom (OpenAI Compatible)', value: 'custom' },
381
+ ...items
382
+ ];
383
+
377
384
  if (!items.length) {
378
385
  return (
379
386
  <Box flexDirection="column" flexGrow={1}>
380
- <StateDisplay type="error" message="No presets available. Please ensure the backend is running." />
387
+ <Title color="cyan">Select Preset</Title>
381
388
  <SelectInput
382
- items={[{ label: '← Back', value: 'back' }]}
383
- onSelect={() => setStage('list')}
389
+ items={finalItems}
390
+ onSelect={it => {
391
+ if (it.value === 'custom') {
392
+ const fields: ProviderField[] = [
393
+ { key: 'name', label: 'Provider Nickname', required: true, placeholder: 'My Custom API' },
394
+ { key: 'baseUrl', label: 'Base URL', required: true, placeholder: 'https://api.example.com/v1' },
395
+ { key: 'apiKey', label: 'API Key', required: true, secret: true },
396
+ ];
397
+ setSelectedPresetId('custom');
398
+ setAddFields(fields);
399
+ setAddFieldValues({});
400
+ setAddFieldIndex(0);
401
+ setListOffset(0);
402
+ setStage('add_input_fields');
403
+ } else {
404
+ setStage('list');
405
+ }
406
+ }}
384
407
  />
408
+ {!items.length && <Hint dimColor>No presets found from server, only Custom available.</Hint>}
385
409
  </Box>
386
410
  );
387
411
  }
388
412
 
389
413
  const MAX_VISIBLE = 8;
390
- const start = Math.min(listOffset, Math.max(0, items.length - MAX_VISIBLE));
391
- const sliced = items.slice(start, start + MAX_VISIBLE);
414
+ const start = Math.min(listOffset, Math.max(0, finalItems.length - MAX_VISIBLE));
415
+ const sliced = finalItems.slice(start, start + MAX_VISIBLE);
392
416
 
393
417
  return (
394
418
  <Box flexDirection="column" flexGrow={1}>
@@ -398,24 +422,38 @@ export const ProviderPanel: React.FC<{
398
422
  <SelectInput
399
423
  items={sliced}
400
424
  onSelect={it => {
401
- const preset = presets.find(p => p.id === (it.value as string));
402
- const fields: ProviderField[] =
403
- preset?.fields && preset.fields.length > 0
404
- ? preset.fields
405
- : [
406
- { key: 'name', label: 'Provider Nickname', required: true },
407
- { key: 'apiKey', label: 'API Key', required: true, secret: true },
408
- ];
409
- setSelectedPresetId(it.value as string);
410
- setAddFields(fields);
411
- setAddFieldValues({});
412
- setAddFieldIndex(0);
413
- setListOffset(0);
414
- setStage('add_input_fields');
425
+ if (it.value === 'custom') {
426
+ const fields: ProviderField[] = [
427
+ { key: 'name', label: 'Provider Nickname', required: true, placeholder: 'My Custom API' },
428
+ { key: 'baseUrl', label: 'Base URL', required: true, placeholder: 'https://api.example.com/v1' },
429
+ { key: 'apiKey', label: 'API Key', required: true, secret: true },
430
+ ];
431
+ setSelectedPresetId('custom');
432
+ setAddFields(fields);
433
+ setAddFieldValues({});
434
+ setAddFieldIndex(0);
435
+ setListOffset(0);
436
+ setStage('add_input_fields');
437
+ } else {
438
+ const preset = presets.find(p => p.id === (it.value as string));
439
+ const fields: ProviderField[] =
440
+ preset?.fields && preset.fields.length > 0
441
+ ? preset.fields
442
+ : [
443
+ { key: 'name', label: 'Provider Nickname', required: true },
444
+ { key: 'apiKey', label: 'API Key', required: true, secret: true },
445
+ ];
446
+ setSelectedPresetId(it.value as string);
447
+ setAddFields(fields);
448
+ setAddFieldValues({});
449
+ setAddFieldIndex(0);
450
+ setListOffset(0);
451
+ setStage('add_input_fields');
452
+ }
415
453
  }}
416
454
  />
417
455
  </Box>
418
- <ScrollBar total={items.length} offset={start} height={MAX_VISIBLE} />
456
+ <ScrollBar total={finalItems.length} offset={start} height={MAX_VISIBLE} />
419
457
  </Box>
420
458
  <Hint>↑↓: Select · j Next Page · k Prev Page · ESC: Back</Hint>
421
459
  </Box>
@@ -220,6 +220,9 @@ export const CliMenuManager: React.FC = () => {
220
220
  // Language
221
221
  const [lang, setLang] = useState<Lang>('en');
222
222
 
223
+ // Config ready probe (avoid Logo early read)
224
+ const [configReady, setConfigReady] = useState(false);
225
+
223
226
  useEffect(() => {
224
227
  if (configReady) {
225
228
  try {
@@ -291,11 +294,9 @@ export const CliMenuManager: React.FC = () => {
291
294
 
292
295
  // Compute viewport
293
296
  const TOP_H = page === null ? TOP_H_HOME : TOP_H_COMPACT;
294
- const MID_H = Math.max(5, VIEW_H - TOP_H - BOTTOM_H);
297
+ const MID_H = Math.max(5, VIEW_H - TOP_H - BOTTOM_H - (page === null ? 0 : 2));
295
298
  const MSGS_PAGE_SIZE = Math.max(1, MID_H - 2);
296
299
  const [expandMsgs, setExpandMsgs] = useState(false);
297
- // Config ready probe (avoid Logo early read)
298
- const [configReady, setConfigReady] = useState(false);
299
300
 
300
301
  // Boot/Reuse singleton local server (with retry)
301
302
  useEffect(() => {
@@ -592,9 +593,10 @@ export const CliMenuManager: React.FC = () => {
592
593
  // History shortcuts
593
594
  if (!showHelp && page === 'history') {
594
595
  if (historyMenuStage === 'list') {
596
+ const HIST_VISIBLE = Math.max(1, MID_H - 1);
595
597
  if (key.downArrow || input === 'j') {
596
598
  // Internal SelectInput handles cursor, we just need to track offset for ScrollBar
597
- setListOffset(o => o + 1);
599
+ setListOffset(o => Math.min(o + 1, Math.max(0, groupedHistoryItems.length - HIST_VISIBLE)));
598
600
  }
599
601
  if (key.upArrow || input === 'k') {
600
602
  setListOffset(o => Math.max(0, o - 1));
@@ -1042,12 +1044,16 @@ export const CliMenuManager: React.FC = () => {
1042
1044
 
1043
1045
 
1044
1046
  // History List
1047
+ const HIST_VISIBLE = Math.max(1, MID_H - 1);
1048
+ const start = Math.min(listOffset, Math.max(0, groupedHistoryItems.length - HIST_VISIBLE));
1049
+ const slicedItems = groupedHistoryItems.slice(start, start + HIST_VISIBLE);
1050
+
1045
1051
  return (
1046
1052
  <Box width={VIEW_W} height={MID_H} flexDirection="row">
1047
1053
  <Box flexDirection="column" flexGrow={1}>
1048
1054
  <SelectInput
1049
- key={`${historyCursor ?? 'first'}:${groupedHistoryItems.length}`}
1050
- items={groupedHistoryItems}
1055
+ key={`${historyCursor ?? 'first'}:${slicedItems.length}:${start}`}
1056
+ items={slicedItems}
1051
1057
  onSelect={item => {
1052
1058
  if (String(item.value).startsWith('__group_')) return; // Ignore group headers
1053
1059
  const session = historyList.find(h => h.id === item.value);
@@ -1069,7 +1075,7 @@ export const CliMenuManager: React.FC = () => {
1069
1075
  />
1070
1076
  <Hint>{i18nMap[lang].historyHint}</Hint>
1071
1077
  </Box>
1072
- <ScrollBar total={groupedHistoryItems.length} offset={listOffset} height={MID_H} />
1078
+ <ScrollBar total={groupedHistoryItems.length} offset={start} height={MID_H} />
1073
1079
  </Box>
1074
1080
  );
1075
1081
  }