tinywebui 0.1.3 → 0.1.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.
Files changed (82) hide show
  1. package/README.md +20 -13
  2. package/bin/tinywebui.js +69 -63
  3. package/package.json +25 -8
  4. package/public/app.js +40 -14
  5. package/public/chat/composer.js +1 -1
  6. package/public/chat/sidebar.js +8 -5
  7. package/public/index.html +88 -49
  8. package/public/panels/admin.js +40 -1
  9. package/public/panels/model-editor.js +336 -0
  10. package/public/panels/panels.js +37 -12
  11. package/public/panels/settings-home.js +125 -0
  12. package/public/panels/settings.js +124 -36
  13. package/public/shared/attachment_note.js +7 -1
  14. package/public/shared/events.js +1 -0
  15. package/public/shared/features.js +1 -0
  16. package/public/shared/origins.js +1 -0
  17. package/public/shared/usage.js +16 -1
  18. package/public/styles.css +83 -20
  19. package/src/access/auth.js +155 -155
  20. package/src/access/auth_gate.js +39 -38
  21. package/src/access/email.js +17 -0
  22. package/src/access/policy.js +123 -138
  23. package/src/audit.js +1 -1
  24. package/src/automations/automation.js +135 -123
  25. package/src/automations/scheduler.js +138 -127
  26. package/src/chat/compact.js +149 -156
  27. package/src/chat/llm.js +380 -382
  28. package/src/chat/runs.js +162 -151
  29. package/src/chat/tool_executor.js +229 -218
  30. package/src/cli.js +535 -433
  31. package/src/config/approval.js +35 -29
  32. package/src/config/config.js +546 -451
  33. package/src/config/data_dir.js +24 -0
  34. package/src/config/models.js +191 -115
  35. package/src/config/refs.js +32 -0
  36. package/src/config/secrets.js +153 -0
  37. package/src/files/documents.js +57 -68
  38. package/src/files/images.js +30 -31
  39. package/src/harness/agent.js +60 -60
  40. package/src/harness/attribution.js +66 -58
  41. package/src/harness/checkpoint.js +67 -62
  42. package/src/harness/history.js +28 -23
  43. package/src/harness/prompt.js +40 -57
  44. package/src/harness/provider.js +284 -283
  45. package/src/harness/types.js +1 -0
  46. package/src/http.js +98 -66
  47. package/src/log.js +11 -12
  48. package/src/mcp.js +398 -374
  49. package/src/retrieval/embedding.js +228 -240
  50. package/src/retrieval/retrieval.js +182 -173
  51. package/src/routes/admin.js +178 -138
  52. package/src/routes/auth.js +184 -100
  53. package/src/routes/automations.js +99 -82
  54. package/src/routes/chats.js +240 -218
  55. package/src/routes/documents.js +130 -120
  56. package/src/routes/library.js +43 -46
  57. package/src/routes/mcp.js +49 -49
  58. package/src/routes/settings.js +127 -122
  59. package/src/routes/types.js +1 -0
  60. package/src/server.js +428 -392
  61. package/src/store/automations.js +106 -106
  62. package/src/store/chats.js +217 -242
  63. package/src/store/documents.js +181 -185
  64. package/src/store/embeddings.js +52 -54
  65. package/src/store/index.js +95 -49
  66. package/src/store/messages.js +55 -80
  67. package/src/store/schema.js +128 -107
  68. package/src/store/scope.js +18 -17
  69. package/src/store/search.js +25 -27
  70. package/src/store/turns.js +53 -53
  71. package/src/store/usage.js +343 -269
  72. package/src/store/users.js +162 -87
  73. package/src/store/wire.js +50 -41
  74. package/src/tools/ask_tool.js +68 -68
  75. package/src/tools/chat_search_tool.js +38 -44
  76. package/src/tools/context_tool.js +131 -138
  77. package/src/tools/document_tool.js +73 -78
  78. package/src/tools/mcp_tool.js +274 -232
  79. package/src/tools/task_tool.js +53 -50
  80. package/src/tools/types.js +1 -0
  81. package/public/panels/connectors.js +0 -184
  82. package/src/set-password.js +0 -43
@@ -1,119 +1,119 @@
1
1
  import { scope, ownerOf } from './scope.js';
2
2
  import { randomBytes } from 'node:crypto';
3
-
4
3
  // Scheduled automations and their runs.
5
4
  // One area of the Store; see index.js.
6
-
7
- function automationView(row) {
8
- return {
9
- id: row.id, userId: row.user_id, chatId: row.chat_id, chatTitle: row.chat_title,
10
- name: row.name, prompt: row.prompt, cron: row.cron, timezone: row.timezone,
11
- enabled: Boolean(row.enabled), nextRunAt: row.next_run_at, lastRunAt: row.last_run_at,
12
- lastStatus: row.last_status, lastResult: row.last_result, source: row.source,
13
- createdAt: row.created_at, updatedAt: row.updated_at
14
- };
5
+ // Takes the row as node:sqlite returns it, so callers can map query results straight through.
6
+ function automationView(r) {
7
+ const row = r;
8
+ return {
9
+ id: row.id, userId: row.user_id, chatId: row.chat_id, chatTitle: row.chat_title,
10
+ name: row.name, prompt: row.prompt, cron: row.cron, timezone: row.timezone,
11
+ enabled: Boolean(row.enabled), nextRunAt: row.next_run_at, lastRunAt: row.last_run_at,
12
+ lastStatus: row.last_status, lastResult: row.last_result, source: row.source,
13
+ createdAt: row.created_at, updatedAt: row.updated_at
14
+ };
15
15
  }
16
-
17
16
  export class AutomationStore {
18
- constructor(db, deps = {}) {
19
- this.db = db;
20
- Object.assign(this, deps);
21
- }
22
-
23
- list(userId) {
24
- const s = scope(userId, 'a.user_id');
25
- return this.db.prepare(`SELECT a.*, c.title AS chat_title FROM automations a JOIN chats c ON c.id=a.chat_id
17
+ db;
18
+ constructor(db) {
19
+ this.db = db;
20
+ }
21
+ claimUnowned(userId) {
22
+ this.db.prepare('UPDATE automations SET user_id = ? WHERE user_id IS NULL').run(userId);
23
+ }
24
+ list(userId) {
25
+ const s = scope(userId, 'a.user_id');
26
+ return this.db.prepare(`SELECT a.*, c.title AS chat_title FROM automations a JOIN chats c ON c.id=a.chat_id
26
27
  WHERE ${s.sql} ORDER BY a.updated_at DESC`).all(...s.params).map(automationView);
27
- }
28
-
29
- listAll() {
30
- return this.db.prepare(`SELECT a.*, c.title AS chat_title FROM automations a JOIN chats c ON c.id=a.chat_id
28
+ }
29
+ listAll() {
30
+ return this.db.prepare(`SELECT a.*, c.title AS chat_title FROM automations a JOIN chats c ON c.id=a.chat_id
31
31
  ORDER BY a.updated_at DESC`).all().map(automationView);
32
- }
33
-
34
- get(id, userId) {
35
- const s = scope(userId, 'a.user_id');
36
- const row = this.db.prepare(`SELECT a.*, c.title AS chat_title FROM automations a JOIN chats c ON c.id=a.chat_id
32
+ }
33
+ get(id, userId) {
34
+ const s = scope(userId, 'a.user_id');
35
+ const row = this.db.prepare(`SELECT a.*, c.title AS chat_title FROM automations a JOIN chats c ON c.id=a.chat_id
37
36
  WHERE a.id=? AND ${s.sql}`).get(id, ...s.params);
38
- return row ? automationView(row) : null;
39
- }
40
-
41
- create(data, userId) {
42
- scope(userId);
43
- const id = randomBytes(12).toString('hex');
44
- const now = Date.now();
45
- this.db.prepare(`INSERT INTO automations
37
+ return row ? automationView(row) : null;
38
+ }
39
+ create(data, userId) {
40
+ scope(userId);
41
+ const id = randomBytes(12).toString('hex');
42
+ const now = Date.now();
43
+ this.db.prepare(`INSERT INTO automations
46
44
  (id,user_id,chat_id,name,prompt,cron,timezone,enabled,next_run_at,source,created_at,updated_at)
47
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`).run(id,ownerOf(userId),data.chatId,data.name,data.prompt,data.cron,data.timezone,
48
- data.enabled === false ? 0 : 1,data.nextRunAt,data.source || 'user',now,now);
49
- return this.get(id,userId);
50
- }
51
-
52
- update(id, patch, userId) {
53
- const current = this.get(id,userId);
54
- if (!current) return null;
55
- const fields = { chatId:'chat_id', name:'name', prompt:'prompt', cron:'cron', timezone:'timezone', enabled:'enabled', nextRunAt:'next_run_at', lastRunAt:'last_run_at', lastStatus:'last_status', lastResult:'last_result' };
56
- const sets = ['updated_at=?'];
57
- const values = [Date.now()];
58
- for (const [key,col] of Object.entries(fields)) if (patch[key] !== undefined) {
59
- sets.push(`${col}=?`); values.push(key === 'enabled' ? (patch[key] ? 1 : 0) : patch[key]);
45
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`).run(id, ownerOf(userId), data.chatId, data.name, data.prompt, data.cron, data.timezone, data.enabled === false ? 0 : 1, data.nextRunAt, data.source || 'user', now, now);
46
+ return this.get(id, userId);
47
+ }
48
+ update(id, patch, userId) {
49
+ const current = this.get(id, userId);
50
+ if (!current)
51
+ return null;
52
+ const fields = { chatId: 'chat_id', name: 'name', prompt: 'prompt', cron: 'cron', timezone: 'timezone', enabled: 'enabled', nextRunAt: 'next_run_at', lastRunAt: 'last_run_at', lastStatus: 'last_status', lastResult: 'last_result' };
53
+ const sets = ['updated_at=?'];
54
+ const values = [Date.now()];
55
+ for (const [key, col] of Object.entries(fields))
56
+ if (patch[key] !== undefined) {
57
+ sets.push(`${col}=?`);
58
+ values.push(key === 'enabled' ? (patch[key] ? 1 : 0) : patch[key]);
59
+ }
60
+ values.push(id);
61
+ this.db.prepare(`UPDATE automations SET ${sets.join(',')} WHERE id=?`).run(...values);
62
+ return this.get(id, userId);
63
+ }
64
+ delete(id, userId) {
65
+ const s = scope(userId);
66
+ return this.db.prepare(`DELETE FROM automations WHERE id=? AND ${s.sql}`).run(id, ...s.params).changes > 0;
60
67
  }
61
- values.push(id);
62
- this.db.prepare(`UPDATE automations SET ${sets.join(',')} WHERE id=?`).run(...values);
63
- return this.get(id,userId);
64
- }
65
-
66
- delete(id, userId) {
67
- const s = scope(userId);
68
- return this.db.prepare(`DELETE FROM automations WHERE id=? AND ${s.sql}`).run(id, ...s.params).changes > 0;
69
- }
70
-
71
- /** Drops every automation that posts into a chat (the chat is going). */
72
- deleteForChat(chatId) {
73
- this.db.prepare('DELETE FROM automations WHERE chat_id = ?').run(chatId);
74
- }
75
-
76
- /** When the next enabled automation is due, or null when none is. */
77
- nextDue() {
78
- return this.db.prepare('SELECT MIN(next_run_at) AS due FROM automations WHERE enabled=1').get()?.due ?? null;
79
- }
80
-
81
- due(now = Date.now()) {
82
- return this.db.prepare(`SELECT a.*, c.title AS chat_title FROM automations a JOIN chats c ON c.id=a.chat_id
68
+ /** Drops every automation that posts into a chat (the chat is going). */
69
+ deleteForChat(chatId) {
70
+ this.db.prepare('DELETE FROM automations WHERE chat_id = ?').run(chatId);
71
+ }
72
+ /** When the next enabled automation is due, or null when none is. */
73
+ nextDue() {
74
+ return this.db.prepare('SELECT MIN(next_run_at) AS due FROM automations WHERE enabled=1').get()?.due ?? null;
75
+ }
76
+ due(now = Date.now()) {
77
+ return this.db.prepare(`SELECT a.*, c.title AS chat_title FROM automations a JOIN chats c ON c.id=a.chat_id
83
78
  WHERE a.enabled=1 AND a.next_run_at<=? ORDER BY a.next_run_at`).all(now).map(automationView);
84
- }
85
-
86
- addRun(automationId, scheduledAt, status = 'queued', triggerType = 'schedule') {
87
- const id = randomBytes(12).toString('hex');
88
- this.db.prepare(`INSERT INTO automation_runs(id,automation_id,scheduled_at,status,trigger_type) VALUES(?,?,?,?,?)`)
89
- .run(id,automationId,scheduledAt,status,triggerType);
90
- return id;
91
- }
92
-
93
- updateRun(id, patch) {
94
- const fields = { status:'status', startedAt:'started_at', finishedAt:'finished_at', result:'result', error:'error' };
95
- const sets = []; const values = [];
96
- for (const [key,col] of Object.entries(fields)) if (patch[key] !== undefined) { sets.push(`${col}=?`); values.push(patch[key]); }
97
- if (sets.length) this.db.prepare(`UPDATE automation_runs SET ${sets.join(',')} WHERE id=?`).run(...values,id);
98
- const row = this.db.prepare('SELECT * FROM automation_runs WHERE id=?').get(id);
99
- const owner = this.db.prepare('SELECT user_id FROM automations WHERE id=?').get(row?.automation_id)?.user_id ?? null;
100
- if (row) this.update(row.automation_id, {
101
- lastRunAt: row.started_at || row.scheduled_at, lastStatus: row.status,
102
- ...(row.result !== null ? { lastResult: row.result.slice(0, 4000) } : {})
103
- }, owner);
104
- }
105
-
106
- listRuns(automationId, userId, limit = 10) {
107
- const owned = this.get(automationId,userId);
108
- if (!owned) return null;
109
- return this.db.prepare('SELECT id,scheduled_at,started_at,finished_at,status,trigger_type,result,error FROM automation_runs WHERE automation_id=? ORDER BY scheduled_at DESC LIMIT ?')
110
- .all(automationId,Math.min(50,Math.max(1,limit)));
111
- }
112
-
113
- recoverRuns(now = Date.now()) {
114
- const rows = this.db.prepare("SELECT id FROM automation_runs WHERE status IN ('queued','running')").all();
115
- for (const row of rows) this.updateRun(row.id, {
116
- status: 'failed', finishedAt: now, error: 'Server restarted during this run.'
117
- });
118
- }
79
+ }
80
+ addRun(automationId, scheduledAt, status = 'queued', triggerType = 'schedule') {
81
+ const id = randomBytes(12).toString('hex');
82
+ this.db.prepare(`INSERT INTO automation_runs(id,automation_id,scheduled_at,status,trigger_type) VALUES(?,?,?,?,?)`)
83
+ .run(id, automationId, scheduledAt, status, triggerType);
84
+ return id;
85
+ }
86
+ updateRun(id, patch) {
87
+ const fields = { status: 'status', startedAt: 'started_at', finishedAt: 'finished_at', result: 'result', error: 'error' };
88
+ const sets = [];
89
+ const values = [];
90
+ for (const [key, col] of Object.entries(fields))
91
+ if (patch[key] !== undefined) {
92
+ sets.push(`${col}=?`);
93
+ values.push(patch[key]);
94
+ }
95
+ if (sets.length)
96
+ this.db.prepare(`UPDATE automation_runs SET ${sets.join(',')} WHERE id=?`).run(...values, id);
97
+ const row = this.db.prepare('SELECT * FROM automation_runs WHERE id=?').get(id);
98
+ const owner = this.db.prepare('SELECT user_id FROM automations WHERE id=?').get(row?.automation_id ?? null)?.user_id ?? null;
99
+ if (row)
100
+ this.update(row.automation_id, {
101
+ lastRunAt: row.started_at || row.scheduled_at, lastStatus: row.status,
102
+ ...(row.result !== null ? { lastResult: row.result.slice(0, 4000) } : {})
103
+ }, owner);
104
+ }
105
+ listRuns(automationId, userId, limit = 10) {
106
+ const owned = this.get(automationId, userId);
107
+ if (!owned)
108
+ return null;
109
+ return this.db.prepare('SELECT id,scheduled_at,started_at,finished_at,status,trigger_type,result,error FROM automation_runs WHERE automation_id=? ORDER BY scheduled_at DESC LIMIT ?')
110
+ .all(automationId, Math.min(50, Math.max(1, limit)));
111
+ }
112
+ recoverRuns(now = Date.now()) {
113
+ const rows = this.db.prepare("SELECT id FROM automation_runs WHERE status IN ('queued','running')").all();
114
+ for (const row of rows)
115
+ this.updateRun(row.id, {
116
+ status: 'failed', finishedAt: now, error: 'Server restarted during this run.'
117
+ });
118
+ }
119
119
  }