ai_co_discuss 0.3.52 → 0.3.54

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.
@@ -12,7 +12,7 @@
12
12
 
13
13
  PDF 站内预览使用 [Mozilla PDF.js](https://github.com/mozilla/pdf.js) 的 `pdfjs-dist 6.4.299`,遵循 Apache-2.0;完整许可证随 [web/pdfjs/LICENSE](../web/pdfjs/LICENSE) 分发。CMap、标准字体、ICC 与 WASM 解码器的独立许可证保留在 `web/pdfjs/cmaps/`、`standard_fonts/`、`iccs/`、`wasm/` 中。构建只复制所需运行文件及这些许可证,不使用远程 CDN。
14
14
 
15
- `package.json` 声明直接依赖,`package-lock.json` 记录实际版本、来源及包许可元数据。Vue、Nuxt UI、Markdown-it、KaTeX、Motion、Iconify / Lucide、fflate、uqr 等第三方代码用于前端渲染、本地备份与配对二维码;Vite、Tailwind CSS 与 Wrangler 用于构建和部署。
15
+ `package.json` 声明直接依赖,`package-lock.json` 记录实际版本、来源及包许可元数据。Vue、Nuxt UI、Markdown-it、KaTeX、Motion、Iconify / Lucide、fflate、uqr、jsQR 等第三方代码用于前端渲染、本地备份、配对二维码的显示与扫描;Vite、Tailwind CSS 与 Wrangler 用于构建和部署。
16
16
 
17
17
  发布带依赖的程序或重新打包前端时,应保留所使用包中的 LICENSE / NOTICE 文件和生成产物中的许可注释。KaTeX 的样式表与公式字体随前端复制到 `web/katex/`(附其 LICENSE),其包内许可同样适用;Lucide 图标遵循该图标集自身许可。依赖安装后的原始许可位于各 `node_modules/<package>/`,下方附录保存直接依赖及当前前端实际打包模块的许可文本,便于源码与静态产物一同分发。
18
18
 
@@ -1263,6 +1263,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1263
1263
  SOFTWARE.
1264
1264
  ```
1265
1265
 
1266
+ ## jsQR 1.4.0
1267
+
1268
+ 来源:[jsQR](https://github.com/cozmo/jsQR),用于在网页内扫描设备配对二维码。按 Apache License 2.0 授权,全文见 <https://www.apache.org/licenses/LICENSE-2.0>。
1269
+
1266
1270
  ## uqr 0.1.2
1267
1271
 
1268
1272
  来源:[uqr](https://github.com/unjs/uqr),用于在网页上显示设备配对二维码。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.3.52",
3
+ "version": "0.3.54",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -34,6 +34,7 @@
34
34
  "markdown-it-cjk-friendly": "3.0.0",
35
35
  "motion-v": "2.7.0",
36
36
  "pdfjs-dist": "6.4.299",
37
+ "jsqr": "1.4.0",
37
38
  "uqr": "0.1.2",
38
39
  "vite": "8.3.4",
39
40
  "vue": "3.5.44",
package/web/app.js CHANGED
@@ -53,7 +53,7 @@ const RELAY_PING_MS = 25000;
53
53
  const ROOM_RELAY_PATH = /^\/api\/(rooms|folders|revisions)(\/|\?|$)/;
54
54
  // One part of a file sent to the computer stays well inside the relay's 1 MiB frame once encoded and encrypted.
55
55
  const RELAY_UPLOAD_PART_BYTES = 384 * 1024;
56
- const COMPUTER_UNREACHABLE = "电脑未连接:请登录管理员,并确认电脑开机且运行 codiscuss";
56
+ const COMPUTER_UNREACHABLE = "电脑未连接:请先与电脑配对,并确认电脑开机且运行 codiscuss";
57
57
  let local_connection = null;
58
58
  let computer_connecting = false;
59
59
  let relay_machine = null;
@@ -220,7 +220,7 @@ async function request_json(path, payload, access_token) {
220
220
  const token = access_token || (target_room ? localStorage.getItem(`discuss-access:${target_room}`) : null);
221
221
  options.headers = token ? { "X-Discussion-Token": token } : {};
222
222
  // The online website has no accounts: a paired device signs its requests with its own non-exportable key.
223
- if (state.admin.devices && /^\/api\/(machines|admin\/profile)/.test(path)) {
223
+ if ((state.admin.devices || paired_record()) && /^\/api\/(machines|admin\/profile|admin$)/.test(path)) {
224
224
  const credential = await device_credential(payload === undefined ? "GET" : "POST", path.split("?")[0]);
225
225
  if (credential) options.headers["X-CoDiscuss-Device"] = credential;
226
226
  }
@@ -340,7 +340,7 @@ async function update_admin_profile(patch) {
340
340
  patch = { ...patch, username: patch.username.trim() };
341
341
  }
342
342
  const result = await request_json("/api/admin/profile", patch);
343
- apply_admin_state({ local: state.admin.local, registered: true, setup_available: false, logged_in: true, profile: result.profile });
343
+ apply_admin_state({ local: state.admin.local, devices: state.admin.devices, computer_rooms: state.admin.computer_rooms, registered: true, setup_available: false, logged_in: true, profile: result.profile });
344
344
  }
345
345
 
346
346
  /** Shrink the chosen image into a small square data URL that fits the server limit. */
@@ -1954,8 +1954,8 @@ function save_room_credentials(room, token, members = []) {
1954
1954
 
1955
1955
  /** Public administrator state for the account UI; the session token never leaves the controller. */
1956
1956
  function admin_snapshot() {
1957
- const { loaded, local, registered, setup_available, logged_in, profile, identity_version } = state.admin;
1958
- return { loaded, local, registered, setup_available, logged_in, identity_version, profile: profile && { ...profile } };
1957
+ const { loaded, local, devices, registered, setup_available, logged_in, profile, identity_version } = state.admin;
1958
+ return { loaded, local, devices, registered, setup_available, logged_in, identity_version, profile: profile && { ...profile } };
1959
1959
  }
1960
1960
 
1961
1961
  /** Build only non-secret status metadata for the currently selected room. */
@@ -2080,6 +2080,16 @@ function take_pairing_code() {
2080
2080
  notify("info", "已读取配对码:打开 设置 → 账号,点“配对”");
2081
2081
  }
2082
2082
 
2083
+ /** Take the code and computer fingerprint from a scanned pairing link; answers the code, or "" when it is not one. */
2084
+ function take_scanned_pairing(text) {
2085
+ const hash = String(text || "").split("#")[1] || "";
2086
+ const code = (hash.match(/(?:^|&)pair=([0-9A-Za-z-]{12,16})/) || [])[1];
2087
+ if (!code) return "";
2088
+ state.device.pairing_code = code.toUpperCase();
2089
+ state.device.pairing_fp = (hash.match(/(?:^|&)fp=([0-9a-f]{32})/) || [])[1] || "";
2090
+ return state.device.pairing_code;
2091
+ }
2092
+
2083
2093
  /** Read a site-bound connection without exposing its credential to the shared view snapshot. */
2084
2094
  function restore_local_connection() {
2085
2095
  const fragment = new URLSearchParams(location.hash?.slice(1) || "");
@@ -2105,6 +2115,8 @@ function restore_local_connection() {
2105
2115
  async function local_launch_request(action, payload) {
2106
2116
  // A website without rooms of its own reaches the computer only through the relay; pairing itself still needs the local bridge.
2107
2117
  if (relay_machine && (!local_connection || computer_rooms_only() && action !== "machine-link")) return relay_command(action, payload);
2118
+ // Only the computer's own page has a local service at this address; elsewhere the computer is reached through the relay alone.
2119
+ if (!local_connection && !["127.0.0.1", "localhost"].includes(location.hostname)) throw Object.assign(new Error(COMPUTER_UNREACHABLE), { preflight: true });
2108
2120
  const headers = { "Content-Type": "application/json", "X-CoDiscuss-Local": "1" };
2109
2121
  if (local_connection) headers["X-CoDiscuss-Connection"] = local_connection.token;
2110
2122
  let response;
@@ -2276,7 +2288,8 @@ async function pair_this_device(code) {
2276
2288
  state.admin.logged_in = true;
2277
2289
  publish_view();
2278
2290
  notify("info", "这台设备已与电脑配对");
2279
- // Settings and the computer's rooms load now that the computer accepts this device.
2291
+ // The owner's profile, settings and the computer's rooms load now that the computer accepts this device.
2292
+ await load_admin_state();
2280
2293
  await load_launch_settings();
2281
2294
  if (computer_rooms_only()) connect_computer_rooms();
2282
2295
  } });
@@ -2770,7 +2783,6 @@ async function load_launch_settings() {
2770
2783
  state.launch.connect_command = local ? "codiscuss local open" : `codiscuss local connect "${location.origin}"`;
2771
2784
  try {
2772
2785
  local_connection = null;
2773
- relay_machine = null;
2774
2786
  take_pairing_code();
2775
2787
  local_connection = restore_local_connection();
2776
2788
  state.launch.remote = Boolean(local_connection);
@@ -2779,8 +2791,9 @@ async function load_launch_settings() {
2779
2791
  // Pair first, so the computer this page runs on can then be reached through the relay like any other device.
2780
2792
  state.launch.relay = await load_relay_machine();
2781
2793
  // An online computer that publishes a command key does everything the local service would; an older one only creates rooms.
2782
- if (!state.launch.relay?.online || !state.launch.relay.public_key) return;
2783
- relay_machine = state.launch.relay;
2794
+ // The previous one stays in use until this answer arrives, so requests made meanwhile still reach the computer.
2795
+ relay_machine = state.launch.relay?.online && state.launch.relay.public_key ? state.launch.relay : null;
2796
+ if (!relay_machine) return;
2784
2797
  state.launch.remote = true;
2785
2798
  }
2786
2799
  const settings = await local_launch_request("settings");
@@ -4689,7 +4702,7 @@ window.discuss_view = { snapshot: view_snapshot, write_field: write_view_field,
4689
4702
  admin: { register: register_admin, login: login_admin, logout: logout_admin,
4690
4703
  update_profile: update_admin_profile, avatar_from_file: admin_avatar_from_file, validate: validate_admin_form,
4691
4704
  sessions: admin_sessions, revoke: revoke_admin_sessions, change_password: change_admin_password },
4692
- device: { request: device_request, pair: pair_this_device, invite: invite_device, renew: renew_this_device, confirm_write: confirm_write_grant, forget: forget_pairing,
4705
+ device: { request: device_request, pair: pair_this_device, take_scanned: take_scanned_pairing, invite: invite_device, renew: renew_this_device, confirm_write: confirm_write_grant, forget: forget_pairing,
4693
4706
  label: device_label, prompt_confirm: () => state.device.prompt?.run(), prompt_cancel: () => state.device.prompt?.cancel() },
4694
4707
  request: request_json, rewind_plan, identities: IDENTITIES, display_name, mention_handles, mentioned_members, render_room_markdown, sources: SOURCE_LABELS,
4695
4708
  set_member_access, set_member_model, allow_denials, set_member_adapter, reload_room_models, read_room_files, limits_label, stop_reason_label, participant_model, participant_files, participant_session_hint, participant_access,