openclacky 1.5.0 → 1.5.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.
@@ -139,6 +139,67 @@ const NewSessionView = (() => {
139
139
 
140
140
  _updatePlaceholder();
141
141
  _renderAgentPanels();
142
+ _renderStarterPrompts();
143
+ }
144
+
145
+ // ── Starter prompts ────────────────────────────────────────────────────
146
+ // Hardcoded UI hints per agent id. These are purely presentational and
147
+ // help users discover what to ask without needing to think from scratch.
148
+ const STARTER_PROMPTS = {
149
+ "ext-developer": [
150
+ {
151
+ en: "Help me build a Xiaohongshu content publishing extension, with a custom panel in the right sidebar",
152
+ zh: "帮我开发一个小红书内容发布管理扩展,在会话右侧边栏添加一个自定义面板",
153
+ },
154
+ {
155
+ en: "I want to build a GitHub PR review assistant extension that auto-analyzes code changes, with an entry in the bottom-left sidebar",
156
+ zh: "我想做一个 GitHub PR 审查助手扩展,自动分析代码变更,入口放在左底部侧边栏",
157
+ },
158
+ {
159
+ en: "Help me build a Pomodoro + task tracking extension with a left sidebar entry and custom Agent",
160
+ zh: "帮我开发一个番茄钟 + 任务追踪扩展,带左侧边栏访问入口和自定义 Agent",
161
+ },
162
+ ],
163
+ };
164
+
165
+ function _renderStarterPrompts() {
166
+ const container = $("new-session-starter-prompts");
167
+ if (!container) return;
168
+ const agent = NewSessionStore.currentAgent();
169
+ const prompts = agent && STARTER_PROMPTS[agent.id];
170
+ if (!prompts || !prompts.length) {
171
+ container.style.display = "none";
172
+ container.replaceChildren();
173
+ return;
174
+ }
175
+
176
+ const zh = _isZh();
177
+ container.replaceChildren();
178
+
179
+ const label = document.createElement("div");
180
+ label.className = "ns-starter-label";
181
+ label.textContent = I18n.t("sessions.new.tryAsking");
182
+ container.appendChild(label);
183
+
184
+ const list = document.createElement("div");
185
+ list.className = "ns-starter-list";
186
+ prompts.forEach((p) => {
187
+ const btn = document.createElement("button");
188
+ btn.type = "button";
189
+ btn.className = "ns-starter-item";
190
+ const text = zh ? p.zh : p.en;
191
+ btn.textContent = "\u201c" + text + "\u201d";
192
+ btn.addEventListener("click", () => {
193
+ const input = $("new-session-input");
194
+ if (!input) return;
195
+ input.value = zh ? p.zh : p.en;
196
+ input.focus();
197
+ _updateSendButton();
198
+ });
199
+ list.appendChild(btn);
200
+ });
201
+ container.appendChild(list);
202
+ container.style.display = "block";
142
203
  }
143
204
 
144
205
  function _renderAgentPanels() {
@@ -33,6 +33,7 @@ const I18n = (() => {
33
33
  "sessions.new.subtitle": "Pick who you want to chat with, then type your first message.",
34
34
  "sessions.new.placeholder": "Type your first message to {{agent}}…",
35
35
  "sessions.new.moreOptions": "More options",
36
+ "sessions.new.tryAsking": "Try asking",
36
37
  "sessions.new.send": "Send",
37
38
 
38
39
  // ── Chat panel ──
@@ -666,6 +667,8 @@ const I18n = (() => {
666
667
  "settings.models.baseurl.variant.ark_payg": "Pay-as-you-go",
667
668
  "settings.models.baseurl.variant.ark_coding": "Coding Plan",
668
669
  "settings.models.baseurl.variant.ark_agent": "Agent Plan",
670
+ "settings.models.baseurl.variant.openclacky_primary": "Primary (Global)",
671
+ "settings.models.baseurl.variant.openclacky_secondary": "Secondary (China)",
669
672
  "settings.models.btn.save": "Save",
670
673
  "settings.models.btn.saving": "Saving…",
671
674
  "settings.models.btn.saved": "Saved ✓",
@@ -753,6 +756,7 @@ const I18n = (() => {
753
756
  "settings.brand.badge.active": "Active",
754
757
  "settings.brand.badge.warning": "Expiring Soon",
755
758
  "settings.brand.badge.expired": "Expired",
759
+ "settings.brand.badge.unreachable": "Connection Issue",
756
760
  "settings.brand.desc": "Have a serial number from a brand partner? Enter it below to activate branded mode.",
757
761
  "settings.brand.descNamed": "Enter your {{name}} serial number to activate branded mode.",
758
762
  "settings.brand.btn.getSerial": "Get a Serial Number",
@@ -1004,6 +1008,7 @@ const I18n = (() => {
1004
1008
  "billing.headerHit": "Hit",
1005
1009
  "billing.headerMiss": "Miss",
1006
1010
  "billing.headerOutput": "Output",
1011
+ "billing.disclaimer": "Costs are estimated based on token usage and may differ from your provider's actual bill.",
1007
1012
  },
1008
1013
  zh: {
1009
1014
  // ── Sidebar ──
@@ -1026,6 +1031,7 @@ const I18n = (() => {
1026
1031
  "sessions.new.subtitle": "选一个 Agent,然后输入第一条消息。",
1027
1032
  "sessions.new.placeholder": "输入第一条消息发给 {{agent}}…",
1028
1033
  "sessions.new.moreOptions": "更多选项",
1034
+ "sessions.new.tryAsking": "试试这样问我",
1029
1035
  "sessions.new.send": "发送",
1030
1036
 
1031
1037
  // ── Chat panel ──
@@ -1663,6 +1669,8 @@ const I18n = (() => {
1663
1669
  "settings.models.baseurl.variant.ark_payg": "按量付费",
1664
1670
  "settings.models.baseurl.variant.ark_coding": "Coding Plan",
1665
1671
  "settings.models.baseurl.variant.ark_agent": "Agent Plan",
1672
+ "settings.models.baseurl.variant.openclacky_primary": "主节点(全球)",
1673
+ "settings.models.baseurl.variant.openclacky_secondary": "备用节点(中国)",
1666
1674
  "settings.models.btn.save": "保存",
1667
1675
  "settings.models.btn.saving": "保存中…",
1668
1676
  "settings.models.btn.saved": "已保存 ✓",
@@ -1750,6 +1758,7 @@ const I18n = (() => {
1750
1758
  "settings.brand.badge.active": "已激活",
1751
1759
  "settings.brand.badge.warning": "即将过期",
1752
1760
  "settings.brand.badge.expired": "已过期",
1761
+ "settings.brand.badge.unreachable": "连接异常",
1753
1762
  "settings.brand.desc": "有品牌合作伙伴的序列号?在下方输入以激活品牌模式。",
1754
1763
  "settings.brand.descNamed": "请输入 {{name}} 的序列号以激活品牌模式。",
1755
1764
  "settings.brand.btn.getSerial": "获取序列号",
@@ -2001,6 +2010,7 @@ const I18n = (() => {
2001
2010
  "billing.headerHit": "命中",
2002
2011
  "billing.headerMiss": "未命中",
2003
2012
  "billing.headerOutput": "输出",
2013
+ "billing.disclaimer": "费用为估算值,基于 Token 用量和模型定价计算,可能与 API 提供商实际账单有所差异。",
2004
2014
  }
2005
2015
  };
2006
2016
  // ── State ──────────────────────────────────────────────────────────────────
@@ -335,6 +335,9 @@
335
335
 
336
336
  <div id="new-session-agents" class="new-session-agents"></div>
337
337
 
338
+ <!-- Starter prompts shown when selected agent provides suggestions -->
339
+ <div id="new-session-starter-prompts" class="new-session-starter-prompts" style="display:none"></div>
340
+
338
341
  <!-- Panels the selected agent exposes (filled when an agent is picked) -->
339
342
  <div id="new-session-agent-panels" class="new-session-agent-panels" style="display:none"></div>
340
343
 
@@ -66,7 +66,11 @@ const Settings = (() => {
66
66
  }
67
67
 
68
68
  function _getProviderName(model) {
69
- const p = _findProviderByBaseUrl(model.base_url);
69
+ // Prefer the saved provider_id (survives custom base_url edits) over
70
+ // a reverse base_url lookup so the card always shows what the user picked.
71
+ const p = (model.provider_id
72
+ ? _providers.find(pr => pr.id === model.provider_id) || null
73
+ : null) || _findProviderByBaseUrl(model.base_url);
70
74
  return _providerDisplayName(p);
71
75
  }
72
76
 
@@ -87,7 +91,11 @@ const Settings = (() => {
87
91
  function _renderCard(container, model, index) {
88
92
  const isDefault = model.type === "default";
89
93
  const isLite = model.type === "lite";
90
- const provider = _findProviderByBaseUrl(model.base_url);
94
+ // Prefer saved provider_id over base_url reverse-lookup so the card
95
+ // shows the correct provider even when the user has customised base_url.
96
+ const provider = (model.provider_id
97
+ ? _providers.find(p => p.id === model.provider_id) || null
98
+ : null) || _findProviderByBaseUrl(model.base_url);
91
99
  const providerName = _providerDisplayName(provider);
92
100
  const websiteUrl = provider && provider.website_url;
93
101
  const displayName = model.model || I18n.t("settings.models.unnamed");
@@ -190,12 +198,21 @@ const Settings = (() => {
190
198
  setDefaultCb.checked = isOnlyModel ? true : (model.type === "default");
191
199
  setDefaultCb.disabled = isOnlyModel;
192
200
 
193
- // Set provider dropdown value
201
+ // Set provider dropdown value.
202
+ // Priority: 1) saved provider_id on the model record (explicit user choice,
203
+ // survives base_url edits); 2) reverse-lookup from base_url (legacy records
204
+ // without provider_id); 3) anthropic_format flag (self-hosted proxy hint).
194
205
  const matched = _findProviderByBaseUrl(model.base_url);
195
- // Preserve an explicit anthropic_format=true even if base_url is custom:
196
- // the user may have configured a self-hosted Anthropic-compatible proxy.
197
- _modalSelectedProviderId = matched ? matched.id : (model.anthropic_format ? "anthropic" : null);
198
- const providerName = matched ? matched.name : I18n.t("settings.models.provider.custom");
206
+ const savedProviderId = model.provider_id || null;
207
+ const resolvedProviderId = savedProviderId ||
208
+ (matched ? matched.id : (model.anthropic_format ? "anthropic" : null));
209
+ _modalSelectedProviderId = resolvedProviderId;
210
+ const resolvedProvider = resolvedProviderId
211
+ ? _providers.find(p => p.id === resolvedProviderId) || null
212
+ : null;
213
+ const providerName = resolvedProvider
214
+ ? _providerDisplayName(resolvedProvider)
215
+ : I18n.t("settings.models.provider.custom");
199
216
  const providerValue = document.getElementById("model-modal-provider-value");
200
217
  providerValue.textContent = providerName;
201
218
  providerValue.classList.remove("placeholder");
@@ -398,7 +415,7 @@ const Settings = (() => {
398
415
 
399
416
  const hasId = !!existingId;
400
417
 
401
- const payload = { model, base_url, anthropic_format };
418
+ const payload = { model, base_url, anthropic_format, provider_id: _modalSelectedProviderId || null };
402
419
  const setDefault = document.getElementById("model-modal-set-default").checked;
403
420
  payload.type = setDefault ? "default" : null;
404
421
  if (setDefault) {
@@ -1389,10 +1406,22 @@ const Settings = (() => {
1389
1406
 
1390
1407
  const badge = document.getElementById("brand-status-badge");
1391
1408
  if (data.warning) {
1392
- // Distinguish between expired (red) and expiring-soon (yellow)
1393
- const isExpired = data.warning && data.warning.toLowerCase().includes("expired");
1394
- badge.textContent = isExpired ? I18n.t("settings.brand.badge.expired") : I18n.t("settings.brand.badge.warning");
1395
- badge.className = "brand-status-value " + (isExpired ? "badge-expired" : "badge-expiring");
1409
+ // Use warning_type from backend for accurate badge display
1410
+ const wt = data.warning_type;
1411
+ let text, cls;
1412
+ if (wt === "expired") {
1413
+ text = I18n.t("settings.brand.badge.expired");
1414
+ cls = "badge-expired";
1415
+ } else if (wt === "unreachable") {
1416
+ text = I18n.t("settings.brand.badge.unreachable");
1417
+ cls = "badge-unreachable";
1418
+ } else {
1419
+ // "expiring" or legacy fallback
1420
+ text = I18n.t("settings.brand.badge.warning");
1421
+ cls = "badge-expiring";
1422
+ }
1423
+ badge.textContent = text;
1424
+ badge.className = "brand-status-value " + cls;
1396
1425
  } else {
1397
1426
  badge.textContent = I18n.t("settings.brand.badge.active");
1398
1427
  badge.className = "brand-status-value badge-active";
@@ -34,7 +34,7 @@ uninstall_gem() {
34
34
  command_exists gem || return 1
35
35
  if gem list -i openclacky >/dev/null 2>&1; then
36
36
  print_step "Uninstalling via RubyGems..."
37
- gem uninstall openclacky -x
37
+ gem uninstall openclacky -a -x
38
38
  else
39
39
  print_info "Gem 'openclacky' not found (already removed)"
40
40
  fi
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openclacky
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - windy
@@ -745,7 +745,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
745
745
  - !ruby/object:Gem::Version
746
746
  version: '0'
747
747
  requirements: []
748
- rubygems_version: 3.6.9
748
+ rubygems_version: 3.6.8
749
749
  specification_version: 4
750
750
  summary: The most Token-efficient open-source AI Agent — BYOK, Skill-driven, IM-integrated.
751
751
  test_files: []