ai_co_discuss 0.2.3 → 0.2.5

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.
@@ -148,7 +148,7 @@ Python 在一个只读事务快照中查询;D1 使用同一个 batch 事务读
148
148
 
149
149
  ### 主题与交互边界
150
150
 
151
- 新建表单独立选择房间用法(头脑风暴/自由协作)与接入方式(程序唤醒/手动),共用内联角色库。接入方式以分段切换放在参与区标题旁;每次添加生成独立实例,已加入成员以 `ExpertCard.vue` 渲染为翻面卡片:正面是身份与只读配置(状态、软件、模型、强度),背面是设置;卡片上的皇冠指定主持人,移出按钮同步修复主持人选择,角色库为卡片下方的“添加”标签行。自由协作默认用户主持(“我来主持”),可改由一位本地 AI 主持。支持本地参与的卡片翻面设置参与能力和软件入口,纯网页角色不翻面。自动 CLI 从本机软件目录下拉选择模型,不要求手填 ID;模型与强度同层显示,恢复默认只清除当前实例覆盖。App、网页和手动会话在原软件内设置。`EffortSlider.vue` 按所选模型的实际档位显示,首档为空值的 CLI 默认,显示标签与提交值分离(Extra High 对应 xhigh);agy 的强度随模型,不另显示滑杆。目录不可用时显示原因并保留软件默认,不虚构列表。
151
+ 新建表单独立选择房间用法(头脑风暴/自由协作)与接入方式(程序唤醒/手动),共用内联角色库。接入方式以分段切换放在参与区标题旁;每次添加生成独立实例,已加入成员以 `ExpertCard.vue` 渲染为翻面卡片:正面是身份与只读配置(状态、软件、模型、强度),背面是设置;卡片上的皇冠指定主持人,移出按钮同步修复主持人选择,角色库为卡片下方的“添加”标签行。自由协作默认用户主持(“我来主持”),可改由一位本地 AI 主持。支持本地参与的卡片翻面设置参与能力和软件入口,纯网页角色不翻面。自动 CLI 从本机软件目录下拉选择模型,不要求手填 ID;模型与强度同层显示,恢复默认只清除当前实例覆盖。App、网页和手动会话在原软件内设置。`EffortSlider.vue` 按所选模型的实际档位显示,首档为空值的 CLI 默认,显示标签与提交值分离(Extra High 对应 xhigh);标题处的档位名随拖动实时预览、松手才提交,悬停、拖动与键盘聚焦分别有光晕反馈,减少动态效果时不做过渡;agy 的强度随模型,不另显示滑杆。目录不可用时显示原因并保留软件默认,不虚构列表。
152
152
 
153
153
  正反面占用同一网格单元,避免翻转引起布局跳动。角度由现有 Motion 弹簧驱动:质量 1、频率 2.615 Hz、阻尼比 0.96(刚度 270.032、阻尼 31.551);目标在 0° / 180° 之间切换,中途反向保留速度,无固定时长。隐藏面设为 inert 并退出辅助技术读取,转过 90° 后移交焦点;Escape 返回,减少动态效果时直接到位。滑块采用单位质量、4.5 Hz 临界阻尼弹簧,拖动由 6 Hz 手部弹簧牵引、8 Hz 端点力限制;松手依据真实位置与速度的投影选择档位,点击直接选档,方向键和 Home / End 覆盖全部档位,取消拖动恢复原选项。模型与强度仅在创建前配置,讨论进度中保持只读。
154
154
 
package/docs/runner.md CHANGED
@@ -77,7 +77,7 @@ python server.py --host 127.0.0.1 --local-launch
77
77
 
78
78
  ### 本机模型目录
79
79
 
80
- 网页的自动 CLI 模型选择通过 `POST /api/local/models` 读取本机软件目录。输入为 `adapter`、可选完整 `executable`;未传路径时沿用该适配器的本机配置。CLI 对应 `codiscuss launch models --file FILE`。返回 `{adapter, models:[{id,label,efforts?}], source, error?}`,只含模型元数据;没有列表时显示原因并允许使用软件默认,不要求手填 ID。页面按适配器和路径缓存,模型支持的档位随目录返回;App、纯网页和手动会话在原软件设置。
80
+ 网页的自动 CLI 模型选择通过 `POST /api/local/models` 读取本机软件目录。输入为 `adapter`、可选完整 `executable`;未传路径时沿用该适配器的本机配置。CLI 对应 `codiscuss launch models --file FILE`。返回 `{adapter, models:[{id,label,efforts?}], source, error?}`,只含模型元数据;没有列表时显示原因并允许使用软件默认,不要求手填 ID。部分 CLI 列表较慢(`agy models` 实测约 15 秒),本机服务把成功结果按适配器、路径与可执行文件大小/修改时间缓存在服务目录的 `model-catalogs.json`,24 小时内复用;可执行文件变化或点“重试”(`refresh: true`)时重新查询,查询失败不覆盖上次成功的列表。卡片加入后即在后台预读,不等翻面;页面同样按适配器和路径缓存,模型支持的档位随目录返回;App、纯网页和手动会话在原软件设置。
81
81
 
82
82
  - Codex:临时运行 `app-server`,依次 `initialize`、`initialized`、分页 `model/list`,不创建 thread 或 turn。见 [官方协议](https://learn.chatgpt.com/docs/app-server#list-models-modellist)。
83
83
  - Claude:临时以 `--safe-mode --no-session-persistence` 开启流式控制通道,仅发送 initialize,读取软件返回的 models 和 supportedEffortLevels;禁用自定义启动扩展,不发送用户消息。协议依据 [官方客户端初始化](https://github.com/anthropics/claude-agent-sdk-python/blob/main/src/claude_agent_sdk/_internal/query.py)。旧版本不支持时显示查询失败。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -52,6 +52,28 @@ conversation 还须 current_session_id,当前会话接手首个主持任务。
52
52
  详见 docs/runner.md 的“两种讨论入口与启动配置”。
53
53
  `;
54
54
 
55
+ /** Some CLIs take seconds to list models; reuse a good list for a day unless the executable changes or the user retries. */
56
+ const MODEL_CACHE_MS = 24 * 60 * 60 * 1000;
57
+
58
+ /** Return the software catalog, served from the private service home when still valid. */
59
+ export async function cached_software_models(home, profile, { refresh = false, query = software_models } = {}) {
60
+ const path = join(home, 'model-catalogs.json');
61
+ let stamp = '';
62
+ try { const file = statSync(profile.executable); stamp = `${file.size}:${file.mtimeMs}`; } catch {}
63
+ const key = JSON.stringify([profile.adapter, profile.executable || '', stamp]);
64
+ let cache = {};
65
+ try { cache = JSON.parse(readFileSync(path, 'utf8')); } catch {}
66
+ const hit = cache[key];
67
+ if (!refresh && hit && Date.now() - hit.at < MODEL_CACHE_MS) return hit.result;
68
+ const result = await query(profile);
69
+ if (!result.error && result.models.length) {
70
+ const prefix = JSON.stringify([profile.adapter, profile.executable || '']).slice(0, -1);
71
+ for (const old of Object.keys(cache)) if (old.startsWith(prefix)) delete cache[old];
72
+ atomic_json(path, { ...cache, [key]: { at: Date.now(), result } });
73
+ }
74
+ return result;
75
+ }
76
+
55
77
  /** Read private defaults without selecting a global room or identity. */
56
78
  export function launch_settings(home) {
57
79
  const path = join(home, 'launch-settings.json');
@@ -585,7 +607,7 @@ export async function launch_main(args = process.argv.slice(2), input_text) {
585
607
  else if (positionals[0] === 'room-models' && input) result = room_models(home, input);
586
608
  else if (positionals[0] === 'models' && input) {
587
609
  const saved = Object.values(launch_settings(home).participants).find(profile => profile.adapter === input.adapter);
588
- result = await software_models({ adapter: input.adapter, executable: input.executable || saved?.executable });
610
+ result = await cached_software_models(home, { adapter: input.adapter, executable: input.executable || saved?.executable }, { refresh: input.refresh === true });
589
611
  }
590
612
  else if (positionals[0] === 'room-member' && input) result = await room_member(home, input);
591
613
  else if (positionals[0] === 'start' && input) result = await launch_discussion(home, input, { dry_run: values['dry-run'], browser_request: values.browser });
package/web/app.js CHANGED
@@ -1591,7 +1591,7 @@ async function load_launch_models(name, force = false) {
1591
1591
  const catalogs = state.launch.model_catalogs ||= {}, key = model_catalog_key(profile);
1592
1592
  if (catalogs[key]?.loading || (!force && catalogs[key])) return;
1593
1593
  const current = { models: [], loading: true, error: "", source: "" };
1594
- const input = { adapter: profile.adapter, ...(profile.executable ? { executable: profile.executable } : {}) };
1594
+ const input = { adapter: profile.adapter, ...(profile.executable ? { executable: profile.executable } : {}), ...(force ? { refresh: true } : {}) };
1595
1595
  catalogs[key] = current;
1596
1596
  publish_view();
1597
1597
  try {