fraim-hub 2.0.285 → 2.0.286
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/dist/src/ai-hub/desktop-auto-updater.js +74 -0
- package/dist/src/ai-hub/desktop-main.js +25 -6
- package/dist/src/ai-hub/hosts.js +23 -0
- package/dist/src/ai-hub/server.js +12 -3
- package/dist/src/config/ai-manager-hiring.js +1 -0
- package/dist/src/config/learning-domains.js +1 -0
- package/package.json +2 -2
- package/public/ai-hub/script.js +33 -13
- package/public/ai-hub/styles.css +7 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runMandatoryDesktopUpdateCheck = runMandatoryDesktopUpdateCheck;
|
|
4
|
+
let updateCheckInFlight = null;
|
|
5
|
+
function runMandatoryDesktopUpdateCheck(options) {
|
|
6
|
+
if (!options.isPackaged)
|
|
7
|
+
return Promise.resolve({ action: 'skipped-unpackaged' });
|
|
8
|
+
if (updateCheckInFlight)
|
|
9
|
+
return updateCheckInFlight;
|
|
10
|
+
updateCheckInFlight = runMandatoryDesktopUpdateCheckOnce(options)
|
|
11
|
+
.finally(() => {
|
|
12
|
+
updateCheckInFlight = null;
|
|
13
|
+
});
|
|
14
|
+
return updateCheckInFlight;
|
|
15
|
+
}
|
|
16
|
+
async function runMandatoryDesktopUpdateCheckOnce(options) {
|
|
17
|
+
const { updater, prompt, currentVersion, logger = console } = options;
|
|
18
|
+
updater.autoDownload = false;
|
|
19
|
+
updater.autoInstallOnAppQuit = false;
|
|
20
|
+
const checkResult = await checkForDesktopUpdate(updater, prompt, currentVersion, logger);
|
|
21
|
+
if (checkResult.action === 'check-failed')
|
|
22
|
+
return checkResult;
|
|
23
|
+
if (!checkResult.update?.isUpdateAvailable) {
|
|
24
|
+
logger.info('[fraim] desktop update check found no newer version');
|
|
25
|
+
return { action: 'current', availableVersion: checkResult.update?.updateInfo?.version };
|
|
26
|
+
}
|
|
27
|
+
const availableVersion = checkResult.update.updateInfo?.version;
|
|
28
|
+
try {
|
|
29
|
+
logger.info(`[fraim] desktop update ${availableVersion ?? 'unknown'} available; downloading`);
|
|
30
|
+
if (checkResult.update.downloadPromise) {
|
|
31
|
+
await checkResult.update.downloadPromise;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
await updater.downloadUpdate();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const message = errorMessage(error);
|
|
39
|
+
logger.error(`[fraim] desktop update download failed: ${message}`);
|
|
40
|
+
await promptUpdateFailure(prompt, 'FRAIM Hub update download failed', 'FRAIM Hub found an update but could not download it. Please restart FRAIM Hub or reinstall from the latest installer.', message);
|
|
41
|
+
return { action: 'download-failed', availableVersion, error: message };
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
logger.info(`[fraim] desktop update ${availableVersion ?? 'unknown'} downloaded; installing`);
|
|
45
|
+
updater.quitAndInstall(false, true);
|
|
46
|
+
return { action: 'installing', availableVersion };
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const message = errorMessage(error);
|
|
50
|
+
logger.error(`[fraim] desktop update install failed: ${message}`);
|
|
51
|
+
await promptUpdateFailure(prompt, 'FRAIM Hub update install failed', 'FRAIM Hub downloaded an update but could not start the installer. Please restart FRAIM Hub or reinstall from the latest installer.', message);
|
|
52
|
+
return { action: 'install-failed', availableVersion, error: message };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function checkForDesktopUpdate(updater, prompt, currentVersion, logger) {
|
|
56
|
+
try {
|
|
57
|
+
logger.info(`[fraim] checking for desktop update from ${currentVersion}`);
|
|
58
|
+
return { action: 'checked', update: await updater.checkForUpdates() };
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const message = errorMessage(error);
|
|
62
|
+
logger.warn(`[fraim] desktop update check failed: ${message}`);
|
|
63
|
+
await promptUpdateFailure(prompt, 'FRAIM Hub update check failed', 'FRAIM Hub could not check for updates. It will continue starting, but this installed app may be stale.', message);
|
|
64
|
+
return { action: 'check-failed', error: message };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function promptUpdateFailure(prompt, title, message, detail) {
|
|
68
|
+
return prompt.showErrorBox(title, message, detail);
|
|
69
|
+
}
|
|
70
|
+
function errorMessage(error) {
|
|
71
|
+
if (error instanceof Error)
|
|
72
|
+
return error.message;
|
|
73
|
+
return String(error);
|
|
74
|
+
}
|
|
@@ -19,6 +19,7 @@ const bundled_asset_resolver_1 = require("./bundled-asset-resolver");
|
|
|
19
19
|
const server_2 = require("../first-run/server");
|
|
20
20
|
const session_service_1 = require("../first-run/session-service");
|
|
21
21
|
const fraim_mcp_latest_launcher_1 = require("../cli/mcp/fraim-mcp-latest-launcher");
|
|
22
|
+
const desktop_auto_updater_1 = require("./desktop-auto-updater");
|
|
22
23
|
// Keep installed, running, and user-pinned Windows shortcuts grouped under the
|
|
23
24
|
// stable identity declared in packages/fraim-hub/package.json.
|
|
24
25
|
electron_1.app.setAppUserModelId('ai.fraim.hub');
|
|
@@ -110,18 +111,33 @@ function ensureLoginItem() {
|
|
|
110
111
|
fs_1.default.mkdirSync(path_1.default.dirname(flagPath), { recursive: true });
|
|
111
112
|
fs_1.default.writeFileSync(flagPath, '1');
|
|
112
113
|
}
|
|
113
|
-
function configureAutoUpdater() {
|
|
114
|
+
async function configureAutoUpdater() {
|
|
114
115
|
if (!electron_1.app.isPackaged)
|
|
115
|
-
return;
|
|
116
|
+
return false;
|
|
116
117
|
// #1110: electron-updater compiles ~114 files (js-yaml, builder-util-runtime, ...) that a
|
|
117
118
|
// non-packaged `npx fraim-hub` launch never uses, and this whole function returns early
|
|
118
119
|
// there. Requiring it lazily keeps those file reads off the cold-start path, which is what
|
|
119
120
|
// dominates time-to-ready on a freshly unpacked install.
|
|
120
121
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
121
122
|
const { autoUpdater } = require('electron-updater');
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
const result = await (0, desktop_auto_updater_1.runMandatoryDesktopUpdateCheck)({
|
|
124
|
+
isPackaged: electron_1.app.isPackaged,
|
|
125
|
+
updater: autoUpdater,
|
|
126
|
+
currentVersion: electron_1.app.getVersion(),
|
|
127
|
+
logger: console,
|
|
128
|
+
prompt: {
|
|
129
|
+
showErrorBox: (title, message, detail) => {
|
|
130
|
+
electron_1.dialog.showErrorBox(title, detail ? `${message}\n\n${detail}` : message);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
return result.action === 'installing';
|
|
135
|
+
}
|
|
136
|
+
function checkForUpdateAfterSecondInstance() {
|
|
137
|
+
if (!electron_1.app.isPackaged || process.env.FRAIM_INSTALLER_LIFECYCLE_TEST === '1')
|
|
138
|
+
return;
|
|
139
|
+
void configureAutoUpdater().catch((err) => {
|
|
140
|
+
console.warn('[fraim] second-instance update check failed:', err);
|
|
125
141
|
});
|
|
126
142
|
}
|
|
127
143
|
// ---------------------------------------------------------------------------
|
|
@@ -467,6 +483,7 @@ async function bootstrap() {
|
|
|
467
483
|
return;
|
|
468
484
|
}
|
|
469
485
|
electron_1.app.on('second-instance', () => {
|
|
486
|
+
void electron_1.app.whenReady().then(checkForUpdateAfterSecondInstance);
|
|
470
487
|
if (mainWindow) {
|
|
471
488
|
mainWindow.show();
|
|
472
489
|
mainWindow.focus();
|
|
@@ -486,7 +503,9 @@ async function bootstrap() {
|
|
|
486
503
|
// First-launch housekeeping (idempotent, fast on subsequent runs)
|
|
487
504
|
if (process.env.FRAIM_INSTALLER_LIFECYCLE_TEST !== '1') {
|
|
488
505
|
ensureLoginItem();
|
|
489
|
-
configureAutoUpdater();
|
|
506
|
+
const installingUpdate = await configureAutoUpdater();
|
|
507
|
+
if (installingUpdate)
|
|
508
|
+
return;
|
|
490
509
|
}
|
|
491
510
|
electron_1.app.on('activate', () => {
|
|
492
511
|
// macOS: clicking dock icon re-shows the window
|
package/dist/src/ai-hub/hosts.js
CHANGED
|
@@ -2071,6 +2071,29 @@ class CliHostRuntime {
|
|
|
2071
2071
|
return null;
|
|
2072
2072
|
return active.pending.length + 1;
|
|
2073
2073
|
}
|
|
2074
|
+
stopActiveSession(hostId, sessionId) {
|
|
2075
|
+
const key = `${hostId}::${sessionId}`;
|
|
2076
|
+
const active = this.activeContinueRuns.get(key);
|
|
2077
|
+
if (!active)
|
|
2078
|
+
return false;
|
|
2079
|
+
active.pending.splice(0);
|
|
2080
|
+
this.activeContinueRuns.delete(key);
|
|
2081
|
+
if (active.child.pid == null)
|
|
2082
|
+
return false;
|
|
2083
|
+
try {
|
|
2084
|
+
this.killTree(active.child.pid, 'SIGTERM');
|
|
2085
|
+
return true;
|
|
2086
|
+
}
|
|
2087
|
+
catch (error) {
|
|
2088
|
+
console.warn('[ai-hub] failed to stop active host session process tree:', {
|
|
2089
|
+
hostId,
|
|
2090
|
+
sessionId,
|
|
2091
|
+
pid: active.child.pid,
|
|
2092
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2093
|
+
});
|
|
2094
|
+
return false;
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2074
2097
|
guardedContinue(hostId, sessionId, entry) {
|
|
2075
2098
|
const key = `${hostId}::${sessionId}`;
|
|
2076
2099
|
const active = this.activeContinueRuns.get(key);
|
|
@@ -417,7 +417,12 @@ class AiHubRunRegistry {
|
|
|
417
417
|
(0, tree_kill_1.default)(child.pid, 'SIGTERM');
|
|
418
418
|
return true;
|
|
419
419
|
}
|
|
420
|
-
catch {
|
|
420
|
+
catch (error) {
|
|
421
|
+
console.warn('[ai-hub] failed to stop run process tree:', {
|
|
422
|
+
runId,
|
|
423
|
+
pid: child.pid,
|
|
424
|
+
error: error instanceof Error ? error.message : String(error),
|
|
425
|
+
});
|
|
421
426
|
return false;
|
|
422
427
|
}
|
|
423
428
|
}
|
|
@@ -5945,7 +5950,11 @@ class AiHubServer {
|
|
|
5945
5950
|
return res.json(this.enrichRunForResponse(run));
|
|
5946
5951
|
}
|
|
5947
5952
|
this.runRegistry.update(run.id, (current) => { current.stoppedByUser = true; });
|
|
5948
|
-
const
|
|
5953
|
+
const killedRunChild = this.runRegistry.stop(run.id);
|
|
5954
|
+
const killedHostSession = run.sessionId
|
|
5955
|
+
? this.hostRuntime.stopActiveSession?.(run.hostId, run.sessionId) === true
|
|
5956
|
+
: false;
|
|
5957
|
+
const killed = killedRunChild || killedHostSession;
|
|
5949
5958
|
// Park it immediately (don't wait for onExit, which may lag or not fire on a
|
|
5950
5959
|
// host that already detached). onExit, if it fires, keeps this same state.
|
|
5951
5960
|
this.runRegistry.update(run.id, (current) => {
|
|
@@ -7309,7 +7318,7 @@ class AiHubServer {
|
|
|
7309
7318
|
const delay = recoveryBackoffMs(attempt);
|
|
7310
7319
|
const tid = setTimeout(() => {
|
|
7311
7320
|
const current = this.runRegistry.get(runId);
|
|
7312
|
-
if (!current || current.status !== 'running')
|
|
7321
|
+
if (!current || current.status !== 'running' || current.stoppedByUser)
|
|
7313
7322
|
return;
|
|
7314
7323
|
const message = classification.recoveryKind === 'compaction'
|
|
7315
7324
|
? buildHubCompactionRecoveryContinueMessage(current, exitCode, attempt)
|
|
@@ -31,6 +31,7 @@ const GENERALIST_PROFILE = {
|
|
|
31
31
|
};
|
|
32
32
|
/** Role key -> the human manager best suited to manage that AI employee. Mirror of registry/scripts/ai-manager-hiring.ts. */
|
|
33
33
|
exports.HUMAN_MANAGER_PROFILES = {
|
|
34
|
+
aida: { humanTitle: 'Head of AI Engineering', keywords: ['"Head of AI Engineering"', '"Director of AI"', '"AI Engineering Manager"', '"VP Engineering"'] },
|
|
34
35
|
maestro: { humanTitle: 'Co-Founder / General Manager', keywords: ['"Co-Founder"', '"General Manager"', '"Chief of Staff"', '"Founder"'] },
|
|
35
36
|
beza: { humanTitle: 'Head of Strategy', keywords: ['"Head of Strategy"', '"Strategy Director"', '"Chief of Staff"'] },
|
|
36
37
|
pam: { humanTitle: 'Head of Product', keywords: ['"Head of Product"', '"Group Product Manager"', '"Director of Product"'] },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.286",
|
|
4
4
|
"description": "FRAIM Hub local companion package.",
|
|
5
5
|
"author": "Sid Mathur <sid.mathur@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/mathursrus/FRAIM#readme",
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
"electron-updater": "^6.8.9",
|
|
211
211
|
"express": "^5.2.1",
|
|
212
212
|
"extract-zip": "^2.0.1",
|
|
213
|
-
"fraim": "2.0.
|
|
213
|
+
"fraim": "2.0.286",
|
|
214
214
|
"mongodb": "^7.0.0",
|
|
215
215
|
"node-cron": "4.2.1",
|
|
216
216
|
"node-edge-tts": "^1.2.10",
|
package/public/ai-hub/script.js
CHANGED
|
@@ -91,6 +91,11 @@ const state = {
|
|
|
91
91
|
cpPersonaOverride: null, // issue #945: custom:* key to stamp on conv.personaKey
|
|
92
92
|
lastRun: null, // {job, instructions, employeeId} for Cmd+Shift+R
|
|
93
93
|
configuredAgentCheckResults: {},
|
|
94
|
+
// #1389: an active Company Brand draft must survive poll-driven tfRenderCompany()
|
|
95
|
+
// calls, just as #1351 preserves an open Manager-tab configured-agent form.
|
|
96
|
+
brandEditorDraftActive: false,
|
|
97
|
+
brandEditorStatus: '',
|
|
98
|
+
brandEditorStatusWarn: false,
|
|
94
99
|
// Issue #540 R10: pending run params captured when hire strip is shown.
|
|
95
100
|
_hireStripPending: null,
|
|
96
101
|
};
|
|
@@ -13071,7 +13076,11 @@ function tfRenderCompany() {
|
|
|
13071
13076
|
// + organizational-learning-synthesis) — not in any project's job list, not as
|
|
13072
13077
|
// chips elsewhere.
|
|
13073
13078
|
// #744: render the Brand editor (its host #company-brand-editor is static in the info view).
|
|
13074
|
-
|
|
13079
|
+
// #1389: background conversation/status polls call tfRenderCompany(). Rebuilding
|
|
13080
|
+
// the editor while its draft is active replaces the focused node and resets its
|
|
13081
|
+
// closure-owned unsaved values. Save/Reset clears the guard below; the status
|
|
13082
|
+
// state keeps their feedback stable if the next poll then performs a render.
|
|
13083
|
+
if (!state.brandEditorDraftActive && typeof tfRenderBrandEditor === 'function') tfRenderBrandEditor();
|
|
13075
13084
|
const rail = document.getElementById('company-rail');
|
|
13076
13085
|
if (rail) {
|
|
13077
13086
|
rail.innerHTML = '';
|
|
@@ -14667,6 +14676,7 @@ function tfApplyOrgBrand(brand) {
|
|
|
14667
14676
|
function tfRenderBrandEditor() {
|
|
14668
14677
|
var host = document.getElementById('company-brand-editor');
|
|
14669
14678
|
if (!host) return;
|
|
14679
|
+
state.brandEditorDraftActive = false;
|
|
14670
14680
|
var draft = {
|
|
14671
14681
|
name: (state.orgBrand && state.orgBrand.name) || '',
|
|
14672
14682
|
color: (state.orgBrand && state.orgBrand.color) || '',
|
|
@@ -14689,13 +14699,13 @@ function tfRenderBrandEditor() {
|
|
|
14689
14699
|
+ ' <span class="brand-hint" id="be-contrast-hint" style="margin:0"></span></div></div>'
|
|
14690
14700
|
+ ' <div class="brand-editor-actions"><button class="send-button" type="button" id="be-save">Save brand</button>'
|
|
14691
14701
|
+ ' <button class="ghost" type="button" id="be-clear">Reset to FRAIM</button>'
|
|
14692
|
-
+ ' <span class="brand-hint" id="be-status" style="margin:0"></span></div>'
|
|
14702
|
+
+ ' <span class="brand-hint" id="be-status" role="status" aria-live="polite" style="margin:0"></span></div>'
|
|
14693
14703
|
+ ' <div class="brand-preview"><div class="brand-preview-cap">Top nav preview</div>'
|
|
14694
|
-
+ ' <nav class="hub-tabs" style="display:flex"><span class="hub-brand" id="be-prev-brand"></span>'
|
|
14704
|
+
+ ' <nav class="hub-tabs" aria-hidden="true" style="display:flex"><span class="hub-brand" id="be-prev-brand"></span>'
|
|
14695
14705
|
+ ' <span class="hub-brand-divider" id="be-prev-div"></span>'
|
|
14696
|
-
+ ' <button class="hub-tab on" type="button">Projects</button><button class="hub-tab" type="button">Company</button>'
|
|
14706
|
+
+ ' <button class="hub-tab on" type="button" tabindex="-1">Projects</button><button class="hub-tab" type="button" tabindex="-1">Company</button>'
|
|
14697
14707
|
+ ' <div class="nav-right"><span class="hub-cobrand">powered by <span class="hub-cobrand-mark"><img src="' + FRAIM_MARK_SRC + '" alt="FRAIM"></span> FRAIM</span>'
|
|
14698
|
-
+ ' <button class="avatar-btn" type="button">SM</button></div></nav></div>'
|
|
14708
|
+
+ ' <button class="avatar-btn" type="button" tabindex="-1">SM</button></div></nav></div>'
|
|
14699
14709
|
+ '</div>';
|
|
14700
14710
|
|
|
14701
14711
|
var nameEl = host.querySelector('#be-name');
|
|
@@ -14704,13 +14714,21 @@ function tfRenderBrandEditor() {
|
|
|
14704
14714
|
var dropEl = host.querySelector('#be-logo-drop');
|
|
14705
14715
|
var swatches = host.querySelector('#be-swatches');
|
|
14706
14716
|
var statusEl = host.querySelector('#be-status');
|
|
14717
|
+
host.addEventListener('focusin', function () { state.brandEditorDraftActive = true; });
|
|
14718
|
+
function setStatus(message, warn) {
|
|
14719
|
+
state.brandEditorStatus = message;
|
|
14720
|
+
state.brandEditorStatusWarn = !!warn;
|
|
14721
|
+
statusEl.textContent = message;
|
|
14722
|
+
statusEl.className = warn ? 'brand-hint warn' : 'brand-hint';
|
|
14723
|
+
}
|
|
14724
|
+
setStatus(state.brandEditorStatus || '', state.brandEditorStatusWarn);
|
|
14707
14725
|
nameEl.value = draft.name;
|
|
14708
14726
|
hexEl.value = draft.color;
|
|
14709
14727
|
|
|
14710
14728
|
BRAND_COLOR_PRESETS.forEach(function (c) {
|
|
14711
14729
|
var b = document.createElement('button');
|
|
14712
14730
|
b.type = 'button'; b.className = 'brand-swatch'; b.style.background = c; b.title = c;
|
|
14713
|
-
b.addEventListener('click', function () { draft.color = c; hexEl.value = c; refresh(); });
|
|
14731
|
+
b.addEventListener('click', function () { setStatus('', false); draft.color = c; hexEl.value = c; refresh(); });
|
|
14714
14732
|
swatches.appendChild(b);
|
|
14715
14733
|
});
|
|
14716
14734
|
|
|
@@ -14744,14 +14762,15 @@ function tfRenderBrandEditor() {
|
|
|
14744
14762
|
}
|
|
14745
14763
|
}
|
|
14746
14764
|
|
|
14747
|
-
nameEl.addEventListener('input', function () { draft.name = nameEl.value; refresh(); });
|
|
14748
|
-
hexEl.addEventListener('input', function () { draft.color = hexEl.value; refresh(); });
|
|
14765
|
+
nameEl.addEventListener('input', function () { setStatus('', false); draft.name = nameEl.value; refresh(); });
|
|
14766
|
+
hexEl.addEventListener('input', function () { setStatus('', false); draft.color = hexEl.value; refresh(); });
|
|
14749
14767
|
dropEl.addEventListener('click', function () { fileEl.click(); });
|
|
14750
14768
|
// Keyboard access (a11y): Enter/Space on the focusable drop zone opens the picker.
|
|
14751
14769
|
dropEl.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); fileEl.click(); } });
|
|
14752
14770
|
fileEl.addEventListener('change', function () {
|
|
14753
14771
|
var f = fileEl.files && fileEl.files[0];
|
|
14754
14772
|
if (!f) return;
|
|
14773
|
+
setStatus('', false);
|
|
14755
14774
|
if (f.size > 512 * 1024) { host.querySelector('#be-logo-hint').textContent = 'That file is over 512 KB. Pick a smaller logo.'; host.querySelector('#be-logo-hint').className = 'brand-hint warn'; return; }
|
|
14756
14775
|
var reader = new FileReader();
|
|
14757
14776
|
reader.onload = function () {
|
|
@@ -14763,23 +14782,24 @@ function tfRenderBrandEditor() {
|
|
|
14763
14782
|
});
|
|
14764
14783
|
|
|
14765
14784
|
host.querySelector('#be-save').addEventListener('click', function () {
|
|
14766
|
-
|
|
14785
|
+
setStatus('Saving…', false);
|
|
14767
14786
|
var body = { name: draft.name, color: draft.color, logo: draft.logo };
|
|
14768
14787
|
if (state.projectPath) body.projectPath = state.projectPath;
|
|
14769
14788
|
requestJson('/api/ai-hub/brand', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
|
|
14770
14789
|
.then(function (resp) {
|
|
14771
14790
|
tfApplyOrgBrand(resp && resp.brand ? resp.brand : null);
|
|
14772
|
-
|
|
14791
|
+
state.brandEditorDraftActive = false;
|
|
14792
|
+
setStatus('Saved. Applied to your Hub.', false);
|
|
14773
14793
|
tfShowBrandTeamNote(host);
|
|
14774
14794
|
})
|
|
14775
|
-
.catch(function (err) {
|
|
14795
|
+
.catch(function (err) { setStatus('Save failed: ' + (err && err.message ? err.message : 'error'), true); });
|
|
14776
14796
|
});
|
|
14777
14797
|
host.querySelector('#be-clear').addEventListener('click', function () {
|
|
14778
14798
|
var body = { name: '', color: '', logo: '' };
|
|
14779
14799
|
if (state.projectPath) body.projectPath = state.projectPath;
|
|
14780
14800
|
requestJson('/api/ai-hub/brand', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
|
|
14781
|
-
.then(function () { tfApplyOrgBrand(null); tfRenderBrandEditor(); })
|
|
14782
|
-
.catch(function () {});
|
|
14801
|
+
.then(function () { state.brandEditorDraftActive = false; setStatus('', false); tfApplyOrgBrand(null); tfRenderBrandEditor(); })
|
|
14802
|
+
.catch(function (err) { setStatus('Reset failed: ' + (err && err.message ? err.message : 'error'), true); });
|
|
14783
14803
|
});
|
|
14784
14804
|
|
|
14785
14805
|
refresh();
|
package/public/ai-hub/styles.css
CHANGED
|
@@ -5407,11 +5407,15 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
5407
5407
|
.hub-cobrand[hidden] { display: none; }
|
|
5408
5408
|
.hub-cobrand-mark { width: 17px; height: 17px; border-radius: 5px; display: block; }
|
|
5409
5409
|
.hub-cobrand-mark img { width: 100%; height: 100%; object-fit: contain; display: block; border-radius: 5px; }
|
|
5410
|
+
/* Tablet-width navs cannot hold the company lockup, tabs, search, co-mark, and
|
|
5411
|
+
account control together. Keep the functional controls and shed the co-mark. */
|
|
5412
|
+
@media (max-width: 820px) {
|
|
5413
|
+
.hub-cobrand { display: none; }
|
|
5414
|
+
}
|
|
5410
5415
|
/* Very narrow windows (the Hub is desktop/Electron-first, but keep the dense nav
|
|
5411
5416
|
from forcing horizontal page scroll): drop the co-mark and the company name,
|
|
5412
5417
|
keeping the company logo mark + tabs. */
|
|
5413
5418
|
@media (max-width: 560px) {
|
|
5414
|
-
.hub-cobrand { display: none; }
|
|
5415
5419
|
.hub-brand-name { display: none; }
|
|
5416
5420
|
.hub-brand { padding: 0 2px 0 10px; }
|
|
5417
5421
|
.hub-brand-divider { margin: 0 4px 0 8px; }
|
|
@@ -5431,6 +5435,7 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
5431
5435
|
.brand-field .brand-hint { font-size: 12px; color: var(--muted); margin-top: 5px; }
|
|
5432
5436
|
.brand-field .brand-hint.warn { color: var(--warn); }
|
|
5433
5437
|
.brand-logo-drop { display: flex; align-items: center; gap: 14px; border: 1px dashed color-mix(in srgb, var(--accent) 40%, var(--line)); border-radius: 12px; padding: 14px; background: var(--accent-soft); cursor: pointer; }
|
|
5438
|
+
.brand-logo-drop:focus-visible, .brand-swatch:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
|
|
5434
5439
|
.brand-logo-drop .blp { width: 44px; height: 44px; border-radius: 10px; box-shadow: 0 0 0 1px var(--line); flex-shrink: 0; overflow: hidden; background: var(--surface); display: flex; align-items: center; justify-content: center; }
|
|
5435
5440
|
.brand-logo-drop .blp svg, .brand-logo-drop .blp img { width: 100%; height: 100%; object-fit: contain; }
|
|
5436
5441
|
.brand-logo-drop .blp-txt { font-size: 12.5px; color: var(--text); }
|
|
@@ -5439,6 +5444,7 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
5439
5444
|
.brand-swatch.sel { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent); }
|
|
5440
5445
|
.brand-color-hex { width: 96px; }
|
|
5441
5446
|
.brand-editor-actions { display: flex; gap: 10px; margin-top: 6px; align-items: center; }
|
|
5447
|
+
[data-theme="dark"] .brand-editor-actions .send-button { color: var(--bg); }
|
|
5442
5448
|
.brand-preview { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; margin-top: 14px; }
|
|
5443
5449
|
.brand-preview-cap { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); padding: 10px 14px; background: var(--bg); border-bottom: 1px solid var(--line); }
|
|
5444
5450
|
|