larkway 0.3.33 → 0.3.35

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.
@@ -797,6 +797,108 @@ function lkHexA(hex, alpha) {
797
797
  return `rgba(${r},${g},${b},${alpha})`;
798
798
  }
799
799
 
800
+ // ---------------------------------------------------------------------------
801
+ // Model / Effort 覆盖(perf plan 批 C 旋钮)—— 挂在底座卡片下方,因为可选的
802
+ // model 建议项直接取决于选了哪个底座。
803
+ // ---------------------------------------------------------------------------
804
+
805
+ /**
806
+ * model 输入的建议项(datalist,非枚举)。按底座区分,因为两家模型名字完全不同:
807
+ * claude 是 --model 接受的别名;codex 是 OpenAI 模型 slug(取自本机
808
+ * ~/.codex/models_cache.json 当前可见模型,按 priority 升序的前三个:
809
+ * gpt-5.5(默认/最强)· gpt-5.4-mini(更快更省)· gpt-5.3-codex(编程特化),
810
+ * 不是瞎猜的名字)。input 本身允许自由输入任意值 —— 两家模型都在演进,建议项
811
+ * 只是省得手打,不是白名单。
812
+ * @param {string} backendId
813
+ * @returns {Array<[string,string]>} [value, label][]
814
+ */
815
+ function modelSuggestionsForBackend(backendId) {
816
+ if (backendId === "codex") {
817
+ return [
818
+ ["gpt-5.5", "gpt-5.5 · 当前默认,最强"],
819
+ ["gpt-5.4-mini", "gpt-5.4-mini · 更快更省"],
820
+ ["gpt-5.3-codex", "gpt-5.3-codex · 编程特化"],
821
+ ];
822
+ }
823
+ return [
824
+ ["fable", "fable"],
825
+ ["opus", "opus"],
826
+ ["sonnet", "sonnet"],
827
+ ];
828
+ }
829
+
830
+ /**
831
+ * effort <select> 的 <option> HTML。两家底座共用同一套 value(low/medium/high/max)
832
+ * ——claude 直接透传给 --effort;codex 经 codexEffortFromLarkway 映射成它自己的
833
+ * low/medium/high/xhigh(即官方 Codex 客户端 Reasoning 选择器里的
834
+ * Light/Medium/High/Extra High,已用 model/list 实测确认)。value 保持一致只是
835
+ * 为了 larkway 配置层统一,label 按底座换成对应措辞,避免用户以为 codex 也有个
836
+ * 叫 "max" 的档位。
837
+ * @param {string} effortVal
838
+ * @param {string} backendId
839
+ */
840
+ function effortOptionsHTML(effortVal, backendId) {
841
+ const labels = backendId === "codex"
842
+ ? { low: "low (Light)", medium: "medium (Medium)", high: "high (High)", max: "max (Extra High)" }
843
+ : { low: "low", medium: "medium", high: "high", max: "max" };
844
+ return [
845
+ ["", "默认(不覆盖)"],
846
+ ["low", labels.low],
847
+ ["medium", labels.medium],
848
+ ["high", labels.high],
849
+ ["max", labels.max],
850
+ ].map(([v, label]) => `<option value="${esc(v)}"${v === effortVal ? " selected" : ""}>${esc(label)}</option>`).join("");
851
+ }
852
+
853
+ /**
854
+ * Model / Effort 覆盖块的 HTML —— 嵌进「用哪个底座驱动它」卡片(lk-bk-card),
855
+ * 紧跟在 lkBackendSelectHTML(...) 后面。只在编辑态渲染(调用方 buildDetailHTML
856
+ * 只服务已存在的 bot);新建流程走 /api/onboard/finalize,那条路径尚不转发
857
+ * 这两个字段,建完再来编辑页配即可。
858
+ * @param {{model?:string, effort?:string, backend?:string}} bot
859
+ */
860
+ function buildModelEffortHTML(bot) {
861
+ const modelVal = bot.model ?? "";
862
+ const effortVal = bot.effort ?? "";
863
+ const backendId = bot.backend || LK_BACKEND_DEFAULT;
864
+ const modelListHTML = modelSuggestionsForBackend(backendId)
865
+ .map(([v, label]) => `<option value="${esc(v)}">${esc(label)}</option>`)
866
+ .join("");
867
+ return (
868
+ `<div class="ac-field" id="lk-bk-model-effort" style="margin-top:14px;padding-top:14px;border-top:1px solid var(--border)">` +
869
+ `<label class="ac-label" for="ac-model">Model / Effort 覆盖 <span class="ac-optional">可选</span></label>` +
870
+ `<p class="ac-hint">不覆盖 = 用账号默认;降低 effort 会牺牲判断深度。</p>` +
871
+ `<div style="display:flex;gap:12px;flex-wrap:wrap;align-items:center">` +
872
+ `<input list="ac-model-list" id="ac-model" name="model" class="ac-input" style="width:210px" value="${esc(modelVal)}" placeholder="默认(不覆盖)" autocomplete="off" />` +
873
+ `<datalist id="ac-model-list">${modelListHTML}</datalist>` +
874
+ `<span id="ac-effort-field">` +
875
+ `<select id="ac-effort" name="effort" class="ac-input" style="width:150px">${effortOptionsHTML(effortVal, backendId)}</select>` +
876
+ `</span>` +
877
+ `</div>` +
878
+ `</div>`
879
+ );
880
+ }
881
+
882
+ /**
883
+ * 底座切换时实时刷新 model 建议项 + effort 选项的措辞。effort 两家底座都生效
884
+ * (见 effortOptionsHTML 顶部注释),这里只换 label,不再隐藏控件。
885
+ * @param {HTMLElement} panel
886
+ * @param {string} backendId
887
+ */
888
+ function refreshModelEffortForBackend(panel, backendId) {
889
+ const datalist = panel.querySelector("#ac-model-list");
890
+ if (datalist) {
891
+ datalist.innerHTML = modelSuggestionsForBackend(backendId)
892
+ .map(([v, label]) => `<option value="${esc(v)}">${esc(label)}</option>`)
893
+ .join("");
894
+ }
895
+ const effortSelect = panel.querySelector("#ac-effort");
896
+ if (effortSelect) {
897
+ const currentVal = effortSelect.value;
898
+ effortSelect.innerHTML = effortOptionsHTML(currentVal, backendId);
899
+ }
900
+ }
901
+
800
902
  // ---------------------------------------------------------------------------
801
903
  // BL-17: 运行态 vs 配置态底座不一致 badge
802
904
  // ---------------------------------------------------------------------------
@@ -2826,6 +2928,14 @@ function readAgentConfigValues(panel) {
2826
2928
  turn_taking_limit: turnLimit,
2827
2929
  _memContent: memContent, // 带出,由调用方分别 PUT /api/memory
2828
2930
  };
2931
+ // model/effort:控件挂在底座卡片(lk-bk-card)里,在 #ac-panel 之外,所以从
2932
+ // panel(而非 ac)查找;仅编辑态渲染(create 模式下查不到,值为 undefined 时
2933
+ // 不带字段)。带 "" 是有意的 ——「默认(不覆盖)」选项要让后端把已有覆盖值删掉,
2934
+ // 不是不发这个字段(不发 = 后端保留原值,详见 api.ts putBot 的合并逻辑)。
2935
+ const modelInput = panel.querySelector("#ac-model");
2936
+ const effortSel = panel.querySelector("#ac-effort");
2937
+ if (modelInput) config.model = modelInput.value.trim();
2938
+ if (effortSel) config.effort = effortSel.value;
2829
2939
  if (botId) config.id = botId;
2830
2940
  // ① 新保存契约:_gitlabTokenValue === undefined 表示「不带字段」(保留现有)
2831
2941
  if (_gitlabTokenValue !== undefined) config.gitlab_token_value = _gitlabTokenValue;
@@ -3226,6 +3336,7 @@ function buildDetailHTML(id, bot, memContent) {
3226
3336
  `<h4 class="lk-bk-card-title">${ICONS.box} 用哪个底座驱动它</h4>` +
3227
3337
  `<p class="lk-bk-card-desc">底座决定这个助手背后跑哪个 CLI agent。默认 Codex;切换后需重启服务生效。</p>` +
3228
3338
  lkBackendSelectHTML(_configuredBk4, `bk-edit-${id}`) +
3339
+ buildModelEffortHTML(bot) +
3229
3340
  bkMismatchDetail +
3230
3341
  `</div>`;
3231
3342
 
@@ -3414,8 +3525,18 @@ function wireDetailEvents(panel, id, bot) {
3414
3525
  const rosterChipEl = rosterLi.querySelector(".roster-state > span[style*='margin-left:auto']");
3415
3526
  if (rosterChipEl) rosterChipEl.innerHTML = lkBackendChipHTML(newId, { size: "sm" });
3416
3527
  }
3528
+ // model 建议项(claude/codex 模型完全不是一回事)+ effort 选项措辞(两家
3529
+ // 底座都生效,只是值域映射不同)随底座即时切换,不用等保存。
3530
+ refreshModelEffortForBackend(panel, newId);
3417
3531
  toast(`底座已切换为 ${lkBackend(newId).name}(保存后生效)`, "info");
3418
3532
  });
3533
+
3534
+ // model(input)/effort(select)挂在 lk-bk-card 里、在 #ac-panel 之外 ——
3535
+ // #ac-panel 的 input 脏检测监听器(见上)碰不到它们,这里单独接一份。
3536
+ bkCard.addEventListener("input", () => {
3537
+ state.formDirty = acFormSnapshot(panel) !== panel._acBaseline;
3538
+ updateFormDirty(panel);
3539
+ });
3419
3540
  }
3420
3541
 
3421
3542
  // 详情 hero 删除按钮
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.33",
3
+ "version": "0.3.35",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",