bridgerator 1.1.1 → 1.1.3
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/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/bridge.mjs +15 -3
- package/src/notice.mjs +5 -0
- package/src/queue.mjs +13 -5
- package/src/settings-tool.mjs +2 -2
- package/src/tray.ps1 +1 -1
- package/ui/app.js +6 -2
- package/ui/i18n/en.json +6 -2
- package/ui/i18n/ru.json +6 -2
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bridgerator",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "bridgerator",
|
|
9
|
-
"version": "1.1.
|
|
9
|
+
"version": "1.1.3",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bridgerator",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Connect ChatGPT to your Windows PC: files, search and terminal through an outbound-only bridge with folder limits. No inbound ports, no admin rights.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/bridge.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { shorten } from './tool-text.mjs';
|
|
|
19
19
|
import { parseDelete, recycle as recycleOnPc, purgeFromBin } from './recycle.mjs';
|
|
20
20
|
import { createSnapshots, changedPaths, undoGroups, SNAP_DEFAULTS, UNDO_TOOL_DEF } from './undo.mjs';
|
|
21
21
|
import { createFuse, wantAwake, FUSE_DEFAULTS } from './guard.mjs';
|
|
22
|
-
import { createQueue, queueTool, QUEUE_TOOL_DEFS, QUEUE_TOOL_NAMES } from './queue.mjs';
|
|
22
|
+
import { createQueue, queueTool, stalledItems, QUEUE_TOOL_DEFS, QUEUE_TOOL_NAMES } from './queue.mjs';
|
|
23
23
|
import { createRecent, outcomeOf } from './recent.mjs';
|
|
24
24
|
import { readSettingsStrict, setIni, writeAtomic, CATEGORIES, findMatch, clip } from './confirm.mjs';
|
|
25
25
|
import { readPlan, planBanner, bannerVisible, dismissFor, safeUrl, forecast, setSite, accountUrl } from './plan.mjs';
|
|
@@ -374,6 +374,14 @@ export async function startDesktopCommander(cfg, log = console.log, { ask: askAr
|
|
|
374
374
|
// --- Recycle Bin, mass-action fuse, Undo (LOGIC: critical-logic/bridge.md) ---------------------
|
|
375
375
|
const fuse = createFuse();
|
|
376
376
|
let fuseAsking = false, inFlight = 0, lastCall = 0, wasPaused = false;
|
|
377
|
+
// One notice per stalled "doing" item (id + updatedAt), on unless the person turned it off.
|
|
378
|
+
const stalledWarned = new Set(), started = Date.now();
|
|
379
|
+
if (queue) setInterval(() => {
|
|
380
|
+
if (cfg.notify?.queueStalled === false || inFlight) return;
|
|
381
|
+
const fresh = stalledItems(queue.list('doing'), { now: Date.now(), idleSince: Math.max(lastCall, started) }).filter((it) => !stalledWarned.has(`${it.id}@${it.updatedAt}`));
|
|
382
|
+
for (const it of fresh) stalledWarned.add(`${it.id}@${it.updatedAt}`);
|
|
383
|
+
if (fresh.length) showNotice({ kind: 'queue_stalled', title: fresh[0].title, more: fresh.length - 1 }, log); // one notice per check, not a burst
|
|
384
|
+
}, 60000).unref();
|
|
377
385
|
const fuseCfg = () => ({ ...FUSE_DEFAULTS, ...(cfg.fuse || {}) });
|
|
378
386
|
const setPaused = (v) => { if (out.setPaused) out.setPaused(v); else { out.paused = v; out.onMode(mode()); } };
|
|
379
387
|
const currentTask = () => { const d = queue ? queue.list('doing') : []; return d.length === 1 ? { id: d[0].id, title: d[0].title } : undefined; };
|
|
@@ -495,7 +503,7 @@ export async function startDesktopCommander(cfg, log = console.log, { ask: askAr
|
|
|
495
503
|
const bannerNow = () => { const b = planBanner(out.plan); return { ...b, visible: bannerVisible(b, cfg.planBannerDismissed) }; };
|
|
496
504
|
out.banner = bannerNow;
|
|
497
505
|
out.onPlanMsg = (m) => { out.plan = readPlan(m); out.onPlan?.(); };
|
|
498
|
-
const BOOL_NOTIFY = ['sound', 'offline', 'queueDone', 'limit'];
|
|
506
|
+
const BOOL_NOTIFY = ['sound', 'offline', 'queueDone', 'queueStalled', 'limit'];
|
|
499
507
|
|
|
500
508
|
// Requests of the Bridgerator window (tray helper). Made by the person at the PC: no extra prompt.
|
|
501
509
|
async function rpc(method, p = {}, paths = []) {
|
|
@@ -504,7 +512,7 @@ export async function startDesktopCommander(cfg, log = console.log, { ask: askAr
|
|
|
504
512
|
const conf = gate.settings();
|
|
505
513
|
return {
|
|
506
514
|
state: out.paused ? 'paused' : out.linkState, name: cfg.deviceName || out.linkName, version: VERSION, paused: out.paused,
|
|
507
|
-
email: out.email || '', plan: out.plan || null, banner: bannerNow(), setupDone: cfg.setupDone === true,
|
|
515
|
+
email: out.email || '', plan: out.plan || null, banner: bannerNow(), setupDone: cfg.setupDone === true, allowOpened: cfg.chatgptAllowOpened === true,
|
|
508
516
|
settings: {
|
|
509
517
|
confirm: conf, allowedDirectories: cfg.allowedDirectories || [], readOnlyDirectories: cfg.readOnlyDirectories || [],
|
|
510
518
|
theme: popup().theme, language: cfg.language || 'en', localHistory: cfg.localHistory === true, telemetry: cfg.telemetryEnabled !== false,
|
|
@@ -586,6 +594,7 @@ export async function startDesktopCommander(cfg, log = console.log, { ask: askAr
|
|
|
586
594
|
return { restored: r.flatMap((x) => x.restored), skipped: r.flatMap((x) => x.skipped) };
|
|
587
595
|
}
|
|
588
596
|
case 'undo.clear': snapshots?.clear(); return true;
|
|
597
|
+
case 'chatgptAllowOpened': patchConfig({ chatgptAllowOpened: true }); return true;
|
|
589
598
|
case 'dismissBanner': { const b = planBanner(out.plan); if (b.dismissible) patchConfig({ planBannerDismissed: dismissFor(b) }); out.onPlan?.(); return true; }
|
|
590
599
|
case 'testNotification': return ask({ head: 'Test notification', text: 'This is how Bridgerator asks before risky actions. Nothing will run.', buttons: [['allow', 'Allow', ''], ['allow10', 'Allow 10 min', ''], ['always', 'Allow always', ''], ['deny', 'Deny', 'deny']], secs: 60, auto: 'closes', ...popup() });
|
|
591
600
|
case 'checkUpdate': {
|
|
@@ -690,6 +699,9 @@ export function cleanEnv(env = process.env) {
|
|
|
690
699
|
export function engineEnv(env = process.env) {
|
|
691
700
|
const e = cleanEnv(env);
|
|
692
701
|
for (const k of Object.keys(e)) if (/^(BRIDGERATOR_|BR_)/i.test(k)) delete e[k];
|
|
702
|
+
// The terminal's PowerShell may run .ps1 (npm.ps1, npx.ps1…): Windows' default "Restricted" blocks them (26.10, a user's
|
|
703
|
+
// PC). Process scope only — the PC's own policy is never changed, a Group Policy still wins, a value the person set stays.
|
|
704
|
+
if (!Object.keys(e).some((k) => k.toLowerCase() === 'psexecutionpolicypreference')) e.PSExecutionPolicyPreference = 'Bypass';
|
|
693
705
|
return e;
|
|
694
706
|
}
|
|
695
707
|
export function engineCwd(cfg, home = os.homedir()) {
|
package/src/notice.mjs
CHANGED
|
@@ -30,6 +30,11 @@ export function noticeText(n) {
|
|
|
30
30
|
const title = typeof n.title === 'string' ? n.title.replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ').trim().slice(0, 120) : '';
|
|
31
31
|
if (!title) return undefined;
|
|
32
32
|
t = ['Queue task done', title];
|
|
33
|
+
} else if (kind === 'queue_stalled') {
|
|
34
|
+
const title = typeof n.title === 'string' ? n.title.replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ').trim().slice(0, 120) : '';
|
|
35
|
+
if (!title) return undefined;
|
|
36
|
+
const more = int(n.more) || 0;
|
|
37
|
+
t = ['ChatGPT may be waiting for you', `No activity on the queue task "${title}"${more ? ` and ${more} more` : ''} for 10 minutes. Open the ChatGPT chat: it may be asking you to allow an action.`];
|
|
33
38
|
} else if (kind === 'protection_off') {
|
|
34
39
|
// Internal list only (the bridge's own labels), never relay text.
|
|
35
40
|
const ok = new Set(['confirmations', 'your own patterns', ...Object.values(LABEL).map((l) => `confirmations for ${l}`)]);
|
package/src/queue.mjs
CHANGED
|
@@ -9,7 +9,11 @@ export const STATUSES = ['todo', 'doing', 'done', 'failed', 'skipped'];
|
|
|
9
9
|
const MAX_ITEMS = 500, MAX_TITLE = 200, MAX_DETAILS = 4000, MAX_NOTE = 1000, MAX_PATHS = 20;
|
|
10
10
|
const RULE = 'The usual loop: queue_next, do the task, queue_update with done / failed / skipped and a short note, then the next one until the queue is empty or the person stops. resultPath set: write the result to that file. from set: another AI added it and reads queue_list.';
|
|
11
11
|
const MAX_FROM = 80, MAX_RESULT_PATH = 1000;
|
|
12
|
-
export const STALE_MS = 30 * 60 * 1000;
|
|
12
|
+
export const STALE_MS = 30 * 60 * 1000;
|
|
13
|
+
export const STALLED_MS = 10 * 60 * 1000; // "doing" + no tool call at all for this long: ChatGPT is likely waiting for the person
|
|
14
|
+
/** "doing" items with no update and no tool call on this PC for `ms` (owner 26.10: ChatGPT's own Allow dialog is invisible on the PC). */
|
|
15
|
+
// ponytail: one PC-wide idle clock — a second chat busy with other calls hides a stalled one; per-chat tracking would need the worker on every call.
|
|
16
|
+
export const stalledItems = (list, { now, idleSince, ms = STALLED_MS }) => (now - idleSince < ms ? [] : list.filter((x) => x.status === 'doing' && now - Date.parse(x.updatedAt) >= ms)); // "doing" with no update for this long: another chat gave up
|
|
13
17
|
const ID = { type: 'string', description: 'item id' };
|
|
14
18
|
const STR = (n) => ({ type: 'string', maxLength: n });
|
|
15
19
|
const PATHS = { type: 'array', maxItems: MAX_PATHS, items: STR(1000) };
|
|
@@ -18,7 +22,7 @@ const HANDOFF = { from: { type: 'string', maxLength: MAX_FROM, description: 'Who
|
|
|
18
22
|
|
|
19
23
|
export const QUEUE_TOOL_DEFS = [
|
|
20
24
|
{ name: 'queue_list', description: `List the task queue the person keeps on this PC (a checklist of things to do): counts and items (id, title, status, note, worker, updatedAt, from, resultPath), optionally only one status.`, inputSchema: { type: 'object', additionalProperties: false, properties: { status: { type: 'string', enum: STATUSES } } } },
|
|
21
|
-
{ name: 'queue_next', description: `Take the next item of the task queue: returns the first "todo" item and marks it "doing" (nothing when the queue is empty). An item left "doing" for 30 minutes without an update (another chat stopped) is returned first, with resumed: true. Several chats can work through one queue at once; each call gets a different item. Pass worker = a short name for this chat (e.g. its title) so the person sees who is on it. ${RULE}`, inputSchema: { type: 'object', additionalProperties: false, properties: { worker: STR(80) } } },
|
|
25
|
+
{ name: 'queue_next', description: `Take the next item of the task queue: returns the first "todo" item and marks it "doing" (nothing when the queue is empty). An item left "doing" for 30 minutes without an update (another chat stopped) is returned first, with resumed: true. Several chats can work through one queue at once; each call gets a different item. Pass worker = a short name for this chat (e.g. its title) so the person sees who is on it. ${RULE}`, inputSchema: { type: 'object', additionalProperties: false, properties: { worker: STR(80), id: { type: 'string', description: 'take this "todo" item instead of the first one' } } } },
|
|
22
26
|
{ name: 'queue_update', description: `Set an item's status (doing / done / failed / skipped / todo) with a short result note.`, inputSchema: { type: 'object', additionalProperties: false, required: ['id', 'status'], properties: { id: ID, status: { type: 'string', enum: STATUSES }, note: STR(MAX_NOTE) } } },
|
|
23
27
|
{ name: 'queue_add', description: `Add steps to the task queue (e.g. when planning a bigger job with the person): one { title, details?, paths?, from?, resultPath? } or items: [...] (1–100). Another AI handing work to ChatGPT sets from (its name) and resultPath (a file for the result), then reads the outcome with queue_list.`, inputSchema: { type: 'object', additionalProperties: false, properties: { title: STR(MAX_TITLE), details: STR(MAX_DETAILS), paths: PATHS, ...HANDOFF, items: { type: 'array', minItems: 1, maxItems: 100, items: { type: 'object', additionalProperties: false, required: ['title'], properties: { title: STR(MAX_TITLE), details: STR(MAX_DETAILS), paths: PATHS, ...HANDOFF } } } } } },
|
|
24
28
|
{ name: 'queue_clear', description: 'Remove finished items from the task queue: status done, failed or skipped. status "all" removes every item and needs the person\'s OK on the PC.', inputSchema: { type: 'object', additionalProperties: false, properties: { status: { type: 'string', enum: ['done', 'failed', 'skipped', 'all'] } } } },
|
|
@@ -99,11 +103,15 @@ export function createQueue(file, { now = () => new Date().toISOString(), onChan
|
|
|
99
103
|
items.push(it); save(); return it;
|
|
100
104
|
},
|
|
101
105
|
// Synchronous take: parallel chats calling queue_next never get the same item.
|
|
102
|
-
|
|
106
|
+
// id: that item only (a chat told to take certain tasks), if it is "todo" or stale "doing"; else an error.
|
|
107
|
+
next({ worker, id } = {}) { return commit(() => {
|
|
103
108
|
const w = str(worker, 80, 'worker');
|
|
104
109
|
const t = Date.parse(now());
|
|
105
|
-
const
|
|
106
|
-
const
|
|
110
|
+
const isStale = (x) => x.status === 'doing' && t - Date.parse(x.updatedAt) >= STALE_MS;
|
|
111
|
+
const pick = id === undefined ? null : get(str(id, 40, 'id', true));
|
|
112
|
+
if (pick && pick.status !== 'todo' && !isStale(pick)) throw new Error(`item ${pick.id} is "${pick.status}", not free to take`);
|
|
113
|
+
const stale = pick ? (isStale(pick) ? pick : null) : sorted().find(isStale);
|
|
114
|
+
const it = stale || pick || sorted().find((x) => x.status === 'todo');
|
|
107
115
|
if (!it) return null;
|
|
108
116
|
if (stale) it.resumedFrom = it.worker || 'another chat';
|
|
109
117
|
it.status = 'doing'; it.updatedAt = now();
|
package/src/settings-tool.mjs
CHANGED
|
@@ -21,11 +21,11 @@ export const DEFAULTS = {
|
|
|
21
21
|
preset: 'full', terminal: true, allowedDirectories: [], readOnlyDirectories: [], protectSensitive: false,
|
|
22
22
|
confirm: { enabled: false, ...readSettings('').ask, timeoutSeconds: 120 }, // no confirm.ini = the defaults per category
|
|
23
23
|
recycleBin: true, snapshots: { ...SNAP_DEFAULTS }, fuse: { ...FUSE_DEFAULTS }, keepAwake: true, askSettings: false,
|
|
24
|
-
theme: 'system', language: 'en', popupStyle: 'toast', notify: { sound: true, offline: true, queueDone: true, limit: true },
|
|
24
|
+
theme: 'system', language: 'en', popupStyle: 'toast', notify: { sound: true, offline: true, queueDone: true, queueStalled: true, limit: true },
|
|
25
25
|
deviceName: '', autostart: true, localHistory: false, telemetryEnabled: true,
|
|
26
26
|
...EXTRA.defaults(),
|
|
27
27
|
};
|
|
28
|
-
const NOTIFY = ['sound', 'offline', 'queueDone', 'limit'];
|
|
28
|
+
const NOTIFY = ['sound', 'offline', 'queueDone', 'queueStalled', 'limit'];
|
|
29
29
|
const dirs = { type: 'array', maxItems: 20, items: { type: 'string' } };
|
|
30
30
|
export const SETTINGS_TOOL_DEF = {
|
|
31
31
|
name: 'bridgerator_settings',
|
package/src/tray.ps1
CHANGED
|
@@ -73,7 +73,7 @@ $account = if ($env:BR_ACCOUNT -match '^https://[a-z0-9.-]+(:\d+)?/app$') { $env
|
|
|
73
73
|
$siteHost = ([Uri]$account).Host
|
|
74
74
|
$send = { param($o) [BrIO]::Send(($o | ConvertTo-Json -Compress -Depth 6)) }
|
|
75
75
|
# Links from the window / menu: only our own site (the main one, or the linked one), opened in the default browser.
|
|
76
|
-
$openUrl = { param($u) try { $x = [Uri]$u; if ($x.Scheme -eq 'https' -and ($x.Host -eq 'bridgerator.recondeck.com' -or $x.Host -eq $siteHost) -and -not $x.UserInfo) { Start-Process $x.AbsoluteUri } } catch {} }
|
|
76
|
+
$openUrl = { param($u) try { $x = [Uri]$u; if ($x.Scheme -eq 'https' -and ($x.Host -eq 'bridgerator.recondeck.com' -or $x.Host -eq $siteHost -or ($x.Host -eq 'chatgpt.com' -and $x.AbsolutePath -like '/settings/*')) -and -not $x.UserInfo) { Start-Process $x.AbsoluteUri } } catch {} }
|
|
77
77
|
$openAccount = { & $openUrl $account }
|
|
78
78
|
$g0 = [Drawing.Graphics]::FromHwnd([IntPtr]::Zero); $script:dpi = $g0.DpiX / 96.0; $g0.Dispose()
|
|
79
79
|
if ($env:BR_DPI) { $script:dpi = [double]$env:BR_DPI } # test harness: simulate scaling
|
package/ui/app.js
CHANGED
|
@@ -171,6 +171,9 @@ function awakeLine() {
|
|
|
171
171
|
const k = s.keepAwake === false ? 'status.awakeOff' : a.on ? 'status.awakeOn' : a.want && !a.ac ? 'status.awakeBattery' : 'status.awakeIdle';
|
|
172
172
|
return `<div class="awake-line ${a.on ? 'on' : ''}"><span class="dot ${a.on ? 'online' : 'offline'}"></span>${esc(t(k))}</div>`;
|
|
173
173
|
}
|
|
174
|
+
// The Bridgerator app's "Permission" page in ChatGPT (App ID of the store app). Owner 26.10: on first use ChatGPT
|
|
175
|
+
// asks before most actions ("Allow low-risk"); one switch to "Allow all" stops that, like Desktop Commander.
|
|
176
|
+
const CHATGPT_PERMISSIONS = 'https://chatgpt.com/settings/plugins-settings/plugin_asdk_app_6ad78d4a3c408191a9215fd304394c4a/permissions';
|
|
174
177
|
P.home = () => {
|
|
175
178
|
const st = state.state || 'offline';
|
|
176
179
|
const c = state.queue || {};
|
|
@@ -193,7 +196,7 @@ P.home = () => {
|
|
|
193
196
|
<a class="card tile" href="#recent"><span class="k">${esc(t('status.cardLast'))}</span><span class="v">${esc(last ? ago(last.t) : t('status.never'))}</span><span class="s" title="${esc(last ? `${last.tool} · ${last.target}` : '')}">${esc(last ? [last.tool, lastTarget(last.target)].filter(Boolean).join(' · ') : t('status.noActions'))}</span></a>
|
|
194
197
|
</div>
|
|
195
198
|
<section class="card steps" aria-label="${esc(t('aria.getStarted'))}">
|
|
196
|
-
<div class="step ok"><span class="check ok">${CHECK(12, 3.5)}</span><span class="
|
|
199
|
+
<div class="step ${state.allowOpened ? 'ok' : ''}"><span class="check ${state.allowOpened ? 'ok' : 'next'}">${state.allowOpened ? CHECK(12, 3.5) : ''}</span><div class="stack"><span class="t">${esc(t('status.stepAllow'))}</span>${state.allowOpened ? '' : `<span class="h">${esc(t('status.stepAllowHint'))}</span>`}</div>${state.allowOpened ? '' : `<button class="btn btn-secondary w112" data-act="chatgptAllow">${esc(t('status.stepAllowBtn'))}</button>`}</div>
|
|
197
200
|
<div class="step ${state.setupDone ? 'ok' : ''}"><span class="check ${state.setupDone ? 'ok' : 'next'}">${state.setupDone ? CHECK(12, 3.5) : ''}</span><div class="stack"><span class="t">${esc(t('status.step2'))}</span>${state.setupDone ? '' : `<span class="h">${esc(t('status.step2Hint'))} <span class="mono">${esc(t('status.step2Phrase'))}</span></span>`}</div>${state.setupDone ? '' : `<button class="btn btn-secondary w112" data-copy="${esc(t('status.step2Phrase'))}">${esc(t('status.copyPhrase'))}</button>`}</div>
|
|
198
201
|
<div class="step ${anyTodo ? 'ok' : ''}"><span class="check ${anyTodo ? 'ok' : state.setupDone ? 'next' : ''}">${anyTodo ? CHECK(12, 3.5) : ''}</span><div class="stack"><span class="t">${esc(t('status.step3'))}</span>${anyTodo ? '' : `<span class="h">${esc(t('status.step3Hint'))} <span class="mono">${esc(t('talk.work'))}</span></span>`}</div>${anyTodo ? '' : `<a class="btn btn-secondary w112" href="#queue">${esc(t('status.openQueue'))}</a>`}</div>
|
|
199
202
|
</section></main>`;
|
|
@@ -331,7 +334,7 @@ P.notifications = () => {
|
|
|
331
334
|
return `<main class="notifications">${bannerHtml()}
|
|
332
335
|
<div class="head"><h1>${esc(t('notify.title'))}</h1><div class="sub">${esc(t('notify.sub'))}</div></div>
|
|
333
336
|
<div class="styles" role="radiogroup" aria-label="${esc(t('aria.style'))}">${style('toast')}${style('popup')}</div>
|
|
334
|
-
<div class="nsplit"><section class="card" aria-label="${esc(t('aria.options'))}">${sw('sound', 'notify.sound')}${sw('offline', 'notify.offline')}${sw('queueDone', 'notify.queueDone')}${sw('limit', 'notify.limit')}</section>
|
|
337
|
+
<div class="nsplit"><section class="card" aria-label="${esc(t('aria.options'))}">${sw('sound', 'notify.sound')}${sw('offline', 'notify.offline')}${sw('queueDone', 'notify.queueDone')}${sw('queueStalled', 'notify.queueStalled')}${sw('limit', 'notify.limit')}</section>
|
|
335
338
|
<section class="preview" aria-label="${esc(t('notify.preview'))}"><div class="bar-top"><span class="b">${esc(t('notify.preview'))}</span><div class="seg" role="radiogroup" aria-label="${esc(t('aria.previewTheme'))}">${pv('light')}${pv('dark')}</div></div>
|
|
336
339
|
<div class="toast ${previewTheme}"><div class="app">${LOGO16}<span class="grow">Bridgerator</span><span>⋯</span></div><div class="h">${esc(t('notify.pHead'))}</div><div class="c">${esc(t('notify.pCmd'))}</div><div class="m">${esc(t('notify.pMeta', { name: state.name || 'PC' }))}</div>
|
|
337
340
|
<div class="btns"><span>${esc(t('notify.allow'))}</span><span>${esc(t('notify.allow10'))}</span><span>${esc(t('notify.deny'))}</span></div></div>
|
|
@@ -788,6 +791,7 @@ document.addEventListener('click', async (e) => {
|
|
|
788
791
|
case 'support': return rpc('openUrl', { url: 'https://bridgerator.recondeck.com/support' }).catch(fail);
|
|
789
792
|
case 'plan': return rpc('openUrl', { url: state.banner?.url || state.plan?.renewUrl || 'https://bridgerator.recondeck.com/app' }).catch(fail);
|
|
790
793
|
case 'dismissBanner': return rpc('dismissBanner').then(() => refresh(), fail);
|
|
794
|
+
case 'chatgptAllow': return rpc('openUrl', { url: CHATGPT_PERMISSIONS }).then(() => rpc('chatgptAllowOpened')).then(() => refresh(), fail);
|
|
791
795
|
case 'pause': case 'resume': return rpc(d.act).then(() => refresh(), fail);
|
|
792
796
|
case 'addTask': return taskDialog(null);
|
|
793
797
|
case 'allowAll': return setSetting('allowedDirectories', []);
|
package/ui/i18n/en.json
CHANGED
|
@@ -57,7 +57,9 @@
|
|
|
57
57
|
"agoMin": "{n} min ago",
|
|
58
58
|
"agoHour": "{n} h ago",
|
|
59
59
|
"agoDay": "{n} d ago",
|
|
60
|
-
"
|
|
60
|
+
"stepAllow": "Let ChatGPT work without asking each time",
|
|
61
|
+
"stepAllowHint": "In ChatGPT: Bridgerator → Permission → Allow all",
|
|
62
|
+
"stepAllowBtn": "Open",
|
|
61
63
|
"step2": "Set up with ChatGPT",
|
|
62
64
|
"step2Hint": "In ChatGPT, say:",
|
|
63
65
|
"step2Phrase": "Set up Bridgerator",
|
|
@@ -274,6 +276,7 @@
|
|
|
274
276
|
"sound": "Play a sound",
|
|
275
277
|
"offline": "When the PC goes offline",
|
|
276
278
|
"queueDone": "When a queue task is done",
|
|
279
|
+
"queueStalled": "When a queue task stalls (ChatGPT may be waiting for your OK in the chat)",
|
|
277
280
|
"limit": "Monthly limit warnings",
|
|
278
281
|
"preview": "Preview",
|
|
279
282
|
"light": "Light",
|
|
@@ -364,7 +367,8 @@
|
|
|
364
367
|
"Add tasks here, or ask ChatGPT to plan the work and add the steps itself.",
|
|
365
368
|
"Attach files or folders to a task so ChatGPT knows where to look.",
|
|
366
369
|
"In ChatGPT, say “Work through my Bridgerator queue”. Watch the ticks appear here.",
|
|
367
|
-
"Edit, reorder or delete tasks any time. A failed task shows why it failed."
|
|
370
|
+
"Edit, reorder or delete tasks any time. A failed task shows why it failed.",
|
|
371
|
+
"ChatGPT may ask you to allow an action in the chat; the task waits for your answer. If a task stalls, Bridgerator shows a notice."
|
|
368
372
|
],
|
|
369
373
|
"tipLabel": "say",
|
|
370
374
|
"tip": "Work through my Bridgerator queue"
|
package/ui/i18n/ru.json
CHANGED
|
@@ -57,7 +57,9 @@
|
|
|
57
57
|
"agoMin": "{n} мин назад",
|
|
58
58
|
"agoHour": "{n} ч назад",
|
|
59
59
|
"agoDay": "{n} дн. назад",
|
|
60
|
-
"
|
|
60
|
+
"stepAllow": "ChatGPT работает без лишних вопросов",
|
|
61
|
+
"stepAllowHint": "В ChatGPT: Bridgerator → Permission → Allow all",
|
|
62
|
+
"stepAllowBtn": "Открыть",
|
|
61
63
|
"step2": "Настроить вместе с ChatGPT",
|
|
62
64
|
"step2Hint": "Скажите в ChatGPT:",
|
|
63
65
|
"step2Phrase": "Set up Bridgerator",
|
|
@@ -274,6 +276,7 @@
|
|
|
274
276
|
"sound": "Звук",
|
|
275
277
|
"offline": "Когда ПК уходит из сети",
|
|
276
278
|
"queueDone": "Когда задача из очереди готова",
|
|
279
|
+
"queueStalled": "Когда задача из очереди зависла (ChatGPT может ждать вашего «Разрешить» в чате)",
|
|
277
280
|
"limit": "Предупреждения о лимите",
|
|
278
281
|
"preview": "Пример",
|
|
279
282
|
"light": "Светлая",
|
|
@@ -364,7 +367,8 @@
|
|
|
364
367
|
"Добавляйте задачи здесь или попросите ChatGPT спланировать работу и добавить шаги самому.",
|
|
365
368
|
"Прикрепите к задаче файлы или папки, чтобы ChatGPT знал, где искать.",
|
|
366
369
|
"Скажите в ChatGPT «Work through my Bridgerator queue» и смотрите, как появляются галочки.",
|
|
367
|
-
"Меняйте, переставляйте и удаляйте задачи когда угодно. У невыполненной задачи видно, почему."
|
|
370
|
+
"Меняйте, переставляйте и удаляйте задачи когда угодно. У невыполненной задачи видно, почему.",
|
|
371
|
+
"ChatGPT может попросить в чате разрешить действие — задача ждёт вашего ответа. Если задача зависла, Bridgerator покажет уведомление."
|
|
368
372
|
],
|
|
369
373
|
"tipLabel": "say",
|
|
370
374
|
"tip": "Work through my Bridgerator queue"
|