@trops/dash-core 0.1.611 → 0.1.613

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -46051,6 +46051,55 @@ var NewProviderPicker = function NewProviderPicker(_ref2) {
46051
46051
  });
46052
46052
  };
46053
46053
 
46054
+ /**
46055
+ * providerType.js
46056
+ *
46057
+ * Helpers for assigning a stable, UNIQUE `type` to a provider.
46058
+ *
46059
+ * A provider's identity in the widget system is its `type` string (widgets
46060
+ * declare `requiredProviders: [{ type }]`, runtime resolves by type). Catalog
46061
+ * providers each carry a unique type (`slack`, `github`, …). Hand-rolled
46062
+ * custom MCP servers historically all shared `type: "custom"`, which conflates
46063
+ * multiple customs at binding/runtime. These helpers give each NEW custom
46064
+ * provider a unique slug derived from its name so it behaves like a catalog
46065
+ * provider.
46066
+ *
46067
+ * Forward-only: existing providers are never renamed here (that would break
46068
+ * widgets already built against their type). Only the create path uses this.
46069
+ */
46070
+
46071
+ /**
46072
+ * Convert a provider name into a kebab-case type slug.
46073
+ * "My Granola Server" → "my-granola-server". Falls back to a sane default
46074
+ * when the name has no slug-able characters.
46075
+ */
46076
+ function slugifyProviderType(name) {
46077
+ var slug = String(name || "").toLowerCase().trim().replace(/[^\w\s-]/g, "") // drop punctuation/symbols
46078
+ .replace(/[\s_]+/g, "-") // whitespace/underscores → hyphen
46079
+ .replace(/-+/g, "-") // collapse repeats
46080
+ .replace(/^-+|-+$/g, ""); // trim hyphens
46081
+ return slug || "custom-provider";
46082
+ }
46083
+
46084
+ /**
46085
+ * Produce a slug type unique against the already-configured provider types.
46086
+ * Appends `-2`, `-3`, … on collision so two providers can never share a type.
46087
+ *
46088
+ * @param {string} name provider instance name
46089
+ * @param {Iterable<string>} existingTypes types already in use (e.g.
46090
+ * Object.values(providers).map(p => p.type))
46091
+ * @returns {string}
46092
+ */
46093
+ function uniqueProviderType(name) {
46094
+ var existingTypes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
46095
+ var base = slugifyProviderType(name);
46096
+ var taken = new Set(existingTypes || []);
46097
+ if (!taken.has(base)) return base;
46098
+ var i = 2;
46099
+ while (taken.has("".concat(base, "-").concat(i))) i++;
46100
+ return "".concat(base, "-").concat(i);
46101
+ }
46102
+
46054
46103
  var ProvidersSection = function ProvidersSection(_ref) {
46055
46104
  var _ref$dashApi = _ref.dashApi,
46056
46105
  dashApi = _ref$dashApi === void 0 ? null : _ref$dashApi,
@@ -46275,8 +46324,21 @@ var ProvidersSection = function ProvidersSection(_ref) {
46275
46324
  function handleMcpSave(providerName, providerType, mcpCredentials, mcpConfig) {
46276
46325
  var allowedTools = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
46277
46326
  if (!dashApi || !appId) return;
46327
+ // Forward-only unique types: a hand-rolled custom MCP server arrives with
46328
+ // the generic default type "custom", which conflates multiple customs at
46329
+ // widget-binding/runtime time. Give each NEW one a unique slug so it has a
46330
+ // distinct identity like catalog providers. Catalog installs pass a real
46331
+ // id (not "custom") and are untouched; the edit path preserves type, so no
46332
+ // existing provider is ever renamed.
46333
+ var resolvedType = providerType;
46334
+ if (resolvedType === "custom") {
46335
+ var existingTypes = Object.values(providers || {}).map(function (p) {
46336
+ return p.type;
46337
+ });
46338
+ resolvedType = uniqueProviderType(providerName, existingTypes);
46339
+ }
46278
46340
  dashApi.saveProvider(appId, providerName, {
46279
- providerType: providerType,
46341
+ providerType: resolvedType,
46280
46342
  credentials: mcpCredentials,
46281
46343
  providerClass: "mcp",
46282
46344
  mcpConfig: mcpConfig,
@@ -52196,18 +52258,35 @@ var McpServerSection = function McpServerSection() {
52196
52258
  });
52197
52259
  };
52198
52260
 
52261
+ /**
52262
+ * modelMigration.js (renderer mirror)
52263
+ *
52264
+ * Mirrors the retired-model map + default from the main-process provider
52265
+ * registry (electron/llm/modelProviders.js) so the Settings UI can detect a
52266
+ * stale saved selection, auto-migrate it, and show a notice — without an IPC
52267
+ * round-trip. KEEP IN SYNC with modelProviders.js (anthropic provider).
52268
+ */
52269
+ var DEFAULT_MODEL = "claude-opus-4-8";
52270
+ var RETIRED_MODEL_MAP = {
52271
+ "claude-sonnet-4-20250514": "claude-sonnet-4-6",
52272
+ "claude-opus-4-20250514": "claude-opus-4-8",
52273
+ "claude-opus-4-1-20250805": "claude-opus-4-8",
52274
+ "claude-3-7-sonnet-20250219": "claude-sonnet-4-6",
52275
+ "claude-3-5-sonnet-20241022": "claude-sonnet-4-6",
52276
+ "claude-3-5-sonnet-20240620": "claude-sonnet-4-6",
52277
+ "claude-3-5-haiku-20241022": "claude-haiku-4-5",
52278
+ "claude-3-opus-20240229": "claude-opus-4-8"
52279
+ };
52280
+ function isRetiredModel(id) {
52281
+ return !!RETIRED_MODEL_MAP[id];
52282
+ }
52283
+ function migrateModelId(id) {
52284
+ if (!id) return DEFAULT_MODEL;
52285
+ return RETIRED_MODEL_MAP[id] || id;
52286
+ }
52287
+
52199
52288
  function ownKeys$m(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
52200
52289
  function _objectSpread$m(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$m(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$m(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
52201
- var MODELS = [{
52202
- value: "claude-sonnet-4-20250514",
52203
- label: "Claude Sonnet 4"
52204
- }, {
52205
- value: "claude-opus-4-20250514",
52206
- label: "Claude Opus 4"
52207
- }, {
52208
- value: "claude-haiku-4-5-20251001",
52209
- label: "Claude Haiku 4.5"
52210
- }];
52211
52290
  var BACKENDS = [{
52212
52291
  value: "claude-code",
52213
52292
  label: "Claude Code CLI"
@@ -52216,6 +52295,7 @@ var BACKENDS = [{
52216
52295
  label: "Anthropic API"
52217
52296
  }];
52218
52297
  var AiAssistantSection = function AiAssistantSection() {
52298
+ var _anthropicProvider$;
52219
52299
  var appContext = React.useContext(AppContext);
52220
52300
  var settings = (appContext === null || appContext === void 0 ? void 0 : appContext.settings) || {};
52221
52301
  var providers = (appContext === null || appContext === void 0 ? void 0 : appContext.providers) || {};
@@ -52233,7 +52313,7 @@ var AiAssistantSection = function AiAssistantSection() {
52233
52313
  _useState4 = _slicedToArray(_useState3, 2),
52234
52314
  preferredBackend = _useState4[0],
52235
52315
  setPreferredBackend = _useState4[1];
52236
- var _useState5 = React.useState(aiSettings.model || "claude-sonnet-4-20250514"),
52316
+ var _useState5 = React.useState(aiSettings.model || DEFAULT_MODEL),
52237
52317
  _useState6 = _slicedToArray(_useState5, 2),
52238
52318
  selectedModel = _useState6[0],
52239
52319
  setSelectedModel = _useState6[1];
@@ -52250,6 +52330,21 @@ var AiAssistantSection = function AiAssistantSection() {
52250
52330
  saveKeyStatus = _useState10[0],
52251
52331
  setSaveKeyStatus = _useState10[1];
52252
52332
 
52333
+ // Provider-pluggable model discovery (live fetch + curated fallback)
52334
+ var _useState11 = React.useState([]),
52335
+ _useState12 = _slicedToArray(_useState11, 2),
52336
+ availableModels = _useState12[0],
52337
+ setAvailableModels = _useState12[1];
52338
+ var _useState13 = React.useState(false),
52339
+ _useState14 = _slicedToArray(_useState13, 2),
52340
+ modelsLoading = _useState14[0],
52341
+ setModelsLoading = _useState14[1];
52342
+ // One-time notice when a retired saved model was auto-migrated
52343
+ var _useState15 = React.useState(null),
52344
+ _useState16 = _slicedToArray(_useState15, 2),
52345
+ migrationNotice = _useState16[0],
52346
+ setMigrationNotice = _useState16[1];
52347
+
52253
52348
  // Find existing Anthropic provider
52254
52349
  var anthropicProvider = Object.entries(providers).find(function (_ref) {
52255
52350
  var _ref2 = _slicedToArray(_ref, 2),
@@ -52258,6 +52353,9 @@ var AiAssistantSection = function AiAssistantSection() {
52258
52353
  });
52259
52354
  var hasAnthropicProvider = !!anthropicProvider;
52260
52355
  var anthropicProviderName = anthropicProvider === null || anthropicProvider === void 0 ? void 0 : anthropicProvider[0];
52356
+ // Decrypted key (when AppContext exposes it) enables the live Models API
52357
+ // fetch; without it, listModels returns the curated fallback list.
52358
+ var anthropicApiKey = (anthropicProvider === null || anthropicProvider === void 0 || (_anthropicProvider$ = anthropicProvider[1]) === null || _anthropicProvider$ === void 0 || (_anthropicProvider$ = _anthropicProvider$.credentials) === null || _anthropicProvider$ === void 0 ? void 0 : _anthropicProvider$.apiKey) || null;
52261
52359
 
52262
52360
  // Check CLI availability on mount
52263
52361
  React.useEffect(function () {
@@ -52279,6 +52377,28 @@ var AiAssistantSection = function AiAssistantSection() {
52279
52377
  }
52280
52378
  }, []);
52281
52379
 
52380
+ // Fetch selectable models for the current provider. With an API key the
52381
+ // main process does a live Anthropic Models API fetch; otherwise it returns
52382
+ // the curated fallback. Provider is "anthropic" for both current backends.
52383
+ var fetchModels = React.useCallback(function () {
52384
+ var _window$mainApi2;
52385
+ if (!((_window$mainApi2 = window.mainApi) !== null && _window$mainApi2 !== void 0 && (_window$mainApi2 = _window$mainApi2.llm) !== null && _window$mainApi2 !== void 0 && _window$mainApi2.listModels)) return;
52386
+ setModelsLoading(true);
52387
+ window.mainApi.llm.listModels("anthropic", anthropicApiKey).then(function (res) {
52388
+ return setAvailableModels(res && res.models || []);
52389
+ })["catch"](function () {
52390
+ return setAvailableModels([]);
52391
+ })["finally"](function () {
52392
+ return setModelsLoading(false);
52393
+ });
52394
+ }, [anthropicApiKey]);
52395
+
52396
+ // Load models on mount and whenever the available API key changes (so the
52397
+ // list upgrades from curated → live once a key is configured).
52398
+ React.useEffect(function () {
52399
+ fetchModels();
52400
+ }, [fetchModels]);
52401
+
52282
52402
  // Save AI settings to app settings
52283
52403
  var saveAiSettings = React.useCallback(function (updates) {
52284
52404
  if (appContext !== null && appContext !== void 0 && appContext.changeSettings) {
@@ -52289,6 +52409,24 @@ var AiAssistantSection = function AiAssistantSection() {
52289
52409
  }));
52290
52410
  }
52291
52411
  }, [appContext]);
52412
+
52413
+ // Auto-migrate a retired saved model to its current replacement (one-time,
52414
+ // on mount) so an old selection can't 404 the assistant. Shows a notice.
52415
+ React.useEffect(function () {
52416
+ var saved = aiSettings.model;
52417
+ if (saved && isRetiredModel(saved)) {
52418
+ var replacement = migrateModelId(saved);
52419
+ setSelectedModel(replacement);
52420
+ saveAiSettings({
52421
+ model: replacement
52422
+ });
52423
+ setMigrationNotice({
52424
+ from: saved,
52425
+ to: replacement
52426
+ });
52427
+ }
52428
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52429
+ }, []);
52292
52430
  function handleBackendChange(e) {
52293
52431
  var value = e.target.value;
52294
52432
  setPreferredBackend(value);
@@ -52332,6 +52470,15 @@ var AiAssistantSection = function AiAssistantSection() {
52332
52470
  });
52333
52471
  }
52334
52472
 
52473
+ // Model dropdown options — keep the current selection selectable even if it
52474
+ // isn't in the fetched list (e.g. a pinned/custom id).
52475
+ var modelOptions = selectedModel && !availableModels.some(function (m) {
52476
+ return m.value === selectedModel;
52477
+ }) ? [{
52478
+ value: selectedModel,
52479
+ label: selectedModel
52480
+ }].concat(_toConsumableArray(availableModels)) : availableModels;
52481
+
52335
52482
  // Determine effective backend (what will actually be used)
52336
52483
  var effectiveBackend = preferredBackend === "claude-code" && cliStatus !== null && cliStatus !== void 0 && cliStatus.available ? "claude-code" : preferredBackend === "anthropic" && hasAnthropicProvider ? "anthropic" : cliStatus !== null && cliStatus !== void 0 && cliStatus.available ? "claude-code" : hasAnthropicProvider ? "anthropic" : null;
52337
52484
  return /*#__PURE__*/jsxRuntime.jsxs("div", {
@@ -52428,6 +52575,18 @@ var AiAssistantSection = function AiAssistantSection() {
52428
52575
  }, b.value);
52429
52576
  })
52430
52577
  })]
52578
+ }), migrationNotice && /*#__PURE__*/jsxRuntime.jsxs("div", {
52579
+ className: "flex items-start gap-2 rounded-lg bg-yellow-900/30 border border-yellow-700 px-3 py-2 text-xs text-yellow-300",
52580
+ children: [/*#__PURE__*/jsxRuntime.jsx(DashReact.FontAwesomeIcon, {
52581
+ icon: "exclamation-triangle",
52582
+ className: "h-3.5 w-3.5 mt-0.5"
52583
+ }), /*#__PURE__*/jsxRuntime.jsxs("span", {
52584
+ children: ["Your previously selected model (", /*#__PURE__*/jsxRuntime.jsx("code", {
52585
+ children: migrationNotice.from
52586
+ }), ") is no longer available. Switched to ", /*#__PURE__*/jsxRuntime.jsx("code", {
52587
+ children: migrationNotice.to
52588
+ }), "."]
52589
+ })]
52431
52590
  }), /*#__PURE__*/jsxRuntime.jsxs("div", {
52432
52591
  className: "flex flex-row items-center justify-between py-1",
52433
52592
  children: [/*#__PURE__*/jsxRuntime.jsxs("div", {
@@ -52437,18 +52596,31 @@ var AiAssistantSection = function AiAssistantSection() {
52437
52596
  children: "Model"
52438
52597
  }), /*#__PURE__*/jsxRuntime.jsx("span", {
52439
52598
  className: "text-xs opacity-50",
52440
- children: "Claude model for AI Assistant conversations"
52599
+ children: "Model for AI Assistant conversations"
52600
+ })]
52601
+ }), /*#__PURE__*/jsxRuntime.jsxs("div", {
52602
+ className: "flex items-center gap-2",
52603
+ children: [/*#__PURE__*/jsxRuntime.jsx("select", {
52604
+ value: selectedModel,
52605
+ onChange: handleModelChange,
52606
+ className: "bg-white/10 border border-white/10 rounded-md px-3 py-1.5 text-sm text-gray-200 focus:outline-none focus:ring-1 focus:ring-blue-500",
52607
+ children: modelOptions.map(function (m) {
52608
+ return /*#__PURE__*/jsxRuntime.jsx("option", {
52609
+ value: m.value,
52610
+ children: m.label
52611
+ }, m.value);
52612
+ })
52613
+ }), /*#__PURE__*/jsxRuntime.jsx("button", {
52614
+ type: "button",
52615
+ onClick: fetchModels,
52616
+ disabled: modelsLoading,
52617
+ title: "Refresh available models",
52618
+ className: "text-gray-400 hover:text-gray-200 disabled:opacity-40 transition-colors p-1.5",
52619
+ children: /*#__PURE__*/jsxRuntime.jsx(DashReact.FontAwesomeIcon, {
52620
+ icon: "rotate",
52621
+ className: "h-3.5 w-3.5 ".concat(modelsLoading ? "animate-spin" : "")
52622
+ })
52441
52623
  })]
52442
- }), /*#__PURE__*/jsxRuntime.jsx("select", {
52443
- value: selectedModel,
52444
- onChange: handleModelChange,
52445
- className: "bg-white/10 border border-white/10 rounded-md px-3 py-1.5 text-sm text-gray-200 focus:outline-none focus:ring-1 focus:ring-blue-500",
52446
- children: MODELS.map(function (m) {
52447
- return /*#__PURE__*/jsxRuntime.jsx("option", {
52448
- value: m.value,
52449
- children: m.label
52450
- }, m.value);
52451
- })
52452
52624
  })]
52453
52625
  })]
52454
52626
  }), !hasAnthropicProvider && /*#__PURE__*/jsxRuntime.jsxs("div", {