@gguf/coder 0.3.1 → 0.3.2
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 +2 -2
- package/pnpm-workspace.yaml +15 -0
- package/color_picker.html +0 -36
- package/scripts/extract-changelog.js +0 -73
- package/scripts/fetch-models.js +0 -143
- package/scripts/test.sh +0 -40
- package/scripts/update-homebrew-formula.sh +0 -125
- package/scripts/update-nix-version.sh +0 -157
- package/source/ai-sdk-client/AISDKClient.spec.ts +0 -117
- package/source/ai-sdk-client/AISDKClient.ts +0 -155
- package/source/ai-sdk-client/chat/chat-handler.spec.ts +0 -121
- package/source/ai-sdk-client/chat/chat-handler.ts +0 -276
- package/source/ai-sdk-client/chat/streaming-handler.spec.ts +0 -173
- package/source/ai-sdk-client/chat/streaming-handler.ts +0 -110
- package/source/ai-sdk-client/chat/tool-processor.spec.ts +0 -92
- package/source/ai-sdk-client/chat/tool-processor.ts +0 -70
- package/source/ai-sdk-client/converters/message-converter.spec.ts +0 -220
- package/source/ai-sdk-client/converters/message-converter.ts +0 -113
- package/source/ai-sdk-client/converters/tool-converter.spec.ts +0 -90
- package/source/ai-sdk-client/converters/tool-converter.ts +0 -46
- package/source/ai-sdk-client/error-handling/error-extractor.spec.ts +0 -55
- package/source/ai-sdk-client/error-handling/error-extractor.ts +0 -15
- package/source/ai-sdk-client/error-handling/error-parser.spec.ts +0 -169
- package/source/ai-sdk-client/error-handling/error-parser.ts +0 -161
- package/source/ai-sdk-client/index.ts +0 -7
- package/source/ai-sdk-client/providers/provider-factory.spec.ts +0 -71
- package/source/ai-sdk-client/providers/provider-factory.ts +0 -41
- package/source/ai-sdk-client/types.ts +0 -9
- package/source/ai-sdk-client-empty-message.spec.ts +0 -141
- package/source/ai-sdk-client-error-handling.spec.ts +0 -186
- package/source/ai-sdk-client-maxretries.spec.ts +0 -114
- package/source/ai-sdk-client-preparestep.spec.ts +0 -279
- package/source/app/App.spec.tsx +0 -32
- package/source/app/App.tsx +0 -480
- package/source/app/components/AppContainer.spec.tsx +0 -96
- package/source/app/components/AppContainer.tsx +0 -56
- package/source/app/components/ChatInterface.spec.tsx +0 -163
- package/source/app/components/ChatInterface.tsx +0 -144
- package/source/app/components/ModalSelectors.spec.tsx +0 -141
- package/source/app/components/ModalSelectors.tsx +0 -135
- package/source/app/helpers.spec.ts +0 -97
- package/source/app/helpers.ts +0 -63
- package/source/app/index.ts +0 -4
- package/source/app/types.ts +0 -39
- package/source/app/utils/appUtils.ts +0 -294
- package/source/app/utils/conversationState.ts +0 -310
- package/source/app.spec.tsx +0 -244
- package/source/cli.spec.ts +0 -73
- package/source/cli.tsx +0 -51
- package/source/client-factory.spec.ts +0 -48
- package/source/client-factory.ts +0 -178
- package/source/command-parser.spec.ts +0 -127
- package/source/command-parser.ts +0 -36
- package/source/commands/checkpoint.spec.tsx +0 -277
- package/source/commands/checkpoint.tsx +0 -366
- package/source/commands/clear.tsx +0 -22
- package/source/commands/custom-commands.tsx +0 -121
- package/source/commands/exit.ts +0 -21
- package/source/commands/export.spec.tsx +0 -131
- package/source/commands/export.tsx +0 -79
- package/source/commands/help.tsx +0 -120
- package/source/commands/index.ts +0 -17
- package/source/commands/init.tsx +0 -339
- package/source/commands/lsp-command.spec.tsx +0 -281
- package/source/commands/lsp.tsx +0 -120
- package/source/commands/mcp-command.spec.tsx +0 -313
- package/source/commands/mcp.tsx +0 -162
- package/source/commands/model-database.spec.tsx +0 -758
- package/source/commands/model-database.tsx +0 -418
- package/source/commands/model.ts +0 -12
- package/source/commands/provider.ts +0 -12
- package/source/commands/setup-config.tsx +0 -16
- package/source/commands/simple-commands.spec.tsx +0 -175
- package/source/commands/status.ts +0 -12
- package/source/commands/theme.ts +0 -12
- package/source/commands/update.spec.tsx +0 -261
- package/source/commands/update.tsx +0 -201
- package/source/commands/usage.spec.tsx +0 -495
- package/source/commands/usage.tsx +0 -100
- package/source/commands.spec.ts +0 -436
- package/source/commands.ts +0 -83
- package/source/components/assistant-message.spec.tsx +0 -796
- package/source/components/assistant-message.tsx +0 -34
- package/source/components/bash-execution-indicator.tsx +0 -21
- package/source/components/cancelling-indicator.tsx +0 -16
- package/source/components/chat-queue.spec.tsx +0 -83
- package/source/components/chat-queue.tsx +0 -36
- package/source/components/checkpoint-display.spec.tsx +0 -219
- package/source/components/checkpoint-display.tsx +0 -126
- package/source/components/checkpoint-selector.spec.tsx +0 -173
- package/source/components/checkpoint-selector.tsx +0 -173
- package/source/components/development-mode-indicator.spec.tsx +0 -268
- package/source/components/development-mode-indicator.tsx +0 -38
- package/source/components/message-box.spec.tsx +0 -427
- package/source/components/message-box.tsx +0 -87
- package/source/components/model-selector.tsx +0 -132
- package/source/components/provider-selector.tsx +0 -75
- package/source/components/random-spinner.tsx +0 -19
- package/source/components/security-disclaimer.tsx +0 -73
- package/source/components/status-connection-display.spec.tsx +0 -133
- package/source/components/status.tsx +0 -267
- package/source/components/theme-selector.tsx +0 -126
- package/source/components/tool-confirmation.tsx +0 -190
- package/source/components/tool-execution-indicator.tsx +0 -33
- package/source/components/tool-message.tsx +0 -85
- package/source/components/ui/titled-box.spec.tsx +0 -207
- package/source/components/ui/titled-box.tsx +0 -57
- package/source/components/usage/progress-bar.spec.tsx +0 -398
- package/source/components/usage/progress-bar.tsx +0 -30
- package/source/components/usage/usage-display.spec.tsx +0 -780
- package/source/components/usage/usage-display.tsx +0 -291
- package/source/components/user-input.spec.tsx +0 -327
- package/source/components/user-input.tsx +0 -533
- package/source/components/user-message.spec.tsx +0 -230
- package/source/components/user-message.tsx +0 -84
- package/source/components/welcome-message.tsx +0 -76
- package/source/config/env-substitution.ts +0 -65
- package/source/config/index.spec.ts +0 -171
- package/source/config/index.ts +0 -154
- package/source/config/paths.spec.ts +0 -241
- package/source/config/paths.ts +0 -55
- package/source/config/preferences.ts +0 -51
- package/source/config/themes.ts +0 -315
- package/source/constants.ts +0 -130
- package/source/context/mode-context.spec.ts +0 -79
- package/source/context/mode-context.ts +0 -24
- package/source/custom-commands/executor.spec.ts +0 -142
- package/source/custom-commands/executor.ts +0 -64
- package/source/custom-commands/loader.spec.ts +0 -314
- package/source/custom-commands/loader.ts +0 -153
- package/source/custom-commands/parser.ts +0 -196
- package/source/hooks/chat-handler/conversation/conversation-loop.spec.ts +0 -39
- package/source/hooks/chat-handler/conversation/conversation-loop.tsx +0 -511
- package/source/hooks/chat-handler/conversation/tool-executor.spec.ts +0 -50
- package/source/hooks/chat-handler/conversation/tool-executor.tsx +0 -109
- package/source/hooks/chat-handler/index.ts +0 -12
- package/source/hooks/chat-handler/state/streaming-state.spec.ts +0 -26
- package/source/hooks/chat-handler/state/streaming-state.ts +0 -19
- package/source/hooks/chat-handler/types.ts +0 -38
- package/source/hooks/chat-handler/useChatHandler.spec.tsx +0 -321
- package/source/hooks/chat-handler/useChatHandler.tsx +0 -194
- package/source/hooks/chat-handler/utils/context-checker.spec.ts +0 -60
- package/source/hooks/chat-handler/utils/context-checker.tsx +0 -73
- package/source/hooks/chat-handler/utils/message-helpers.spec.ts +0 -42
- package/source/hooks/chat-handler/utils/message-helpers.tsx +0 -36
- package/source/hooks/chat-handler/utils/tool-filters.spec.ts +0 -109
- package/source/hooks/chat-handler/utils/tool-filters.ts +0 -64
- package/source/hooks/useAppHandlers.tsx +0 -291
- package/source/hooks/useAppInitialization.tsx +0 -422
- package/source/hooks/useAppState.tsx +0 -311
- package/source/hooks/useDirectoryTrust.tsx +0 -98
- package/source/hooks/useInputState.ts +0 -414
- package/source/hooks/useModeHandlers.tsx +0 -302
- package/source/hooks/useNonInteractiveMode.ts +0 -140
- package/source/hooks/useTerminalWidth.tsx +0 -81
- package/source/hooks/useTheme.ts +0 -18
- package/source/hooks/useToolHandler.tsx +0 -349
- package/source/hooks/useUIState.ts +0 -61
- package/source/init/agents-template-generator.ts +0 -421
- package/source/init/existing-rules-extractor.ts +0 -319
- package/source/init/file-scanner.spec.ts +0 -227
- package/source/init/file-scanner.ts +0 -238
- package/source/init/framework-detector.ts +0 -382
- package/source/init/language-detector.ts +0 -269
- package/source/init/project-analyzer.spec.ts +0 -231
- package/source/init/project-analyzer.ts +0 -458
- package/source/lsp/index.ts +0 -31
- package/source/lsp/lsp-client.spec.ts +0 -508
- package/source/lsp/lsp-client.ts +0 -487
- package/source/lsp/lsp-manager.spec.ts +0 -477
- package/source/lsp/lsp-manager.ts +0 -419
- package/source/lsp/protocol.spec.ts +0 -502
- package/source/lsp/protocol.ts +0 -360
- package/source/lsp/server-discovery.spec.ts +0 -654
- package/source/lsp/server-discovery.ts +0 -515
- package/source/markdown-parser/html-entities.spec.ts +0 -88
- package/source/markdown-parser/html-entities.ts +0 -45
- package/source/markdown-parser/index.spec.ts +0 -281
- package/source/markdown-parser/index.ts +0 -126
- package/source/markdown-parser/table-parser.spec.ts +0 -133
- package/source/markdown-parser/table-parser.ts +0 -114
- package/source/markdown-parser/utils.spec.ts +0 -70
- package/source/markdown-parser/utils.ts +0 -13
- package/source/mcp/mcp-client.spec.ts +0 -81
- package/source/mcp/mcp-client.ts +0 -625
- package/source/mcp/transport-factory.spec.ts +0 -406
- package/source/mcp/transport-factory.ts +0 -312
- package/source/message-handler.ts +0 -67
- package/source/model-database/database-engine.spec.ts +0 -494
- package/source/model-database/database-engine.ts +0 -50
- package/source/model-database/model-database.spec.ts +0 -363
- package/source/model-database/model-database.ts +0 -91
- package/source/model-database/model-engine.spec.ts +0 -447
- package/source/model-database/model-engine.ts +0 -65
- package/source/model-database/model-fetcher.spec.ts +0 -583
- package/source/model-database/model-fetcher.ts +0 -330
- package/source/models/index.ts +0 -1
- package/source/models/models-cache.spec.ts +0 -214
- package/source/models/models-cache.ts +0 -78
- package/source/models/models-dev-client.spec.ts +0 -379
- package/source/models/models-dev-client.ts +0 -329
- package/source/models/models-types.ts +0 -68
- package/source/prompt-history.ts +0 -155
- package/source/security/command-injection.spec.ts +0 -240
- package/source/services/checkpoint-manager.spec.ts +0 -523
- package/source/services/checkpoint-manager.ts +0 -466
- package/source/services/file-snapshot.spec.ts +0 -569
- package/source/services/file-snapshot.ts +0 -220
- package/source/test-utils/render-with-theme.tsx +0 -48
- package/source/tokenization/index.ts +0 -1
- package/source/tokenization/tokenizer-factory.spec.ts +0 -170
- package/source/tokenization/tokenizer-factory.ts +0 -125
- package/source/tokenization/tokenizers/anthropic-tokenizer.spec.ts +0 -200
- package/source/tokenization/tokenizers/anthropic-tokenizer.ts +0 -43
- package/source/tokenization/tokenizers/fallback-tokenizer.spec.ts +0 -236
- package/source/tokenization/tokenizers/fallback-tokenizer.ts +0 -26
- package/source/tokenization/tokenizers/llama-tokenizer.spec.ts +0 -224
- package/source/tokenization/tokenizers/llama-tokenizer.ts +0 -41
- package/source/tokenization/tokenizers/openai-tokenizer.spec.ts +0 -184
- package/source/tokenization/tokenizers/openai-tokenizer.ts +0 -57
- package/source/tool-calling/index.ts +0 -5
- package/source/tool-calling/json-parser.spec.ts +0 -639
- package/source/tool-calling/json-parser.ts +0 -247
- package/source/tool-calling/tool-parser.spec.ts +0 -395
- package/source/tool-calling/tool-parser.ts +0 -120
- package/source/tool-calling/xml-parser.spec.ts +0 -662
- package/source/tool-calling/xml-parser.ts +0 -289
- package/source/tools/execute-bash.spec.tsx +0 -353
- package/source/tools/execute-bash.tsx +0 -219
- package/source/tools/execute-function.spec.ts +0 -130
- package/source/tools/fetch-url.spec.tsx +0 -342
- package/source/tools/fetch-url.tsx +0 -172
- package/source/tools/find-files.spec.tsx +0 -924
- package/source/tools/find-files.tsx +0 -293
- package/source/tools/index.ts +0 -102
- package/source/tools/lsp-get-diagnostics.tsx +0 -192
- package/source/tools/needs-approval.spec.ts +0 -282
- package/source/tools/read-file.spec.tsx +0 -801
- package/source/tools/read-file.tsx +0 -387
- package/source/tools/search-file-contents.spec.tsx +0 -1273
- package/source/tools/search-file-contents.tsx +0 -293
- package/source/tools/string-replace.spec.tsx +0 -730
- package/source/tools/string-replace.tsx +0 -548
- package/source/tools/tool-manager.ts +0 -210
- package/source/tools/tool-registry.spec.ts +0 -415
- package/source/tools/tool-registry.ts +0 -228
- package/source/tools/web-search.tsx +0 -223
- package/source/tools/write-file.spec.tsx +0 -559
- package/source/tools/write-file.tsx +0 -228
- package/source/types/app.ts +0 -37
- package/source/types/checkpoint.ts +0 -48
- package/source/types/commands.ts +0 -46
- package/source/types/components.ts +0 -27
- package/source/types/config.ts +0 -103
- package/source/types/core-connection-status.spec.ts +0 -67
- package/source/types/core.ts +0 -181
- package/source/types/hooks.ts +0 -50
- package/source/types/index.ts +0 -12
- package/source/types/markdown-parser.ts +0 -11
- package/source/types/mcp.ts +0 -52
- package/source/types/system.ts +0 -16
- package/source/types/tokenization.ts +0 -41
- package/source/types/ui.ts +0 -40
- package/source/types/usage.ts +0 -58
- package/source/types/utils.ts +0 -16
- package/source/usage/calculator.spec.ts +0 -385
- package/source/usage/calculator.ts +0 -104
- package/source/usage/storage.spec.ts +0 -703
- package/source/usage/storage.ts +0 -238
- package/source/usage/tracker.spec.ts +0 -456
- package/source/usage/tracker.ts +0 -102
- package/source/utils/atomic-deletion.spec.ts +0 -194
- package/source/utils/atomic-deletion.ts +0 -127
- package/source/utils/bounded-map.spec.ts +0 -300
- package/source/utils/bounded-map.ts +0 -193
- package/source/utils/checkpoint-utils.spec.ts +0 -222
- package/source/utils/checkpoint-utils.ts +0 -92
- package/source/utils/error-formatter.spec.ts +0 -169
- package/source/utils/error-formatter.ts +0 -194
- package/source/utils/file-autocomplete.spec.ts +0 -173
- package/source/utils/file-autocomplete.ts +0 -196
- package/source/utils/file-cache.spec.ts +0 -309
- package/source/utils/file-cache.ts +0 -195
- package/source/utils/file-content-loader.spec.ts +0 -180
- package/source/utils/file-content-loader.ts +0 -179
- package/source/utils/file-mention-handler.spec.ts +0 -261
- package/source/utils/file-mention-handler.ts +0 -84
- package/source/utils/file-mention-parser.spec.ts +0 -182
- package/source/utils/file-mention-parser.ts +0 -170
- package/source/utils/fuzzy-matching.spec.ts +0 -149
- package/source/utils/fuzzy-matching.ts +0 -146
- package/source/utils/indentation-normalizer.spec.ts +0 -216
- package/source/utils/indentation-normalizer.ts +0 -76
- package/source/utils/installation-detector.spec.ts +0 -178
- package/source/utils/installation-detector.ts +0 -153
- package/source/utils/logging/config.spec.ts +0 -311
- package/source/utils/logging/config.ts +0 -210
- package/source/utils/logging/console-facade.spec.ts +0 -184
- package/source/utils/logging/console-facade.ts +0 -384
- package/source/utils/logging/correlation.spec.ts +0 -679
- package/source/utils/logging/correlation.ts +0 -474
- package/source/utils/logging/formatters.spec.ts +0 -464
- package/source/utils/logging/formatters.ts +0 -207
- package/source/utils/logging/health-monitor/alerts/alert-manager.spec.ts +0 -93
- package/source/utils/logging/health-monitor/alerts/alert-manager.ts +0 -79
- package/source/utils/logging/health-monitor/checks/configuration-check.spec.ts +0 -56
- package/source/utils/logging/health-monitor/checks/configuration-check.ts +0 -43
- package/source/utils/logging/health-monitor/checks/logging-check.spec.ts +0 -56
- package/source/utils/logging/health-monitor/checks/logging-check.ts +0 -58
- package/source/utils/logging/health-monitor/checks/memory-check.spec.ts +0 -100
- package/source/utils/logging/health-monitor/checks/memory-check.ts +0 -78
- package/source/utils/logging/health-monitor/checks/performance-check.spec.ts +0 -56
- package/source/utils/logging/health-monitor/checks/performance-check.ts +0 -56
- package/source/utils/logging/health-monitor/checks/request-check.spec.ts +0 -56
- package/source/utils/logging/health-monitor/checks/request-check.ts +0 -76
- package/source/utils/logging/health-monitor/core/health-check-runner.spec.ts +0 -70
- package/source/utils/logging/health-monitor/core/health-check-runner.ts +0 -138
- package/source/utils/logging/health-monitor/core/health-monitor.spec.ts +0 -58
- package/source/utils/logging/health-monitor/core/health-monitor.ts +0 -344
- package/source/utils/logging/health-monitor/core/scoring.spec.ts +0 -65
- package/source/utils/logging/health-monitor/core/scoring.ts +0 -91
- package/source/utils/logging/health-monitor/index.ts +0 -15
- package/source/utils/logging/health-monitor/instances.ts +0 -48
- package/source/utils/logging/health-monitor/middleware/http-middleware.spec.ts +0 -141
- package/source/utils/logging/health-monitor/middleware/http-middleware.ts +0 -75
- package/source/utils/logging/health-monitor/types.ts +0 -126
- package/source/utils/logging/index.spec.ts +0 -284
- package/source/utils/logging/index.ts +0 -236
- package/source/utils/logging/integration.spec.ts +0 -441
- package/source/utils/logging/log-method-factory.spec.ts +0 -573
- package/source/utils/logging/log-method-factory.ts +0 -233
- package/source/utils/logging/log-query/aggregation/aggregator.spec.ts +0 -277
- package/source/utils/logging/log-query/aggregation/aggregator.ts +0 -159
- package/source/utils/logging/log-query/aggregation/facet-generator.spec.ts +0 -159
- package/source/utils/logging/log-query/aggregation/facet-generator.ts +0 -47
- package/source/utils/logging/log-query/index.ts +0 -23
- package/source/utils/logging/log-query/query/filter-predicates.spec.ts +0 -247
- package/source/utils/logging/log-query/query/filter-predicates.ts +0 -154
- package/source/utils/logging/log-query/query/query-builder.spec.ts +0 -182
- package/source/utils/logging/log-query/query/query-builder.ts +0 -151
- package/source/utils/logging/log-query/query/query-engine.spec.ts +0 -214
- package/source/utils/logging/log-query/query/query-engine.ts +0 -45
- package/source/utils/logging/log-query/storage/circular-buffer.spec.ts +0 -143
- package/source/utils/logging/log-query/storage/circular-buffer.ts +0 -75
- package/source/utils/logging/log-query/storage/index-manager.spec.ts +0 -150
- package/source/utils/logging/log-query/storage/index-manager.ts +0 -71
- package/source/utils/logging/log-query/storage/log-storage.spec.ts +0 -257
- package/source/utils/logging/log-query/storage/log-storage.ts +0 -80
- package/source/utils/logging/log-query/types.ts +0 -163
- package/source/utils/logging/log-query/utils/helpers.spec.ts +0 -263
- package/source/utils/logging/log-query/utils/helpers.ts +0 -72
- package/source/utils/logging/log-query/utils/sorting.spec.ts +0 -182
- package/source/utils/logging/log-query/utils/sorting.ts +0 -61
- package/source/utils/logging/logger-provider.spec.ts +0 -262
- package/source/utils/logging/logger-provider.ts +0 -362
- package/source/utils/logging/performance.spec.ts +0 -209
- package/source/utils/logging/performance.ts +0 -757
- package/source/utils/logging/pino-logger.spec.ts +0 -425
- package/source/utils/logging/pino-logger.ts +0 -514
- package/source/utils/logging/redaction.spec.ts +0 -490
- package/source/utils/logging/redaction.ts +0 -267
- package/source/utils/logging/request-tracker.spec.ts +0 -1198
- package/source/utils/logging/request-tracker.ts +0 -803
- package/source/utils/logging/transports.spec.ts +0 -505
- package/source/utils/logging/transports.ts +0 -305
- package/source/utils/logging/types.ts +0 -216
- package/source/utils/message-builder.spec.ts +0 -179
- package/source/utils/message-builder.ts +0 -101
- package/source/utils/message-queue.tsx +0 -486
- package/source/utils/paste-detection.spec.ts +0 -69
- package/source/utils/paste-detection.ts +0 -124
- package/source/utils/paste-roundtrip.spec.ts +0 -442
- package/source/utils/paste-utils.spec.ts +0 -128
- package/source/utils/paste-utils.ts +0 -52
- package/source/utils/programming-language-helper.spec.ts +0 -74
- package/source/utils/programming-language-helper.ts +0 -32
- package/source/utils/prompt-assembly.spec.ts +0 -221
- package/source/utils/prompt-processor.ts +0 -173
- package/source/utils/tool-args-parser.spec.ts +0 -136
- package/source/utils/tool-args-parser.ts +0 -54
- package/source/utils/tool-cancellation.spec.ts +0 -230
- package/source/utils/tool-cancellation.ts +0 -28
- package/source/utils/tool-result-display.spec.tsx +0 -469
- package/source/utils/tool-result-display.tsx +0 -90
- package/source/utils/update-checker.spec.ts +0 -383
- package/source/utils/update-checker.ts +0 -183
- package/source/wizard/config-wizard.spec.tsx +0 -103
- package/source/wizard/config-wizard.tsx +0 -382
- package/source/wizard/steps/location-step.spec.tsx +0 -186
- package/source/wizard/steps/location-step.tsx +0 -147
- package/source/wizard/steps/mcp-step.spec.tsx +0 -607
- package/source/wizard/steps/mcp-step.tsx +0 -632
- package/source/wizard/steps/provider-step.spec.tsx +0 -342
- package/source/wizard/steps/provider-step.tsx +0 -957
- package/source/wizard/steps/summary-step.spec.tsx +0 -749
- package/source/wizard/steps/summary-step.tsx +0 -228
- package/source/wizard/templates/mcp-templates.spec.ts +0 -613
- package/source/wizard/templates/mcp-templates.ts +0 -570
- package/source/wizard/templates/provider-templates.spec.ts +0 -152
- package/source/wizard/templates/provider-templates.ts +0 -485
- package/source/wizard/utils/fetch-cloud-models.spec.ts +0 -428
- package/source/wizard/utils/fetch-cloud-models.ts +0 -223
- package/source/wizard/utils/fetch-local-models.spec.ts +0 -297
- package/source/wizard/utils/fetch-local-models.ts +0 -192
- package/source/wizard/validation-array.spec.ts +0 -264
- package/source/wizard/validation.spec.ts +0 -373
- package/source/wizard/validation.ts +0 -232
|
@@ -1,957 +0,0 @@
|
|
|
1
|
-
import { colors } from '@/config/index';
|
|
2
|
-
import { useResponsiveTerminal } from '@/hooks/useTerminalWidth';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import SelectInput from 'ink-select-input';
|
|
5
|
-
import RandomSpinner from '@/components/random-spinner';
|
|
6
|
-
import TextInput from 'ink-text-input';
|
|
7
|
-
import { useEffect, useRef, useState } from 'react';
|
|
8
|
-
import type { ProviderConfig } from '../../types/config';
|
|
9
|
-
import {
|
|
10
|
-
PROVIDER_TEMPLATES,
|
|
11
|
-
type ProviderTemplate,
|
|
12
|
-
} from '../templates/provider-templates';
|
|
13
|
-
import { fetchCloudModels } from '../utils/fetch-cloud-models';
|
|
14
|
-
import {
|
|
15
|
-
type CloudModelsEndpointType,
|
|
16
|
-
type LocalModel,
|
|
17
|
-
type LocalModelsEndpointType,
|
|
18
|
-
fetchLocalModels,
|
|
19
|
-
} from '../utils/fetch-local-models';
|
|
20
|
-
|
|
21
|
-
// Helper to check if modelsEndpoint is a cloud provider type
|
|
22
|
-
const CLOUD_ENDPOINTS: CloudModelsEndpointType[] = [
|
|
23
|
-
'anthropic',
|
|
24
|
-
'openai',
|
|
25
|
-
'mistral',
|
|
26
|
-
'github',
|
|
27
|
-
];
|
|
28
|
-
const isCloudEndpoint = (
|
|
29
|
-
endpoint: string | undefined,
|
|
30
|
-
): endpoint is CloudModelsEndpointType => {
|
|
31
|
-
return CLOUD_ENDPOINTS.includes(endpoint as CloudModelsEndpointType);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const LOCAL_ENDPOINTS: LocalModelsEndpointType[] = [
|
|
35
|
-
'ollama',
|
|
36
|
-
'openai-compatible',
|
|
37
|
-
];
|
|
38
|
-
const isLocalEndpoint = (
|
|
39
|
-
endpoint: string | undefined,
|
|
40
|
-
): endpoint is LocalModelsEndpointType => {
|
|
41
|
-
return LOCAL_ENDPOINTS.includes(endpoint as LocalModelsEndpointType);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
interface ProviderStepProps {
|
|
45
|
-
onComplete: (providers: ProviderConfig[]) => void;
|
|
46
|
-
onBack?: () => void;
|
|
47
|
-
existingProviders?: ProviderConfig[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
type Mode =
|
|
51
|
-
| 'select-template-or-custom'
|
|
52
|
-
| 'template-selection'
|
|
53
|
-
| 'edit-selection'
|
|
54
|
-
| 'edit-or-delete'
|
|
55
|
-
| 'field-input'
|
|
56
|
-
| 'model-source-choice'
|
|
57
|
-
| 'fetching-models'
|
|
58
|
-
| 'model-selection'
|
|
59
|
-
| 'done';
|
|
60
|
-
|
|
61
|
-
interface TemplateOption {
|
|
62
|
-
label: string;
|
|
63
|
-
value: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function ProviderStep({
|
|
67
|
-
onComplete,
|
|
68
|
-
onBack,
|
|
69
|
-
existingProviders = [],
|
|
70
|
-
}: ProviderStepProps) {
|
|
71
|
-
const { isNarrow } = useResponsiveTerminal();
|
|
72
|
-
const [providers, setProviders] =
|
|
73
|
-
useState<ProviderConfig[]>(existingProviders);
|
|
74
|
-
|
|
75
|
-
// Update providers when existingProviders prop changes
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
setProviders(existingProviders);
|
|
78
|
-
}, [existingProviders]);
|
|
79
|
-
|
|
80
|
-
const [mode, setMode] = useState<Mode>('select-template-or-custom');
|
|
81
|
-
const [selectedTemplate, setSelectedTemplate] =
|
|
82
|
-
useState<ProviderTemplate | null>(null);
|
|
83
|
-
const [currentFieldIndex, setCurrentFieldIndex] = useState(0);
|
|
84
|
-
const [fieldAnswers, setFieldAnswers] = useState<Record<string, string>>({});
|
|
85
|
-
const [currentValue, setCurrentValue] = useState('');
|
|
86
|
-
const [error, setError] = useState<string | null>(null);
|
|
87
|
-
const [inputKey, setInputKey] = useState(0);
|
|
88
|
-
const [cameFromCustom, setCameFromCustom] = useState(false);
|
|
89
|
-
const [editingIndex, setEditingIndex] = useState<number | null>(null);
|
|
90
|
-
const [fetchedModels, setFetchedModels] = useState<LocalModel[]>([]);
|
|
91
|
-
const [selectedModelIds, setSelectedModelIds] = useState<Set<string>>(
|
|
92
|
-
new Set(),
|
|
93
|
-
);
|
|
94
|
-
const [fetchError, setFetchError] = useState<string | null>(null);
|
|
95
|
-
|
|
96
|
-
// Ref to store timeout ID for cleanup (prevents memory leak)
|
|
97
|
-
const fallbackTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
98
|
-
// Ref to track current template during async operations (prevents stale closure)
|
|
99
|
-
const currentTemplateRef = useRef<ProviderTemplate | null>(null);
|
|
100
|
-
// Ref to track if component is mounted (prevents setState after unmount)
|
|
101
|
-
const isMountedRef = useRef(true);
|
|
102
|
-
|
|
103
|
-
// Keep template ref in sync with state
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
currentTemplateRef.current = selectedTemplate;
|
|
106
|
-
}, [selectedTemplate]);
|
|
107
|
-
|
|
108
|
-
// Track mount status and cleanup on unmount
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
isMountedRef.current = true;
|
|
111
|
-
return () => {
|
|
112
|
-
isMountedRef.current = false;
|
|
113
|
-
if (fallbackTimeoutRef.current) {
|
|
114
|
-
clearTimeout(fallbackTimeoutRef.current);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
}, []);
|
|
118
|
-
|
|
119
|
-
// Clear model-related state when template changes (prevents stale data leaking)
|
|
120
|
-
// Note: We intentionally depend on selectedTemplate to trigger cleanup on template change
|
|
121
|
-
useEffect(() => {
|
|
122
|
-
// Only clear if we have a template (avoid clearing on initial mount with null)
|
|
123
|
-
if (selectedTemplate !== null) {
|
|
124
|
-
setFetchedModels([]);
|
|
125
|
-
setSelectedModelIds(new Set());
|
|
126
|
-
setFetchError(null);
|
|
127
|
-
}
|
|
128
|
-
// Also clear any pending fallback timeout when template changes
|
|
129
|
-
if (fallbackTimeoutRef.current) {
|
|
130
|
-
clearTimeout(fallbackTimeoutRef.current);
|
|
131
|
-
fallbackTimeoutRef.current = null;
|
|
132
|
-
}
|
|
133
|
-
}, [selectedTemplate]);
|
|
134
|
-
|
|
135
|
-
const initialOptions = [
|
|
136
|
-
{ label: 'Choose from common templates', value: 'templates' },
|
|
137
|
-
{ label: 'Add custom provider manually', value: 'custom' },
|
|
138
|
-
...(providers.length > 0
|
|
139
|
-
? [{ label: 'Edit existing providers', value: 'edit' }]
|
|
140
|
-
: []),
|
|
141
|
-
{ label: 'Skip providers', value: 'skip' },
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
const templateOptions: TemplateOption[] = [
|
|
145
|
-
...PROVIDER_TEMPLATES.map(template => ({
|
|
146
|
-
label: template.name,
|
|
147
|
-
value: template.id,
|
|
148
|
-
})),
|
|
149
|
-
{
|
|
150
|
-
label: `Done adding providers`,
|
|
151
|
-
value: 'done',
|
|
152
|
-
},
|
|
153
|
-
];
|
|
154
|
-
|
|
155
|
-
const editOptions: TemplateOption[] = [
|
|
156
|
-
...providers.map((provider, index) => ({
|
|
157
|
-
label: `${index + 1}. ${provider.name}`,
|
|
158
|
-
value: `edit-${index}`,
|
|
159
|
-
})),
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
const handleInitialSelect = (item: { value: string }) => {
|
|
163
|
-
if (item.value === 'templates') {
|
|
164
|
-
setMode('template-selection');
|
|
165
|
-
setCameFromCustom(false);
|
|
166
|
-
} else if (item.value === 'custom') {
|
|
167
|
-
// Find custom template
|
|
168
|
-
const customTemplate = PROVIDER_TEMPLATES.find(t => t.id === 'custom');
|
|
169
|
-
if (customTemplate) {
|
|
170
|
-
setSelectedTemplate(customTemplate);
|
|
171
|
-
setCurrentFieldIndex(0);
|
|
172
|
-
setFieldAnswers({});
|
|
173
|
-
setCurrentValue('');
|
|
174
|
-
setMode('field-input');
|
|
175
|
-
setCameFromCustom(true);
|
|
176
|
-
}
|
|
177
|
-
} else if (item.value === 'edit') {
|
|
178
|
-
setMode('edit-selection');
|
|
179
|
-
} else {
|
|
180
|
-
// Skip
|
|
181
|
-
onComplete(providers);
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const handleTemplateSelect = (item: TemplateOption) => {
|
|
186
|
-
if (item.value === 'done') {
|
|
187
|
-
onComplete(providers);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Adding new provider
|
|
192
|
-
const template = PROVIDER_TEMPLATES.find(t => t.id === item.value);
|
|
193
|
-
if (template) {
|
|
194
|
-
setEditingIndex(null); // Not editing
|
|
195
|
-
setSelectedTemplate(template);
|
|
196
|
-
setCurrentFieldIndex(0);
|
|
197
|
-
setFieldAnswers({});
|
|
198
|
-
setCurrentValue(template.fields[0]?.default || '');
|
|
199
|
-
setError(null);
|
|
200
|
-
setMode('field-input');
|
|
201
|
-
setCameFromCustom(false);
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const handleEditSelect = (item: TemplateOption) => {
|
|
206
|
-
// Store the index and show edit/delete options
|
|
207
|
-
if (item.value.startsWith('edit-')) {
|
|
208
|
-
const index = Number.parseInt(item.value.replace('edit-', ''), 10);
|
|
209
|
-
setEditingIndex(index);
|
|
210
|
-
setMode('edit-or-delete');
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const handleEditOrDeleteChoice = (item: { value: string }) => {
|
|
215
|
-
if (item.value === 'delete' && editingIndex !== null) {
|
|
216
|
-
// Delete the provider
|
|
217
|
-
const newProviders = providers.filter((_, i) => i !== editingIndex);
|
|
218
|
-
setProviders(newProviders);
|
|
219
|
-
setEditingIndex(null);
|
|
220
|
-
// Always go back to initial menu after deleting
|
|
221
|
-
setMode('select-template-or-custom');
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (item.value === 'edit' && editingIndex !== null) {
|
|
226
|
-
const provider = providers[editingIndex];
|
|
227
|
-
if (provider) {
|
|
228
|
-
// Find matching template (or use custom)
|
|
229
|
-
const template =
|
|
230
|
-
PROVIDER_TEMPLATES.find(t => t.id === provider.name) ||
|
|
231
|
-
PROVIDER_TEMPLATES.find(t => t.id === 'custom');
|
|
232
|
-
|
|
233
|
-
if (template) {
|
|
234
|
-
setSelectedTemplate(template);
|
|
235
|
-
setCurrentFieldIndex(0);
|
|
236
|
-
|
|
237
|
-
// Pre-populate field answers from existing provider
|
|
238
|
-
const answers: Record<string, string> = {};
|
|
239
|
-
if (provider.name) answers.providerName = provider.name;
|
|
240
|
-
if (provider.baseUrl) answers.baseUrl = provider.baseUrl;
|
|
241
|
-
if (provider.apiKey) answers.apiKey = provider.apiKey;
|
|
242
|
-
if (provider.models) answers.model = provider.models.join(', ');
|
|
243
|
-
|
|
244
|
-
setFieldAnswers(answers);
|
|
245
|
-
setCurrentValue(
|
|
246
|
-
answers[template.fields[0]?.name] ||
|
|
247
|
-
template.fields[0]?.default ||
|
|
248
|
-
'',
|
|
249
|
-
);
|
|
250
|
-
setError(null);
|
|
251
|
-
setMode('field-input');
|
|
252
|
-
setCameFromCustom(false);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const handleFieldSubmit = () => {
|
|
259
|
-
if (!selectedTemplate) return;
|
|
260
|
-
|
|
261
|
-
const currentField = selectedTemplate.fields[currentFieldIndex];
|
|
262
|
-
if (!currentField) return;
|
|
263
|
-
|
|
264
|
-
// Validate required fields
|
|
265
|
-
if (currentField.required && !currentValue.trim()) {
|
|
266
|
-
setError('This field is required');
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Validate with custom validator
|
|
271
|
-
if (currentField.validator && currentValue.trim()) {
|
|
272
|
-
const validationError = currentField.validator(currentValue);
|
|
273
|
-
if (validationError) {
|
|
274
|
-
setError(validationError);
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Save answer
|
|
280
|
-
const newAnswers = {
|
|
281
|
-
...fieldAnswers,
|
|
282
|
-
[currentField.name]: currentValue.trim(),
|
|
283
|
-
};
|
|
284
|
-
setFieldAnswers(newAnswers);
|
|
285
|
-
setError(null);
|
|
286
|
-
|
|
287
|
-
// Move to next field or complete
|
|
288
|
-
if (currentFieldIndex < selectedTemplate.fields.length - 1) {
|
|
289
|
-
const nextField = selectedTemplate.fields[currentFieldIndex + 1];
|
|
290
|
-
|
|
291
|
-
// Check if we should offer to fetch models
|
|
292
|
-
// For local providers: after baseUrl field
|
|
293
|
-
// For cloud providers: after apiKey field
|
|
294
|
-
const shouldOfferModelFetch =
|
|
295
|
-
nextField?.name === 'model' &&
|
|
296
|
-
selectedTemplate.modelsEndpoint &&
|
|
297
|
-
((currentField.name === 'baseUrl' &&
|
|
298
|
-
isLocalEndpoint(selectedTemplate.modelsEndpoint)) ||
|
|
299
|
-
(currentField.name === 'apiKey' &&
|
|
300
|
-
isCloudEndpoint(selectedTemplate.modelsEndpoint)));
|
|
301
|
-
|
|
302
|
-
if (shouldOfferModelFetch) {
|
|
303
|
-
setMode('model-source-choice');
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
setCurrentFieldIndex(currentFieldIndex + 1);
|
|
308
|
-
setCurrentValue(newAnswers[nextField?.name] || nextField?.default || '');
|
|
309
|
-
} else {
|
|
310
|
-
// Validate models array is not empty before building config
|
|
311
|
-
const modelsValue = newAnswers.model || '';
|
|
312
|
-
const modelsArray = modelsValue
|
|
313
|
-
.split(',')
|
|
314
|
-
.map(m => m.trim())
|
|
315
|
-
.filter(Boolean);
|
|
316
|
-
if (modelsArray.length === 0) {
|
|
317
|
-
setError('At least one model name is required');
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Build config and add/update provider
|
|
322
|
-
try {
|
|
323
|
-
const providerConfig = selectedTemplate.buildConfig(newAnswers);
|
|
324
|
-
|
|
325
|
-
if (editingIndex !== null) {
|
|
326
|
-
// Replace existing provider
|
|
327
|
-
const newProviders = [...providers];
|
|
328
|
-
newProviders[editingIndex] = providerConfig;
|
|
329
|
-
setProviders(newProviders);
|
|
330
|
-
} else {
|
|
331
|
-
// Add new provider
|
|
332
|
-
setProviders([...providers, providerConfig]);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Reset for next provider
|
|
336
|
-
setSelectedTemplate(null);
|
|
337
|
-
setCurrentFieldIndex(0);
|
|
338
|
-
setFieldAnswers({});
|
|
339
|
-
setCurrentValue('');
|
|
340
|
-
setEditingIndex(null);
|
|
341
|
-
setMode('template-selection');
|
|
342
|
-
} catch (err) {
|
|
343
|
-
setError(
|
|
344
|
-
err instanceof Error ? err.message : 'Failed to build configuration',
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
const handleModelSourceChoice = async (item: { value: string }) => {
|
|
351
|
-
if (item.value === 'manual') {
|
|
352
|
-
// User chose to enter manually - go to model field input
|
|
353
|
-
const modelFieldIndex = selectedTemplate?.fields.findIndex(
|
|
354
|
-
f => f.name === 'model',
|
|
355
|
-
);
|
|
356
|
-
if (modelFieldIndex !== undefined && modelFieldIndex >= 0) {
|
|
357
|
-
setCurrentFieldIndex(modelFieldIndex);
|
|
358
|
-
const modelField = selectedTemplate?.fields[modelFieldIndex];
|
|
359
|
-
setCurrentValue(
|
|
360
|
-
fieldAnswers[modelField?.name || ''] || modelField?.default || '',
|
|
361
|
-
);
|
|
362
|
-
setMode('field-input');
|
|
363
|
-
}
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// User chose to fetch models
|
|
368
|
-
const endpointType = selectedTemplate?.modelsEndpoint;
|
|
369
|
-
const isCloud = isCloudEndpoint(endpointType);
|
|
370
|
-
|
|
371
|
-
// For cloud providers, we need the API key; for local providers, we need baseUrl
|
|
372
|
-
if (isCloud) {
|
|
373
|
-
const apiKey = fieldAnswers.apiKey;
|
|
374
|
-
if (!apiKey || !apiKey.trim()) {
|
|
375
|
-
setFetchError('API key is required');
|
|
376
|
-
const modelFieldIndex = selectedTemplate?.fields.findIndex(
|
|
377
|
-
f => f.name === 'model',
|
|
378
|
-
);
|
|
379
|
-
if (modelFieldIndex !== undefined && modelFieldIndex >= 0) {
|
|
380
|
-
setCurrentFieldIndex(modelFieldIndex);
|
|
381
|
-
const modelField = selectedTemplate?.fields[modelFieldIndex];
|
|
382
|
-
setCurrentValue(
|
|
383
|
-
fieldAnswers[modelField?.name || ''] || modelField?.default || '',
|
|
384
|
-
);
|
|
385
|
-
setMode('field-input');
|
|
386
|
-
}
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
setMode('fetching-models');
|
|
391
|
-
setFetchError(null);
|
|
392
|
-
|
|
393
|
-
const result = await fetchCloudModels(endpointType, apiKey);
|
|
394
|
-
|
|
395
|
-
// Guard against setState after unmount
|
|
396
|
-
if (!isMountedRef.current) return;
|
|
397
|
-
|
|
398
|
-
if (result.success && result.models.length > 0) {
|
|
399
|
-
setFetchedModels(result.models);
|
|
400
|
-
setSelectedModelIds(new Set());
|
|
401
|
-
setMode('model-selection');
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// API key validation failed - go back to API key field with error
|
|
406
|
-
// This is a meaningful check: invalid keys should be fixed, not bypassed
|
|
407
|
-
const apiKeyIndex = selectedTemplate?.fields.findIndex(
|
|
408
|
-
f => f.name === 'apiKey',
|
|
409
|
-
);
|
|
410
|
-
if (apiKeyIndex !== undefined && apiKeyIndex >= 0) {
|
|
411
|
-
setCurrentFieldIndex(apiKeyIndex);
|
|
412
|
-
setCurrentValue(''); // Clear the invalid key
|
|
413
|
-
setError(result.error || 'Failed to validate API key');
|
|
414
|
-
setMode('field-input');
|
|
415
|
-
}
|
|
416
|
-
return;
|
|
417
|
-
} else {
|
|
418
|
-
// Local provider - need baseUrl
|
|
419
|
-
const baseUrl = fieldAnswers.baseUrl;
|
|
420
|
-
|
|
421
|
-
if (!baseUrl || !baseUrl.trim()) {
|
|
422
|
-
setFetchError('Base URL is required');
|
|
423
|
-
const modelFieldIndex = selectedTemplate?.fields.findIndex(
|
|
424
|
-
f => f.name === 'model',
|
|
425
|
-
);
|
|
426
|
-
if (modelFieldIndex !== undefined && modelFieldIndex >= 0) {
|
|
427
|
-
setCurrentFieldIndex(modelFieldIndex);
|
|
428
|
-
const modelField = selectedTemplate?.fields[modelFieldIndex];
|
|
429
|
-
setCurrentValue(
|
|
430
|
-
fieldAnswers[modelField?.name || ''] || modelField?.default || '',
|
|
431
|
-
);
|
|
432
|
-
setMode('field-input');
|
|
433
|
-
}
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
setMode('fetching-models');
|
|
438
|
-
setFetchError(null);
|
|
439
|
-
|
|
440
|
-
const localEndpoint = isLocalEndpoint(endpointType)
|
|
441
|
-
? endpointType
|
|
442
|
-
: 'openai-compatible';
|
|
443
|
-
const result = await fetchLocalModels(baseUrl, localEndpoint);
|
|
444
|
-
|
|
445
|
-
// Guard against setState after unmount
|
|
446
|
-
if (!isMountedRef.current) return;
|
|
447
|
-
|
|
448
|
-
if (result.success && result.models.length > 0) {
|
|
449
|
-
setFetchedModels(result.models);
|
|
450
|
-
setSelectedModelIds(new Set());
|
|
451
|
-
setMode('model-selection');
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Fetch failed - show brief error and fallback to manual input
|
|
456
|
-
setFetchError(result.error || 'Failed to fetch models');
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Both branches fall through here on failure - set up fallback timeout
|
|
460
|
-
// Clear any existing timeout before setting a new one
|
|
461
|
-
if (fallbackTimeoutRef.current) {
|
|
462
|
-
clearTimeout(fallbackTimeoutRef.current);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Capture fieldAnswers at this moment to avoid stale closure
|
|
466
|
-
const capturedFieldAnswers = { ...fieldAnswers };
|
|
467
|
-
|
|
468
|
-
// After a brief delay, go to manual input (500ms - short enough to not frustrate)
|
|
469
|
-
fallbackTimeoutRef.current = setTimeout(() => {
|
|
470
|
-
// Guard against setState after unmount
|
|
471
|
-
if (!isMountedRef.current) return;
|
|
472
|
-
|
|
473
|
-
// Use ref for template to get current value (prevents stale closure)
|
|
474
|
-
const template = currentTemplateRef.current;
|
|
475
|
-
if (!template) return;
|
|
476
|
-
|
|
477
|
-
const modelFieldIndex = template.fields.findIndex(
|
|
478
|
-
f => f.name === 'model',
|
|
479
|
-
);
|
|
480
|
-
if (modelFieldIndex !== undefined && modelFieldIndex >= 0) {
|
|
481
|
-
setCurrentFieldIndex(modelFieldIndex);
|
|
482
|
-
const modelField = template.fields[modelFieldIndex];
|
|
483
|
-
setCurrentValue(
|
|
484
|
-
capturedFieldAnswers[modelField?.name || ''] ||
|
|
485
|
-
modelField?.default ||
|
|
486
|
-
'',
|
|
487
|
-
);
|
|
488
|
-
setMode('field-input');
|
|
489
|
-
}
|
|
490
|
-
}, 500);
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
const handleModelToggle = (modelId: string) => {
|
|
494
|
-
setSelectedModelIds(prev => {
|
|
495
|
-
const newSet = new Set(prev);
|
|
496
|
-
if (newSet.has(modelId)) {
|
|
497
|
-
newSet.delete(modelId);
|
|
498
|
-
} else {
|
|
499
|
-
newSet.add(modelId);
|
|
500
|
-
}
|
|
501
|
-
return newSet;
|
|
502
|
-
});
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
const handleSelectAllModels = () => {
|
|
506
|
-
setSelectedModelIds(prev => {
|
|
507
|
-
if (prev.size === fetchedModels.length) {
|
|
508
|
-
// Deselect all
|
|
509
|
-
return new Set();
|
|
510
|
-
} else {
|
|
511
|
-
// Select all
|
|
512
|
-
return new Set(fetchedModels.map(m => m.id));
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
const handleModelSelectionComplete = () => {
|
|
518
|
-
if (selectedModelIds.size === 0) {
|
|
519
|
-
setError('Please select at least one model');
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Save selected models to fieldAnswers
|
|
524
|
-
const selectedModels = Array.from(selectedModelIds).join(', ');
|
|
525
|
-
const newAnswers: Record<string, string> = {
|
|
526
|
-
...fieldAnswers,
|
|
527
|
-
model: selectedModels,
|
|
528
|
-
};
|
|
529
|
-
setFieldAnswers(newAnswers);
|
|
530
|
-
setError(null);
|
|
531
|
-
|
|
532
|
-
// Find the model field index and continue to the next field or complete
|
|
533
|
-
if (!selectedTemplate) return;
|
|
534
|
-
|
|
535
|
-
const modelFieldIndex = selectedTemplate.fields.findIndex(
|
|
536
|
-
f => f.name === 'model',
|
|
537
|
-
);
|
|
538
|
-
|
|
539
|
-
if (modelFieldIndex < selectedTemplate.fields.length - 1) {
|
|
540
|
-
// There are more fields after model
|
|
541
|
-
setCurrentFieldIndex(modelFieldIndex + 1);
|
|
542
|
-
const nextField = selectedTemplate.fields[modelFieldIndex + 1];
|
|
543
|
-
setCurrentValue(newAnswers[nextField?.name] || nextField?.default || '');
|
|
544
|
-
setMode('field-input');
|
|
545
|
-
} else {
|
|
546
|
-
// Model was the last field - build config
|
|
547
|
-
try {
|
|
548
|
-
const providerConfig = selectedTemplate.buildConfig(newAnswers);
|
|
549
|
-
|
|
550
|
-
if (editingIndex !== null) {
|
|
551
|
-
const newProviders = [...providers];
|
|
552
|
-
newProviders[editingIndex] = providerConfig;
|
|
553
|
-
setProviders(newProviders);
|
|
554
|
-
} else {
|
|
555
|
-
setProviders([...providers, providerConfig]);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// Reset for next provider
|
|
559
|
-
setSelectedTemplate(null);
|
|
560
|
-
setCurrentFieldIndex(0);
|
|
561
|
-
setFieldAnswers({});
|
|
562
|
-
setCurrentValue('');
|
|
563
|
-
setEditingIndex(null);
|
|
564
|
-
setFetchedModels([]);
|
|
565
|
-
setSelectedModelIds(new Set());
|
|
566
|
-
setMode('template-selection');
|
|
567
|
-
} catch (err) {
|
|
568
|
-
setError(
|
|
569
|
-
err instanceof Error ? err.message : 'Failed to build configuration',
|
|
570
|
-
);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
useInput((_input, key) => {
|
|
576
|
-
// Handle Shift+Tab for going back
|
|
577
|
-
if (key.shift && key.tab) {
|
|
578
|
-
if (mode === 'field-input') {
|
|
579
|
-
// In field input mode, check if we can go back to previous field
|
|
580
|
-
if (currentFieldIndex > 0) {
|
|
581
|
-
// Go back to previous field
|
|
582
|
-
setCurrentFieldIndex(currentFieldIndex - 1);
|
|
583
|
-
const prevField = selectedTemplate?.fields[currentFieldIndex - 1];
|
|
584
|
-
setCurrentValue(
|
|
585
|
-
fieldAnswers[prevField?.name || ''] || prevField?.default || '',
|
|
586
|
-
);
|
|
587
|
-
setInputKey(prev => prev + 1); // Force remount to reset cursor position
|
|
588
|
-
setError(null);
|
|
589
|
-
} else {
|
|
590
|
-
// At first field, go back based on where we came from
|
|
591
|
-
if (editingIndex !== null) {
|
|
592
|
-
// Was editing, go back to edit-or-delete choice
|
|
593
|
-
setMode('edit-or-delete');
|
|
594
|
-
} else if (cameFromCustom) {
|
|
595
|
-
// Came from custom selection, go back to initial choice
|
|
596
|
-
setMode('select-template-or-custom');
|
|
597
|
-
} else {
|
|
598
|
-
// Came from template selection, go back there
|
|
599
|
-
setMode('template-selection');
|
|
600
|
-
}
|
|
601
|
-
setSelectedTemplate(null);
|
|
602
|
-
setCurrentFieldIndex(0);
|
|
603
|
-
setFieldAnswers({});
|
|
604
|
-
setCurrentValue('');
|
|
605
|
-
setError(null);
|
|
606
|
-
}
|
|
607
|
-
} else if (mode === 'template-selection') {
|
|
608
|
-
// In template selection, go back to initial choice
|
|
609
|
-
setMode('select-template-or-custom');
|
|
610
|
-
} else if (mode === 'edit-or-delete') {
|
|
611
|
-
// In edit-or-delete, go back to edit selection
|
|
612
|
-
setEditingIndex(null);
|
|
613
|
-
setMode('edit-selection');
|
|
614
|
-
} else if (mode === 'edit-selection') {
|
|
615
|
-
// In edit selection, go back to initial choice
|
|
616
|
-
setMode('select-template-or-custom');
|
|
617
|
-
} else if (mode === 'model-source-choice') {
|
|
618
|
-
// In model source choice, go back to the appropriate field
|
|
619
|
-
// Cloud providers: go back to apiKey; Local providers: go back to baseUrl
|
|
620
|
-
const isCloud = isCloudEndpoint(selectedTemplate?.modelsEndpoint);
|
|
621
|
-
const fieldToGoBack = isCloud ? 'apiKey' : 'baseUrl';
|
|
622
|
-
const fieldIndex = selectedTemplate?.fields.findIndex(
|
|
623
|
-
f => f.name === fieldToGoBack,
|
|
624
|
-
);
|
|
625
|
-
if (fieldIndex !== undefined && fieldIndex >= 0) {
|
|
626
|
-
setCurrentFieldIndex(fieldIndex);
|
|
627
|
-
const field = selectedTemplate?.fields[fieldIndex];
|
|
628
|
-
setCurrentValue(
|
|
629
|
-
fieldAnswers[field?.name || ''] || field?.default || '',
|
|
630
|
-
);
|
|
631
|
-
setError(null);
|
|
632
|
-
setMode('field-input');
|
|
633
|
-
}
|
|
634
|
-
} else if (mode === 'model-selection') {
|
|
635
|
-
// In model selection, go back to model source choice
|
|
636
|
-
setFetchedModels([]);
|
|
637
|
-
setSelectedModelIds(new Set());
|
|
638
|
-
setFetchError(null);
|
|
639
|
-
setError(null);
|
|
640
|
-
setMode('model-source-choice');
|
|
641
|
-
} else if (mode === 'select-template-or-custom') {
|
|
642
|
-
// At root level, call parent's onBack
|
|
643
|
-
if (onBack) {
|
|
644
|
-
onBack();
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
if (mode === 'field-input') {
|
|
651
|
-
if (key.return) {
|
|
652
|
-
handleFieldSubmit();
|
|
653
|
-
} else if (key.escape) {
|
|
654
|
-
// Go back to template selection
|
|
655
|
-
setMode('template-selection');
|
|
656
|
-
setSelectedTemplate(null);
|
|
657
|
-
setCurrentFieldIndex(0);
|
|
658
|
-
setFieldAnswers({});
|
|
659
|
-
setCurrentValue('');
|
|
660
|
-
setError(null);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
if (mode === 'model-selection') {
|
|
665
|
-
if (key.escape) {
|
|
666
|
-
// Go back to model source choice
|
|
667
|
-
setFetchedModels([]);
|
|
668
|
-
setSelectedModelIds(new Set());
|
|
669
|
-
setError(null);
|
|
670
|
-
setMode('model-source-choice');
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
if (mode === 'select-template-or-custom') {
|
|
676
|
-
return (
|
|
677
|
-
<Box flexDirection="column">
|
|
678
|
-
<Box marginBottom={1}>
|
|
679
|
-
<Text bold color={colors.primary}>
|
|
680
|
-
Let's add AI providers. Would you like to use a template?
|
|
681
|
-
</Text>
|
|
682
|
-
</Box>
|
|
683
|
-
{providers.length > 0 && (
|
|
684
|
-
<Box marginBottom={1}>
|
|
685
|
-
<Text color={colors.success}>
|
|
686
|
-
{providers.length} provider(s) already added
|
|
687
|
-
</Text>
|
|
688
|
-
</Box>
|
|
689
|
-
)}
|
|
690
|
-
<SelectInput
|
|
691
|
-
items={initialOptions}
|
|
692
|
-
onSelect={(item: { value: string }) => handleInitialSelect(item)}
|
|
693
|
-
/>
|
|
694
|
-
</Box>
|
|
695
|
-
);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
if (mode === 'template-selection') {
|
|
699
|
-
return (
|
|
700
|
-
<Box flexDirection="column">
|
|
701
|
-
<Box marginBottom={1}>
|
|
702
|
-
<Text bold color={colors.primary}>
|
|
703
|
-
Choose a provider template:
|
|
704
|
-
</Text>
|
|
705
|
-
</Box>
|
|
706
|
-
{providers.length > 0 && (
|
|
707
|
-
<Box marginBottom={1}>
|
|
708
|
-
<Text color={colors.success}>
|
|
709
|
-
Added: {providers.map(p => p.name).join(', ')}
|
|
710
|
-
</Text>
|
|
711
|
-
</Box>
|
|
712
|
-
)}
|
|
713
|
-
<SelectInput
|
|
714
|
-
items={templateOptions}
|
|
715
|
-
onSelect={(item: TemplateOption) => handleTemplateSelect(item)}
|
|
716
|
-
/>
|
|
717
|
-
</Box>
|
|
718
|
-
);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
if (mode === 'edit-selection') {
|
|
722
|
-
return (
|
|
723
|
-
<Box flexDirection="column">
|
|
724
|
-
<Box marginBottom={1}>
|
|
725
|
-
<Text bold color={colors.primary}>
|
|
726
|
-
Select a provider to edit:
|
|
727
|
-
</Text>
|
|
728
|
-
</Box>
|
|
729
|
-
<SelectInput
|
|
730
|
-
items={editOptions}
|
|
731
|
-
onSelect={(item: TemplateOption) => handleEditSelect(item)}
|
|
732
|
-
/>
|
|
733
|
-
</Box>
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if (mode === 'edit-or-delete') {
|
|
738
|
-
const provider = editingIndex !== null ? providers[editingIndex] : null;
|
|
739
|
-
const editOrDeleteOptions = [
|
|
740
|
-
{ label: 'Edit this provider', value: 'edit' },
|
|
741
|
-
{ label: 'Delete this provider', value: 'delete' },
|
|
742
|
-
];
|
|
743
|
-
|
|
744
|
-
return (
|
|
745
|
-
<Box flexDirection="column">
|
|
746
|
-
<Box marginBottom={1}>
|
|
747
|
-
<Text bold color={colors.primary}>
|
|
748
|
-
{provider?.name} - What would you like to do?
|
|
749
|
-
</Text>
|
|
750
|
-
</Box>
|
|
751
|
-
<SelectInput
|
|
752
|
-
items={editOrDeleteOptions}
|
|
753
|
-
onSelect={(item: { value: string }) => handleEditOrDeleteChoice(item)}
|
|
754
|
-
/>
|
|
755
|
-
</Box>
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
if (mode === 'field-input' && selectedTemplate) {
|
|
760
|
-
const currentField = selectedTemplate.fields[currentFieldIndex];
|
|
761
|
-
if (!currentField) return null;
|
|
762
|
-
|
|
763
|
-
return (
|
|
764
|
-
<Box flexDirection="column">
|
|
765
|
-
<Box marginBottom={1}>
|
|
766
|
-
<Text bold color={colors.primary}>
|
|
767
|
-
{selectedTemplate.name} Configuration
|
|
768
|
-
</Text>
|
|
769
|
-
<Text dimColor>
|
|
770
|
-
{' '}
|
|
771
|
-
(Field {currentFieldIndex + 1}/{selectedTemplate.fields.length})
|
|
772
|
-
</Text>
|
|
773
|
-
</Box>
|
|
774
|
-
|
|
775
|
-
<Box>
|
|
776
|
-
<Text>
|
|
777
|
-
{currentField.prompt}
|
|
778
|
-
{currentField.required && <Text color={colors.error}> *</Text>}:{' '}
|
|
779
|
-
{currentField.sensitive && '****'}
|
|
780
|
-
</Text>
|
|
781
|
-
</Box>
|
|
782
|
-
|
|
783
|
-
{!currentField.sensitive && (
|
|
784
|
-
<Box
|
|
785
|
-
marginBottom={1}
|
|
786
|
-
borderStyle="round"
|
|
787
|
-
borderColor={colors.secondary}
|
|
788
|
-
>
|
|
789
|
-
<TextInput
|
|
790
|
-
key={inputKey}
|
|
791
|
-
value={currentValue}
|
|
792
|
-
onChange={setCurrentValue}
|
|
793
|
-
onSubmit={handleFieldSubmit}
|
|
794
|
-
/>
|
|
795
|
-
</Box>
|
|
796
|
-
)}
|
|
797
|
-
|
|
798
|
-
{currentField.sensitive && (
|
|
799
|
-
<Box
|
|
800
|
-
marginBottom={1}
|
|
801
|
-
borderStyle="round"
|
|
802
|
-
borderColor={colors.secondary}
|
|
803
|
-
>
|
|
804
|
-
<TextInput
|
|
805
|
-
key={inputKey}
|
|
806
|
-
value={currentValue}
|
|
807
|
-
onChange={setCurrentValue}
|
|
808
|
-
onSubmit={handleFieldSubmit}
|
|
809
|
-
mask="*"
|
|
810
|
-
/>
|
|
811
|
-
</Box>
|
|
812
|
-
)}
|
|
813
|
-
|
|
814
|
-
{error && (
|
|
815
|
-
<Box marginBottom={1}>
|
|
816
|
-
<Text color={colors.error}>{error}</Text>
|
|
817
|
-
</Box>
|
|
818
|
-
)}
|
|
819
|
-
|
|
820
|
-
{isNarrow ? (
|
|
821
|
-
<Box flexDirection="column">
|
|
822
|
-
<Text color={colors.secondary}>Enter: continue</Text>
|
|
823
|
-
<Text color={colors.secondary}>Shift+Tab: go back</Text>
|
|
824
|
-
</Box>
|
|
825
|
-
) : (
|
|
826
|
-
<Box>
|
|
827
|
-
<Text color={colors.secondary}>
|
|
828
|
-
Press Enter to continue | Shift+Tab to go back
|
|
829
|
-
</Text>
|
|
830
|
-
</Box>
|
|
831
|
-
)}
|
|
832
|
-
</Box>
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
if (mode === 'model-source-choice' && selectedTemplate) {
|
|
837
|
-
const modelSourceOptions = [
|
|
838
|
-
{ label: 'Fetch available models from server', value: 'fetch' },
|
|
839
|
-
{ label: 'Enter model names manually', value: 'manual' },
|
|
840
|
-
];
|
|
841
|
-
|
|
842
|
-
return (
|
|
843
|
-
<Box flexDirection="column">
|
|
844
|
-
<Box marginBottom={1}>
|
|
845
|
-
<Text bold color={colors.primary}>
|
|
846
|
-
{selectedTemplate.name} Configuration
|
|
847
|
-
</Text>
|
|
848
|
-
</Box>
|
|
849
|
-
<Box marginBottom={1}>
|
|
850
|
-
<Text>How would you like to specify models?</Text>
|
|
851
|
-
</Box>
|
|
852
|
-
<SelectInput
|
|
853
|
-
items={modelSourceOptions}
|
|
854
|
-
onSelect={(item: { value: string }) => handleModelSourceChoice(item)}
|
|
855
|
-
/>
|
|
856
|
-
{isNarrow ? (
|
|
857
|
-
<Box flexDirection="column" marginTop={1}>
|
|
858
|
-
<Text color={colors.secondary}>Shift+Tab: go back</Text>
|
|
859
|
-
</Box>
|
|
860
|
-
) : (
|
|
861
|
-
<Box marginTop={1}>
|
|
862
|
-
<Text color={colors.secondary}>Shift+Tab to go back</Text>
|
|
863
|
-
</Box>
|
|
864
|
-
)}
|
|
865
|
-
</Box>
|
|
866
|
-
);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
if (mode === 'fetching-models' && selectedTemplate) {
|
|
870
|
-
return (
|
|
871
|
-
<Box flexDirection="column">
|
|
872
|
-
<Box marginBottom={1}>
|
|
873
|
-
<Text bold color={colors.primary}>
|
|
874
|
-
{selectedTemplate.name} Configuration
|
|
875
|
-
</Text>
|
|
876
|
-
</Box>
|
|
877
|
-
{fetchError ? (
|
|
878
|
-
<Box flexDirection="column">
|
|
879
|
-
<Box marginBottom={1}>
|
|
880
|
-
<Text color={colors.error}>{fetchError}</Text>
|
|
881
|
-
</Box>
|
|
882
|
-
<Text dimColor>Falling back to manual input...</Text>
|
|
883
|
-
</Box>
|
|
884
|
-
) : (
|
|
885
|
-
<Box>
|
|
886
|
-
<Text color={colors.info}>
|
|
887
|
-
<RandomSpinner /> Fetching models from{' '}
|
|
888
|
-
{fieldAnswers.baseUrl || selectedTemplate.name}...
|
|
889
|
-
</Text>
|
|
890
|
-
</Box>
|
|
891
|
-
)}
|
|
892
|
-
</Box>
|
|
893
|
-
);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
if (mode === 'model-selection' && selectedTemplate) {
|
|
897
|
-
const allSelected = selectedModelIds.size === fetchedModels.length;
|
|
898
|
-
const modelOptions = [
|
|
899
|
-
{
|
|
900
|
-
// Show checked when all selected, unchecked when not - matches visual state
|
|
901
|
-
label: allSelected
|
|
902
|
-
? '[✓] All selected (toggle to deselect)'
|
|
903
|
-
: '[ ] Select All',
|
|
904
|
-
value: '__select_all__',
|
|
905
|
-
},
|
|
906
|
-
...fetchedModels.map(m => ({
|
|
907
|
-
label: `${selectedModelIds.has(m.id) ? '[✓]' : '[ ]'} ${m.name}`,
|
|
908
|
-
value: m.id,
|
|
909
|
-
})),
|
|
910
|
-
{ label: 'Done - Continue with selected models', value: '__done__' },
|
|
911
|
-
];
|
|
912
|
-
|
|
913
|
-
return (
|
|
914
|
-
<Box flexDirection="column">
|
|
915
|
-
<Box marginBottom={1}>
|
|
916
|
-
<Text bold color={colors.primary}>
|
|
917
|
-
{selectedTemplate.name} Configuration
|
|
918
|
-
</Text>
|
|
919
|
-
</Box>
|
|
920
|
-
<Box marginBottom={1}>
|
|
921
|
-
<Text>Select models to use ({selectedModelIds.size} selected):</Text>
|
|
922
|
-
</Box>
|
|
923
|
-
<SelectInput
|
|
924
|
-
items={modelOptions}
|
|
925
|
-
onSelect={(item: { value: string }) => {
|
|
926
|
-
if (item.value === '__done__') {
|
|
927
|
-
handleModelSelectionComplete();
|
|
928
|
-
} else if (item.value === '__select_all__') {
|
|
929
|
-
handleSelectAllModels();
|
|
930
|
-
} else {
|
|
931
|
-
handleModelToggle(item.value);
|
|
932
|
-
}
|
|
933
|
-
}}
|
|
934
|
-
/>
|
|
935
|
-
{error && (
|
|
936
|
-
<Box marginTop={1}>
|
|
937
|
-
<Text color={colors.error}>{error}</Text>
|
|
938
|
-
</Box>
|
|
939
|
-
)}
|
|
940
|
-
{isNarrow ? (
|
|
941
|
-
<Box flexDirection="column" marginTop={1}>
|
|
942
|
-
<Text color={colors.secondary}>Enter: toggle/continue</Text>
|
|
943
|
-
<Text color={colors.secondary}>Shift+Tab: go back</Text>
|
|
944
|
-
</Box>
|
|
945
|
-
) : (
|
|
946
|
-
<Box marginTop={1}>
|
|
947
|
-
<Text color={colors.secondary}>
|
|
948
|
-
Press Enter to toggle | Shift+Tab to go back
|
|
949
|
-
</Text>
|
|
950
|
-
</Box>
|
|
951
|
-
)}
|
|
952
|
-
</Box>
|
|
953
|
-
);
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
return null;
|
|
957
|
-
}
|