ai_co_discuss 0.3.31 → 0.3.33
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/backend/free_room.py +27 -1
- package/package.json +1 -1
- package/web/app.js +17 -5
- package/web/modern.css +1 -1
- package/web/modern.js +1061 -1035
package/backend/free_room.py
CHANGED
|
@@ -1699,6 +1699,8 @@ def execution_action(connection, room_id, execution_id, token, payload):
|
|
|
1699
1699
|
connection.execute("""INSERT INTO execution_capabilities (execution_id,room_id,stoppable,progress) VALUES (?,?,?,?)
|
|
1700
1700
|
ON CONFLICT(execution_id) DO UPDATE SET stoppable=excluded.stoppable,progress=excluded.progress""",
|
|
1701
1701
|
(execution_id, room_id, int(payload["stoppable"]), int(payload["progress"])))
|
|
1702
|
+
elif action == "retry":
|
|
1703
|
+
retry_failed(connection, room_id, row)
|
|
1702
1704
|
elif row["status"] not in ACTIVE:
|
|
1703
1705
|
raise FileExistsError("执行已结束")
|
|
1704
1706
|
elif action == "renew":
|
|
@@ -1745,11 +1747,35 @@ def execution_action(connection, room_id, execution_id, token, payload):
|
|
|
1745
1747
|
# Only for proven non-delivery: the requests return to the queue unchanged.
|
|
1746
1748
|
finish_execution(connection, execution_id, "cancelled", str(payload.get("reason") or "未送达"), "pending")
|
|
1747
1749
|
else:
|
|
1748
|
-
raise ValueError("action 须为 renew、fail、release、report、segments、retry_wait、retry_start、retry_now 或
|
|
1750
|
+
raise ValueError("action 须为 renew、fail、release、report、segments、retry_wait、retry_start、retry_now、retry_stop 或 retry")
|
|
1749
1751
|
detail = execution_detail(connection, connection.execute("SELECT * FROM member_executions WHERE id=?", (execution_id,)).fetchone())
|
|
1750
1752
|
return {**detail, "duplicate": duplicate} if action == "stopped" else detail
|
|
1751
1753
|
|
|
1752
1754
|
|
|
1755
|
+
def retry_failed(connection, room_id, execution):
|
|
1756
|
+
"""Owner retry of a failed execution: its requests go back to the same member's queue and the runner delivers them anew.
|
|
1757
|
+
|
|
1758
|
+
The failed execution stays as history but no longer carries the requests, so a second retry is refused. A host batch
|
|
1759
|
+
that failed as a whole reopens and its chain resumes, so the host still gets the answer back.
|
|
1760
|
+
"""
|
|
1761
|
+
if execution["status"] != "failed":
|
|
1762
|
+
raise FileExistsError("只有失败的执行可以重试")
|
|
1763
|
+
requests = connection.execute("""SELECT id,batch_id,chain_id FROM member_requests r WHERE execution_id=? AND status='failed'
|
|
1764
|
+
AND NOT EXISTS(SELECT 1 FROM session_requests s WHERE s.member_request_id=r.id)""", (execution["id"],)).fetchall()
|
|
1765
|
+
if not requests:
|
|
1766
|
+
raise FileExistsError("这次执行没有可重试的请求:已重试过,或是讨论发言(由主持人重新安排)")
|
|
1767
|
+
if not connection.execute("SELECT 1 FROM room_members WHERE id=? AND status='active'", (execution["member_id"],)).fetchone():
|
|
1768
|
+
raise FileExistsError("该成员已离开或暂离,无法重试")
|
|
1769
|
+
connection.execute(f"UPDATE member_requests SET status='pending',execution_id=NULL WHERE id IN ({','.join('?' * len(requests))})",
|
|
1770
|
+
[request["id"] for request in requests])
|
|
1771
|
+
for request in requests:
|
|
1772
|
+
batch_id, chain_id = request["batch_id"], request["chain_id"]
|
|
1773
|
+
if batch_id and not connection.execute(
|
|
1774
|
+
"SELECT 1 FROM member_requests WHERE room_id=? AND batch_id=? AND status='completed'", (room_id, batch_id)).fetchone():
|
|
1775
|
+
connection.execute("UPDATE free_batches SET settled=0 WHERE room_id=? AND batch_id=?", (room_id, batch_id))
|
|
1776
|
+
connection.execute("DELETE FROM free_chains WHERE room_id=? AND chain_id=? AND reason='all_failed'", (room_id, chain_id))
|
|
1777
|
+
|
|
1778
|
+
|
|
1753
1779
|
def owned_member(connection, room_id, member_id, token):
|
|
1754
1780
|
"""Read one member after proving the owner's credential."""
|
|
1755
1781
|
require_owner(connection, room_id, token)
|
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -361,9 +361,10 @@ async function admin_avatar_from_file(file) {
|
|
|
361
361
|
} finally { URL.revokeObjectURL(url); }
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
/** Whether this browser may write as the room owner: its own control credential,
|
|
364
|
+
/** Whether this browser may write as the room owner: its own control credential, a logged-in administrator session, or the
|
|
365
|
+
* loopback-only local site, whose server writes as the owner for every visitor (`admin_as_owner`). */
|
|
365
366
|
function has_room_control(room_id) {
|
|
366
|
-
return Boolean(localStorage.getItem(`discuss-access:${room_id}`) || (state.admin.logged_in && admin_token()));
|
|
367
|
+
return Boolean(localStorage.getItem(`discuss-access:${room_id}`) || state.admin.local || (state.admin.logged_in && admin_token()));
|
|
367
368
|
}
|
|
368
369
|
|
|
369
370
|
/** Explain why a management write failed when this browser has neither credential. */
|
|
@@ -947,7 +948,7 @@ async function refresh_room({ room: read_room = true } = {}) {
|
|
|
947
948
|
}
|
|
948
949
|
if (state.room && !state.rooms.some((item) => item.id === state.room.id)) return select_room(visible_rooms()[0]?.id);
|
|
949
950
|
if (!state.room || !read_room) {
|
|
950
|
-
if (!state.room && visible_rooms().length) return select_room(
|
|
951
|
+
if (!state.room && visible_rooms().length) return select_room(opening_room());
|
|
951
952
|
render_rooms();
|
|
952
953
|
set_connection(true);
|
|
953
954
|
return;
|
|
@@ -964,6 +965,17 @@ async function refresh_room({ room: read_room = true } = {}) {
|
|
|
964
965
|
}
|
|
965
966
|
|
|
966
967
|
/** Select a room and reset room-specific draft and filter state. */
|
|
968
|
+
// The room last opened in this browser, reopened on the next visit instead of the first in the list.
|
|
969
|
+
const LAST_ROOM_KEY = "ai-codiscuss-last-room";
|
|
970
|
+
|
|
971
|
+
/** The room to open when none is chosen: the address's room, else the one last opened here, else the first listed. */
|
|
972
|
+
function opening_room() {
|
|
973
|
+
let last = null;
|
|
974
|
+
try { last = localStorage.getItem(LAST_ROOM_KEY); } catch { /* Storage may be unavailable. */ }
|
|
975
|
+
const wanted = [new URLSearchParams(location.search).get("room"), last].find((id) => id && state.rooms.some((room) => room.id === id));
|
|
976
|
+
return wanted || visible_rooms()[0]?.id;
|
|
977
|
+
}
|
|
978
|
+
|
|
967
979
|
async function select_room(room_id) {
|
|
968
980
|
state.navigation_version++;
|
|
969
981
|
state.refresh_version++;
|
|
@@ -991,6 +1003,7 @@ async function select_room(room_id) {
|
|
|
991
1003
|
cancel_reply();
|
|
992
1004
|
select_tab("discussion", { refresh: false });
|
|
993
1005
|
history.replaceState(null, "", state.room ? `/?room=${encodeURIComponent(room_id)}` : "/");
|
|
1006
|
+
if (state.room) try { localStorage.setItem(LAST_ROOM_KEY, room_id); } catch { /* Keep working without remembering. */ }
|
|
994
1007
|
if (state.room) state.archive_view = Boolean(state.room.archived);
|
|
995
1008
|
else {
|
|
996
1009
|
element("host-brief-panel").hidden = true;
|
|
@@ -3862,8 +3875,7 @@ async function initialize() {
|
|
|
3862
3875
|
const result = computer_rooms_only() && !state.remote_rooms ? { rooms: [] } : await request_json("/api/rooms");
|
|
3863
3876
|
state.rooms = result.rooms;
|
|
3864
3877
|
set_folder_directory(result);
|
|
3865
|
-
|
|
3866
|
-
await select_room(state.rooms.some((room) => room.id === requested) ? requested : visible_rooms()[0]?.id);
|
|
3878
|
+
await select_room(opening_room());
|
|
3867
3879
|
set_connection(true);
|
|
3868
3880
|
} catch (error) { set_connection(false); show_toast(error.message, true); }
|
|
3869
3881
|
start_live_updates();
|