pinokiod 3.297.0 → 3.298.0

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/kernel/index.js CHANGED
@@ -737,7 +737,57 @@ class Kernel {
737
737
  })
738
738
  }
739
739
  }
740
+ _readWindowsPath(scope) {
741
+ try {
742
+ const cmd = `powershell.exe -NoProfile -Command "[Environment]::GetEnvironmentVariable('Path','${scope}')"`
743
+ return execSync(cmd, { encoding: "utf-8" }).trim()
744
+ } catch (e) {
745
+ return ""
746
+ }
747
+ }
748
+ refreshPath() {
749
+ if (!this.envs) {
750
+ return
751
+ }
752
+ let pathKey
753
+ if (this.envs.Path) {
754
+ pathKey = "Path"
755
+ } else if (this.envs.PATH) {
756
+ pathKey = "PATH"
757
+ }
758
+ if (!pathKey) {
759
+ return
760
+ }
761
+ let refreshed
762
+ if (this.platform === "win32") {
763
+ const machinePath = this._readWindowsPath("Machine")
764
+ const userPath = this._readWindowsPath("User")
765
+ const segments = [machinePath, userPath].filter(Boolean)
766
+ if (segments.length > 0) {
767
+ refreshed = segments.join(path.delimiter)
768
+ }
769
+ } else {
770
+ try {
771
+ refreshed = shellPath.sync()
772
+ } catch (e) {
773
+ refreshed = null
774
+ }
775
+ }
776
+ if (!refreshed) {
777
+ return
778
+ }
779
+ const current = this.envs[pathKey] || ""
780
+ if (this.shellpath && current.includes(this.shellpath)) {
781
+ this.envs[pathKey] = current.replace(this.shellpath, refreshed)
782
+ } else if (!current) {
783
+ this.envs[pathKey] = refreshed
784
+ } else {
785
+ this.envs[pathKey] = `${refreshed}${path.delimiter}${current}`
786
+ }
787
+ this.shellpath = refreshed
788
+ }
740
789
  which(name, pattern) {
790
+ this.refreshPath()
741
791
  if (this.platform === "win32") {
742
792
  try {
743
793
  const result = execSync(`where ${name}`, { env: this.envs, encoding: "utf-8" })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "3.297.0",
3
+ "version": "3.298.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -1903,6 +1903,7 @@ class Server {
1903
1903
  let logpath = encodeURIComponent(Util.log_path(filepath, this.kernel))
1904
1904
  const result = {
1905
1905
  portal: this.portal,
1906
+ projectName: (pathComponents.length > 0 ? pathComponents[0] : ''),
1906
1907
  kill_message,
1907
1908
  callback,
1908
1909
  callback_target,
@@ -52,9 +52,39 @@ function check_ready () {
52
52
 
53
53
  function check_dev () {
54
54
  createLauncherDebugLog('check_dev start');
55
- return fetch('/bundle/dev').then((response) => response.json()).then((payload) => {
55
+ let controller = null;
56
+ let timeoutId = null;
57
+ if (typeof AbortController === 'function') {
58
+ try {
59
+ controller = new AbortController();
60
+ timeoutId = setTimeout(() => {
61
+ try {
62
+ controller.abort();
63
+ } catch (_) {}
64
+ }, 7000);
65
+ } catch (_) {
66
+ controller = null;
67
+ }
68
+ }
69
+
70
+ const fetchPromise = fetch('/bundle/dev', controller ? { signal: controller.signal } : undefined).then((response) => response.json());
71
+ const timedPromise = controller ? fetchPromise : Promise.race([
72
+ fetchPromise,
73
+ new Promise((_, reject) => setTimeout(() => reject(new Error('dev status timeout')), 7000))
74
+ ]);
75
+
76
+ return timedPromise.then((payload) => {
77
+ if (timeoutId) {
78
+ clearTimeout(timeoutId);
79
+ }
56
80
  createLauncherDebugLog('check_dev response', payload);
57
81
  return payload
82
+ }).catch((error) => {
83
+ if (timeoutId) {
84
+ clearTimeout(timeoutId);
85
+ }
86
+ createLauncherDebugLog('check_dev error', error);
87
+ return { available: null, transientError: true }
58
88
  })
59
89
  }
60
90
 
@@ -72,15 +102,7 @@ function wait_ready () {
72
102
  check_ready().then((ready) => {
73
103
  createLauncherDebugLog('wait_ready initial requirements readiness', ready);
74
104
  if (ready) {
75
- check_dev().then((data) => {
76
- const available = !(data && data.available === false)
77
- createLauncherDebugLog('wait_ready dev bundle availability (initial)', available, data);
78
- if (available) {
79
- resolve({ closeModal: null, ready: true })
80
- } else {
81
- resolve({ closeModal: null, ready: false })
82
- }
83
- })
105
+ ensureDevReady().then(resolve)
84
106
  } else {
85
107
  let loader = createMinimalLoadingSwal();
86
108
  let interval = setInterval(() => {
@@ -88,15 +110,7 @@ function wait_ready () {
88
110
  createLauncherDebugLog('wait_ready polling requirements readiness', ready);
89
111
  if (ready) {
90
112
  clearInterval(interval)
91
- check_dev().then((data) => {
92
- const available = !(data && data.available === false)
93
- createLauncherDebugLog('wait_ready dev bundle availability (after poll)', available, data);
94
- if (available) {
95
- resolve({ ready: true, closeModal: loader })
96
- } else {
97
- resolve({ ready: false, closeModal: loader })
98
- }
99
- })
113
+ ensureDevReady(loader, 'after poll').then(resolve)
100
114
  }
101
115
  })
102
116
  }, 500)
@@ -105,6 +119,33 @@ function wait_ready () {
105
119
  })
106
120
  }
107
121
 
122
+ function ensureDevReady(existingLoader = null, label = 'initial') {
123
+ let loader = existingLoader;
124
+ const ensureLoader = () => {
125
+ if (!loader) {
126
+ loader = createMinimalLoadingSwal();
127
+ }
128
+ return loader;
129
+ };
130
+
131
+ return new Promise((resolve) => {
132
+ const attempt = (contextLabel) => {
133
+ check_dev().then((data) => {
134
+ if (data && data.transientError) {
135
+ createLauncherDebugLog('wait_ready dev bundle transient error', data);
136
+ ensureLoader();
137
+ setTimeout(() => attempt('retry'), 500);
138
+ return;
139
+ }
140
+ const available = !(data && data.available === false)
141
+ createLauncherDebugLog('wait_ready dev bundle availability (' + contextLabel + ')', available, data);
142
+ resolve({ ready: available, closeModal: loader })
143
+ })
144
+ };
145
+ attempt(label)
146
+ })
147
+ }
148
+
108
149
  function collectPostMessageTargets(contextWindow) {
109
150
  const ctx = contextWindow || window;
110
151
  const targets = new Set();
@@ -3215,12 +3256,14 @@ document.addEventListener("DOMContentLoaded", () => {
3215
3256
 
3216
3257
  function initializeCreateLauncherIntegration() {
3217
3258
  const defaults = parseCreateLauncherDefaults();
3218
- const triggerExists = document.getElementById('create-launcher-button');
3259
+ const createTrigger = document.getElementById('create-launcher-button');
3260
+ const askTriggers = Array.from(document.querySelectorAll('[data-ask-ai-trigger]'));
3219
3261
  createLauncherDebugLog('initializeCreateLauncherIntegration', {
3220
3262
  defaultsPresent: Boolean(defaults),
3221
- triggerExists: Boolean(triggerExists)
3263
+ triggerExists: Boolean(createTrigger),
3264
+ askTriggerCount: askTriggers.length
3222
3265
  });
3223
- if (!triggerExists && !defaults) {
3266
+ if (!createTrigger && askTriggers.length === 0 && !defaults) {
3224
3267
  createLauncherDebugLog('initializeCreateLauncherIntegration aborted (no trigger/defaults)');
3225
3268
  return;
3226
3269
  }
@@ -3235,6 +3278,7 @@ document.addEventListener("DOMContentLoaded", () => {
3235
3278
  return;
3236
3279
  }
3237
3280
  initCreateLauncherTrigger(api);
3281
+ initAskAiTrigger(api);
3238
3282
  openPendingCreateLauncherModal(api);
3239
3283
  });
3240
3284
  }
@@ -3288,6 +3332,90 @@ document.addEventListener("DOMContentLoaded", () => {
3288
3332
  });
3289
3333
  }
3290
3334
 
3335
+ function initAskAiTrigger(api) {
3336
+ const triggers = Array.from(document.querySelectorAll('[data-ask-ai-trigger]'));
3337
+ if (triggers.length === 0) {
3338
+ createLauncherDebugLog('initAskAiTrigger: trigger not found');
3339
+ return;
3340
+ }
3341
+ triggers.forEach((trigger) => {
3342
+ if (trigger.dataset.askAiInit === 'true') {
3343
+ return;
3344
+ }
3345
+ trigger.dataset.askAiInit = 'true';
3346
+ createLauncherDebugLog('initAskAiTrigger: binding click handler');
3347
+ trigger.addEventListener('click', () => {
3348
+ const workspace = deriveWorkspaceForAskAi(trigger);
3349
+ const defaults = {
3350
+ variant: 'ask',
3351
+ };
3352
+ if (workspace) {
3353
+ defaults.folder = workspace;
3354
+ defaults.projectName = workspace;
3355
+ }
3356
+ guardCreateLauncher(api, defaults);
3357
+ });
3358
+ });
3359
+ }
3360
+
3361
+ function deriveWorkspaceForAskAi(trigger) {
3362
+ const direct = (trigger && trigger.dataset && typeof trigger.dataset.workspace === 'string')
3363
+ ? trigger.dataset.workspace.trim()
3364
+ : '';
3365
+ if (direct) {
3366
+ return direct;
3367
+ }
3368
+ const bodyWorkspace = document.body && document.body.dataset && typeof document.body.dataset.workspace === 'string'
3369
+ ? document.body.dataset.workspace.trim()
3370
+ : '';
3371
+ if (bodyWorkspace) {
3372
+ return bodyWorkspace;
3373
+ }
3374
+ const workspaceElement = document.querySelector('[data-workspace]');
3375
+ if (workspaceElement) {
3376
+ const attr = workspaceElement.getAttribute('data-workspace');
3377
+ if (attr && attr.trim()) {
3378
+ return attr.trim();
3379
+ }
3380
+ }
3381
+ const pathValue = (window.location && typeof window.location.pathname === 'string')
3382
+ ? window.location.pathname
3383
+ : '';
3384
+ if (!pathValue) {
3385
+ return '';
3386
+ }
3387
+ const patterns = [
3388
+ /\/_api\/([^/]+)/i,
3389
+ /\/api\/([^/]+)/i,
3390
+ /\/p\/([^/]+)/i,
3391
+ /\/pinokio\/fileview\/([^/]+)/i,
3392
+ /\/pinokio\/terminal\/([^/]+)/i,
3393
+ /\/pinokio\/editor\/([^/]+)/i,
3394
+ ];
3395
+ for (let i = 0; i < patterns.length; i += 1) {
3396
+ const match = patterns[i].exec(pathValue);
3397
+ if (match && match[1]) {
3398
+ return safeDecodeURIComponent(match[1]);
3399
+ }
3400
+ }
3401
+ const segments = pathValue.split('/').filter(Boolean);
3402
+ if (segments.length > 0) {
3403
+ return safeDecodeURIComponent(segments[segments.length - 1]);
3404
+ }
3405
+ return '';
3406
+ }
3407
+
3408
+ function safeDecodeURIComponent(value) {
3409
+ if (typeof value !== 'string') {
3410
+ return '';
3411
+ }
3412
+ try {
3413
+ return decodeURIComponent(value);
3414
+ } catch (_) {
3415
+ return value;
3416
+ }
3417
+ }
3418
+
3291
3419
  function openPendingCreateLauncherModal(api) {
3292
3420
  if (!api || !createLauncherState.pendingDefaults) {
3293
3421
  return;
@@ -33,6 +33,14 @@
33
33
  ];
34
34
 
35
35
  const CATEGORY_ORDER = ['CLI', 'IDE'];
36
+ const MODAL_VARIANTS = {
37
+ CREATE: 'create',
38
+ ASK: 'ask',
39
+ };
40
+ const CREATE_PROMPT_PLACEHOLDER = 'Examples: "a 1-click launcher for ComfyUI", "I want to change file format", "I want to clone a website to run locally", etc. (Leave empty to decide later)';
41
+ const ASK_PROMPT_PLACEHOLDER = 'Examples: "Fix it", "Can you make this dark theme?", "What does this do?", "How does X feature work?", "What should i do to X".';
42
+ const CREATE_DESCRIPTION = 'Create a reusable and shareable launcher for any task or any app';
43
+ const ASK_DESCRIPTION = 'Ask the AI to customize, fix, or explain how the app works.';
36
44
 
37
45
  let cachedTools = null;
38
46
  let loadingTools = null;
@@ -361,6 +369,7 @@
361
369
 
362
370
  const container = document.createElement('div');
363
371
  container.className = isPage ? 'create-launcher-page-card' : 'create-launcher-modal';
372
+ container.dataset.variant = MODAL_VARIANTS.CREATE;
364
373
 
365
374
  const header = document.createElement('div');
366
375
  header.className = 'create-launcher-modal-header';
@@ -384,7 +393,7 @@
384
393
  const description = document.createElement('p');
385
394
  description.className = 'create-launcher-modal-description';
386
395
  description.id = `${mode}-create-launcher-description`;
387
- description.textContent = 'Create a reusable and shareable launcher for any task or any app';
396
+ description.textContent = CREATE_DESCRIPTION;
388
397
 
389
398
  headingStack.appendChild(title);
390
399
  headingStack.appendChild(description);
@@ -408,7 +417,7 @@
408
417
 
409
418
  const promptTextarea = document.createElement('textarea');
410
419
  promptTextarea.className = 'create-launcher-modal-textarea';
411
- promptTextarea.placeholder = 'Examples: "a 1-click launcher for ComfyUI", "I want to change file format", "I want to clone a website to run locally", etc. (Leave empty to decide later)';
420
+ promptTextarea.placeholder = CREATE_PROMPT_PLACEHOLDER;
412
421
  promptLabel.appendChild(promptTextarea);
413
422
 
414
423
  const templateWrapper = document.createElement('div');
@@ -501,7 +510,7 @@
501
510
 
502
511
  promptTextarea.addEventListener('input', () => {
503
512
  templateManager.syncTemplateFields(promptTextarea.value);
504
- if (!folderEditedByUser) {
513
+ if (!folderEditedByUser && container.dataset.variant !== MODAL_VARIANTS.ASK) {
505
514
  folderInput.value = generateFolderSuggestion(promptTextarea.value);
506
515
  }
507
516
  });
@@ -510,8 +519,11 @@
510
519
  mode,
511
520
  overlay,
512
521
  container,
522
+ title,
523
+ description,
513
524
  promptTextarea,
514
525
  folderInput,
526
+ folderLabel,
515
527
  templateWrapper,
516
528
  templateFields,
517
529
  templateManager,
@@ -522,6 +534,9 @@
522
534
  closeButton,
523
535
  advancedLink,
524
536
  bookmarkletLink,
537
+ linkRow,
538
+ currentVariant: MODAL_VARIANTS.CREATE,
539
+ projectName: '',
525
540
  resetFolderTracking() {
526
541
  folderEditedByUser = false;
527
542
  },
@@ -531,18 +546,48 @@
531
546
  };
532
547
  }
533
548
 
549
+ function applyVariantToUi(ui, variant = MODAL_VARIANTS.CREATE) {
550
+ if (!ui) return;
551
+ const targetVariant = variant === MODAL_VARIANTS.ASK ? MODAL_VARIANTS.ASK : MODAL_VARIANTS.CREATE;
552
+ const isAsk = targetVariant === MODAL_VARIANTS.ASK;
553
+ ui.currentVariant = targetVariant;
554
+ if (ui.container) {
555
+ ui.container.dataset.variant = targetVariant;
556
+ }
557
+ if (ui.title) {
558
+ ui.title.textContent = isAsk ? 'Ask AI' : 'Create';
559
+ }
560
+ if (ui.description) {
561
+ ui.description.textContent = isAsk ? ASK_DESCRIPTION : CREATE_DESCRIPTION;
562
+ }
563
+ if (ui.promptTextarea) {
564
+ ui.promptTextarea.placeholder = isAsk ? ASK_PROMPT_PLACEHOLDER : CREATE_PROMPT_PLACEHOLDER;
565
+ }
566
+ if (ui.folderLabel) {
567
+ ui.folderLabel.style.display = isAsk ? 'none' : '';
568
+ }
569
+ if (ui.linkRow) {
570
+ ui.linkRow.style.display = isAsk ? 'none' : '';
571
+ }
572
+ if (ui.confirmButton) {
573
+ ui.confirmButton.textContent = isAsk ? 'Ask' : 'Create';
574
+ }
575
+ }
576
+
534
577
  function applyDefaultsToUi(ui, defaults = {}) {
535
578
  if (!ui) return;
536
579
  const promptValue = typeof defaults.prompt === 'string' ? defaults.prompt : '';
580
+ const projectName = typeof defaults.projectName === 'string' ? defaults.projectName.trim() : '';
537
581
  const folderValue = typeof defaults.folder === 'string' && defaults.folder.trim()
538
582
  ? defaults.folder.trim()
539
- : generateFolderSuggestion(promptValue);
583
+ : (projectName || generateFolderSuggestion(promptValue));
540
584
  const toolValue = typeof defaults.tool === 'string' ? defaults.tool.trim() : '';
541
585
  const templateDefaults = defaults.templateValues || {};
542
586
 
543
587
  ui.promptTextarea.value = promptValue;
544
588
  ui.templateManager.syncTemplateFields(promptValue, templateDefaults);
545
589
  ui.folderInput.value = folderValue || '';
590
+ ui.projectName = projectName || '';
546
591
  ui.resetFolderTracking();
547
592
 
548
593
  if (toolValue) {
@@ -590,7 +635,10 @@
590
635
  if (!ui) return;
591
636
  ui.error.textContent = '';
592
637
 
638
+ const variant = ui.currentVariant || MODAL_VARIANTS.CREATE;
639
+ const isAskVariant = variant === MODAL_VARIANTS.ASK;
593
640
  const folderName = ui.folderInput.value.trim();
641
+ const targetProject = isAskVariant ? (ui.projectName || folderName) : folderName;
594
642
  const rawPrompt = ui.promptTextarea.value;
595
643
  const templateValues = readTemplateValues(ui);
596
644
  const selectedTool = getSelectedTool(ui);
@@ -600,16 +648,18 @@
600
648
  return;
601
649
  }
602
650
 
603
- if (!folderName) {
604
- ui.error.textContent = 'Please enter a folder name.';
605
- ui.folderInput.focus();
606
- return;
607
- }
651
+ if (!isAskVariant) {
652
+ if (!folderName) {
653
+ ui.error.textContent = 'Please enter a folder name.';
654
+ ui.folderInput.focus();
655
+ return;
656
+ }
608
657
 
609
- if (folderName.includes(' ')) {
610
- ui.error.textContent = 'Folder names cannot contain spaces.';
611
- ui.folderInput.focus();
612
- return;
658
+ if (folderName.includes(' ')) {
659
+ ui.error.textContent = 'Folder names cannot contain spaces.';
660
+ ui.folderInput.focus();
661
+ return;
662
+ }
613
663
  }
614
664
 
615
665
  let finalPrompt = rawPrompt;
@@ -636,6 +686,17 @@
636
686
  }
637
687
 
638
688
  const prompt = finalPrompt.trim();
689
+
690
+ if (isAskVariant) {
691
+ const params = new URLSearchParams();
692
+ params.set('plugin', `/plugin/code/${selectedTool}/pinokio.js`);
693
+ if (prompt) {
694
+ params.set('prompt', prompt);
695
+ }
696
+ window.location.href = `/p/${targetProject}/dev?${params.toString()}`;
697
+ return;
698
+ }
699
+
639
700
  const params = new URLSearchParams();
640
701
  params.set('name', folderName);
641
702
  params.set('message', prompt);
@@ -684,13 +745,18 @@
684
745
  return;
685
746
  }
686
747
 
687
- applyDefaultsToUi(ui, defaults);
688
- ui.templateManager.syncTemplateFields(ui.promptTextarea.value, defaults.templateValues || {});
748
+ const options = (defaults && typeof defaults === 'object') ? defaults : {};
749
+ const { variant, ...restDefaults } = options;
750
+ applyVariantToUi(ui, variant);
751
+ applyDefaultsToUi(ui, restDefaults);
752
+ ui.templateManager.syncTemplateFields(ui.promptTextarea.value, restDefaults.templateValues || {});
689
753
 
690
754
  requestAnimationFrame(() => {
691
755
  ui.overlay.classList.add('is-visible');
692
756
  requestAnimationFrame(() => {
693
- ui.folderInput.select();
757
+ if (ui.currentVariant !== MODAL_VARIANTS.ASK && ui.folderInput) {
758
+ ui.folderInput.select();
759
+ }
694
760
  ui.promptTextarea.focus();
695
761
  });
696
762
  });
@@ -9,7 +9,7 @@
9
9
  display: flex;
10
10
  align-items: center;
11
11
  justify-content: center;
12
- z-index: 9999;
12
+ z-index: 100000002;
13
13
  opacity: 0;
14
14
  visibility: hidden;
15
15
  pointer-events: none;
@@ -13,6 +13,7 @@
13
13
  <link href="/filepond-plugin-image-preview.min.css" rel="stylesheet" />
14
14
  <link href="/filepond-plugin-image-edit.min.css" rel="stylesheet" />
15
15
  <link href="/style.css" rel="stylesheet"/>
16
+ <link href="/urldropdown.css" rel="stylesheet" />
16
17
  <script src="/modalinput.js"></script>
17
18
  <script src="/pinokio-touch.js"></script>
18
19
  <% if (agent === "electron") { %>
@@ -3042,6 +3043,13 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3042
3043
  <div class='flexible'></div>
3043
3044
  </div>
3044
3045
  </a>
3046
+ <button type='button' id='ask-ai-tab' class="btn header-item" data-static="ask-ai" data-workspace="<%=name%>" data-ask-ai-trigger="true">
3047
+ <div class='tab'>
3048
+ <i class="fa-solid fa-robot"></i>
3049
+ <div class='display'>Ask AI</div>
3050
+ <div class='flexible'></div>
3051
+ </div>
3052
+ </button>
3045
3053
  </div>
3046
3054
  <% } %>
3047
3055
  <div class='m s temp-menu' data-type='s'>
@@ -3948,14 +3956,14 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3948
3956
  return false
3949
3957
  }
3950
3958
  // Rebuild a navigation container while keeping any active selection intact.
3951
- const rerenderMenuSection = (container, html) => {
3952
- const targetContainer = typeof container === 'string' ? document.querySelector(container) : container
3953
- if (!targetContainer) {
3954
- return
3955
- }
3956
- const staticNodes = Array.from(targetContainer.children || []).filter((node) => {
3957
- return node && node.dataset && node.dataset.static === 'retain'
3958
- })
3959
+ const rerenderMenuSection = (container, html) => {
3960
+ const targetContainer = typeof container === 'string' ? document.querySelector(container) : container
3961
+ if (!targetContainer) {
3962
+ return
3963
+ }
3964
+ const staticNodes = Array.from(targetContainer.children || []).filter((node) => {
3965
+ return node && node.dataset && node.dataset.static
3966
+ })
3959
3967
  const template = document.createElement('template')
3960
3968
  template.innerHTML = html || ''
3961
3969
  const fragment = template.content
@@ -16,39 +16,117 @@
16
16
  <link href="/electron.css" rel="stylesheet"/>
17
17
  <% } %>
18
18
  <style>
19
- body {
19
+ body[data-page="connect"] {
20
+ --connect-body-bg: #f8fafc;
21
+ --connect-text: #0f172a;
22
+ --connect-heading: #1f2937;
23
+ --connect-card-bg: #ffffff;
24
+ --connect-card-border: rgba(15, 23, 42, 0.08);
25
+ --connect-card-shadow: 0 15px 35px rgba(15, 23, 42, 0.08);
26
+ --connect-user-info-bg: #f0f9ff;
27
+ --connect-user-info-border: #3b82f6;
28
+ --connect-status-success-bg: #d1fae5;
29
+ --connect-status-success-text: #065f46;
30
+ --connect-status-error-bg: #fee2e2;
31
+ --connect-status-error-text: #991b1b;
32
+ --connect-status-warning-bg: #fef3c7;
33
+ --connect-status-warning-text: #92400e;
34
+ --connect-loader-overlay: rgba(248, 250, 252, 0.95);
35
+ --connect-loader-text: #374151;
36
+ --connect-loader-border: #6b7280;
37
+ --connect-btn-bg: #000000;
38
+ --connect-btn-hover-bg: #111827;
39
+ --connect-btn-text: #ffffff;
40
+ --connect-secondary-btn-bg: #6b7280;
41
+ --connect-secondary-btn-hover: #4b5563;
20
42
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
21
- background: #f8fafc;
22
- }
23
- /*
24
- .container {
25
- background: white;
26
- padding: 30px;
27
- border-radius: 10px;
28
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
43
+ background: var(--connect-body-bg);
44
+ color: var(--connect-text);
45
+ margin: 0;
46
+ min-height: 100vh;
47
+ color-scheme: light;
48
+ }
49
+
50
+ @media (prefers-color-scheme: dark) {
51
+ body[data-page="connect"] {
52
+ --connect-body-bg: #010617;
53
+ --connect-text: #e2e8f0;
54
+ --connect-heading: #f8fafc;
55
+ --connect-card-bg: #0f172a;
56
+ --connect-card-border: rgba(148, 163, 184, 0.2);
57
+ --connect-card-shadow: 0 25px 35px rgba(0, 0, 0, 0.65);
58
+ --connect-user-info-bg: rgba(59, 130, 246, 0.12);
59
+ --connect-user-info-border: #60a5fa;
60
+ --connect-status-success-bg: rgba(16, 185, 129, 0.18);
61
+ --connect-status-success-text: #6ee7b7;
62
+ --connect-status-error-bg: rgba(248, 113, 113, 0.16);
63
+ --connect-status-error-text: #fecaca;
64
+ --connect-status-warning-bg: rgba(251, 191, 36, 0.16);
65
+ --connect-status-warning-text: #fde68a;
66
+ --connect-loader-overlay: rgba(2, 6, 23, 0.9);
67
+ --connect-loader-text: #e2e8f0;
68
+ --connect-loader-border: rgba(148, 163, 184, 0.7);
69
+ --connect-btn-bg: #ff6b35;
70
+ --connect-btn-hover-bg: #e75a2a;
71
+ --connect-btn-text: #ffffff;
72
+ --connect-secondary-btn-bg: #475569;
73
+ --connect-secondary-btn-hover: #334155;
74
+ color-scheme: dark;
75
+ }
29
76
  }
30
- */
31
- .container {
32
- background: white;
77
+
78
+ body.dark[data-page="connect"] {
79
+ --connect-body-bg: #010617;
80
+ --connect-text: #e2e8f0;
81
+ --connect-heading: #f8fafc;
82
+ --connect-card-bg: #0f172a;
83
+ --connect-card-border: rgba(148, 163, 184, 0.2);
84
+ --connect-card-shadow: 0 25px 35px rgba(0, 0, 0, 0.65);
85
+ --connect-user-info-bg: rgba(59, 130, 246, 0.12);
86
+ --connect-user-info-border: #60a5fa;
87
+ --connect-status-success-bg: rgba(16, 185, 129, 0.18);
88
+ --connect-status-success-text: #6ee7b7;
89
+ --connect-status-error-bg: rgba(248, 113, 113, 0.16);
90
+ --connect-status-error-text: #fecaca;
91
+ --connect-status-warning-bg: rgba(251, 191, 36, 0.16);
92
+ --connect-status-warning-text: #fde68a;
93
+ --connect-loader-overlay: rgba(2, 6, 23, 0.9);
94
+ --connect-loader-text: #e2e8f0;
95
+ --connect-loader-border: rgba(148, 163, 184, 0.7);
96
+ --connect-btn-bg: #ff6b35;
97
+ --connect-btn-hover-bg: #e75a2a;
98
+ --connect-btn-text: #ffffff;
99
+ --connect-secondary-btn-bg: #475569;
100
+ --connect-secondary-btn-hover: #334155;
101
+ color-scheme: dark;
102
+ }
103
+
104
+ body[data-page="connect"] .container {
105
+ background: var(--connect-card-bg);
33
106
  max-width: 600px;
34
107
  margin: 0 auto;
35
108
  padding: 30px;
36
109
  border-radius: 10px;
37
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
110
+ box-shadow: var(--connect-card-shadow);
111
+ border: 1px solid var(--connect-card-border);
112
+ color: var(--connect-text);
38
113
  }
39
- h1 {
114
+
115
+ body[data-page="connect"] header.head h1 {
40
116
  text-transform: capitalize;
41
- color: #1f2937;
117
+ color: var(--connect-heading);
42
118
  margin: 0;
43
119
  font-size: 40px;
44
120
  margin-top: 10px;
45
121
  }
46
- .btn {
122
+
123
+ body[data-page="connect"] .btn {
47
124
  text-decoration: none;
48
- display: inline-block;
49
- background: black;
125
+ display: inline-flex;
126
+ justify-content: center;
127
+ background: var(--connect-btn-bg);
50
128
  width: 100%;
51
- color: white;
129
+ color: var(--connect-btn-text);
52
130
  border: none;
53
131
  padding: 12px 24px;
54
132
  border-radius: 6px;
@@ -56,48 +134,63 @@
56
134
  font-size: 16px;
57
135
  box-sizing: border-box;
58
136
  text-align: center;
59
- /*
60
- margin: 10px 5px 10px 0;
61
- */
137
+ transition: background 0.2s ease;
138
+ }
139
+
140
+ body[data-page="connect"] .btn:hover {
141
+ background: var(--connect-btn-hover-bg);
62
142
  }
63
- .btn.secondary {
64
- background: #6b7280;
143
+
144
+ body[data-page="connect"] .btn.secondary {
145
+ background: var(--connect-secondary-btn-bg);
65
146
  }
66
- #user-details {
147
+
148
+ body[data-page="connect"] .btn.secondary:hover {
149
+ background: var(--connect-secondary-btn-hover);
150
+ }
151
+
152
+ body[data-page="connect"] #user-details {
67
153
  margin-bottom: 20px;
68
154
  }
69
- .user-info {
70
- background: #f0f9ff;
155
+
156
+ body[data-page="connect"] .user-info {
157
+ background: var(--connect-user-info-bg);
71
158
  padding: 15px;
72
- border-left: 4px solid #3b82f6;
73
- /*
74
- margin-top: 20px;
75
- */
159
+ border-left: 4px solid var(--connect-user-info-border);
76
160
  }
77
- #login-section p {
161
+
162
+ body[data-page="connect"] #login-section p {
78
163
  padding: 5px 0;
79
164
  }
80
- .user-info h3 {
165
+
166
+ body[data-page="connect"] .user-info h3 {
81
167
  margin: 0 0 10px;
82
168
  font-size: 30px;
169
+ color: var(--connect-heading);
83
170
  }
84
- .status {
171
+
172
+ body[data-page="connect"] .status {
85
173
  padding: 12px;
86
174
  margin: 10px 0;
87
175
  border-radius: 6px;
88
176
  font-weight: 500;
177
+ background: var(--connect-status-warning-bg);
178
+ color: var(--connect-status-warning-text);
89
179
  }
90
- .status.success { background: #d1fae5; color: #065f46; }
91
- .status.error { background: #fee2e2; color: #991b1b; }
92
- .status.warning { background: #fef3c7; color: #92400e; }
93
- .hidden { display: none; }
94
- .loader-overlay {
180
+
181
+ body[data-page="connect"] .status.success { background: var(--connect-status-success-bg); color: var(--connect-status-success-text); }
182
+ body[data-page="connect"] .status.error { background: var(--connect-status-error-bg); color: var(--connect-status-error-text); }
183
+ body[data-page="connect"] .status.warning { background: var(--connect-status-warning-bg); color: var(--connect-status-warning-text); }
184
+
185
+ body[data-page="connect"] .hidden { display: none; }
186
+
187
+ body[data-page="connect"] .loader-overlay {
95
188
  position: fixed;
96
189
  top: 0;
97
190
  left: 0;
98
191
  width: 100%;
99
192
  height: 100%;
100
- background: rgba(248, 250, 252, 0.95);
193
+ background: var(--connect-loader-overlay);
101
194
  display: flex;
102
195
  flex-direction: column;
103
196
  align-items: center;
@@ -105,76 +198,94 @@
105
198
  gap: 16px;
106
199
  z-index: 999;
107
200
  }
108
- .loader-overlay.hidden {
201
+
202
+ body[data-page="connect"] .loader-overlay.hidden {
109
203
  display: none;
110
204
  }
111
- .loader-spinner {
205
+
206
+ body[data-page="connect"] .loader-spinner {
112
207
  width: 48px;
113
208
  height: 48px;
114
- border: 4px solid #e5e7eb;
209
+ border: 4px solid rgba(229, 231, 235, 0.4);
115
210
  border-top: 4px solid #ff6b35;
116
211
  border-radius: 50%;
117
212
  animation: spin 1s linear infinite;
118
213
  }
214
+
119
215
  @keyframes spin {
120
216
  to { transform: rotate(360deg); }
121
217
  }
122
- .loader-message {
218
+
219
+ body[data-page="connect"] .loader-message {
123
220
  font-size: 16px;
124
- color: #374151;
221
+ color: var(--connect-loader-text);
125
222
  text-align: center;
126
223
  }
127
- .loader-cancel {
224
+
225
+ body[data-page="connect"] .loader-cancel {
128
226
  background: transparent;
129
- border: 1px solid #6b7280;
130
- color: #374151;
227
+ border: 1px solid var(--connect-loader-border);
228
+ color: var(--connect-loader-text);
131
229
  padding: 8px 16px;
132
230
  border-radius: 6px;
133
231
  cursor: pointer;
134
232
  }
135
- .profile {
233
+
234
+ body[data-page="connect"] .profile {
136
235
  width: 400px;
137
236
  display: flex;
138
237
  gap: 10px;
139
238
  font-size: 14px;
140
239
  box-sizing: border-box;
240
+ color: var(--connect-text);
141
241
  }
142
- .profile img {
242
+
243
+ body[data-page="connect"] .profile img {
143
244
  width: 100px;
144
245
  flex-shrink: 0;
246
+ border-radius: 8px;
247
+ border: 1px solid rgba(148, 163, 184, 0.4);
145
248
  }
146
- .profile td {
249
+
250
+ body[data-page="connect"] .profile td {
147
251
  font-size: 14px;
148
252
  padding: 0 10px 0 0;
149
253
  }
150
- header {
254
+
255
+ body[data-page="connect"] header {
151
256
  text-align: center;
152
257
  letter-spacing: -1px;
258
+ color: var(--connect-text);
153
259
  }
154
- header.head {
260
+
261
+ body[data-page="connect"] header.head {
155
262
  max-width: 600px;
156
263
  margin: 0 auto;
157
264
  padding: 50px;
158
265
  text-align: center;
159
266
  }
160
- header.head h1 {
267
+
268
+ body[data-page="connect"] header.head h1 {
161
269
  justify-content: center;
162
270
  }
163
- .logos {
271
+
272
+ body[data-page="connect"] .logos {
164
273
  display: flex;
165
274
  justify-content: center;
166
275
  align-items: center;
167
276
  gap: 10px;
168
277
  font-size: 30px;
169
278
  }
170
- .logo {
279
+
280
+ body[data-page="connect"] .logo {
171
281
  height: 40px;
172
282
  font-size: 40px;
173
283
  display: flex;
174
284
  justify-content: center;
175
285
  align-items: center;
176
286
  }
177
- p {
287
+
288
+ body[data-page="connect"] p {
178
289
  margin: 5px 0;
179
290
  font-size: 14px;
180
291
  }
@@ -185,7 +296,7 @@
185
296
  <script src="/sweetalert2.js"></script>
186
297
  <script src="/nav.js"></script>
187
298
  </head>
188
- <body class='<%=theme%>' data-agent="<%=agent%>">
299
+ <body class='<%=theme%>' data-agent="<%=agent%>" data-page="connect">
189
300
  <header class="navheader grabbable">
190
301
  <h1>
191
302
  <a class="home" href="/home"><img class="icon" src="/pinokio-black.png"></a>
@@ -36,6 +36,7 @@
36
36
  <link href="/css/brands.min.css" rel="stylesheet">
37
37
  <link href="/style.css" rel="stylesheet"/>
38
38
  <link href="/noty.css" rel="stylesheet"/>
39
+ <link href="/urldropdown.css" rel="stylesheet"/>
39
40
  <% if (agent === "electron") { %>
40
41
  <link href="/electron.css" rel="stylesheet"/>
41
42
  <% } %>
@@ -1063,6 +1064,9 @@ const reloadMemory = async () => {
1063
1064
  <div><i class="fa-solid fa-xmark"></i></div>
1064
1065
  </a>
1065
1066
  <div class='flexible'></div>
1067
+ <button type='button' class='btn' data-ask-ai-trigger="true" data-workspace="<%= (typeof projectName !== 'undefined' && projectName) ? projectName : '' %>">
1068
+ <div><i class="fa-solid fa-robot"></i> Ask AI</div>
1069
+ </button>
1066
1070
  <a class='btn' href="<%=portal%>" target="_blank">
1067
1071
  <div><i class="fa-solid fa-question"></i> Ask Community</div>
1068
1072
  </a>
@@ -44,6 +44,7 @@
44
44
  <link href="/style.css" rel="stylesheet"/>
45
45
  <link href="/noty.css" rel="stylesheet"/>
46
46
  <link href="/dropzone.css" rel="stylesheet"/>
47
+ <link href="/urldropdown.css" rel="stylesheet"/>
47
48
  <% if (agent === "electron") { %>
48
49
  <link href="/electron.css" rel="stylesheet"/>
49
50
  <% } %>
@@ -1970,6 +1971,9 @@ const reloadMemory = async () => {
1970
1971
  <div><i class="fa-solid fa-xmark"></i></div>
1971
1972
  </a>
1972
1973
  <div class='flexible'></div>
1974
+ <button type='button' class='btn' data-ask-ai-trigger="true" data-workspace="<%= (typeof projectName !== 'undefined' && projectName) ? projectName : '' %>">
1975
+ <div><i class="fa-solid fa-robot"></i> Ask AI</div>
1976
+ </button>
1973
1977
  <a class='btn' href="<%=portal%>" target="_blank">
1974
1978
  <div><i class="fa-solid fa-question"></i> Ask Community</div>
1975
1979
  </a>