pinokiod 3.296.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.296.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,
@@ -1,4 +1,11 @@
1
1
  const CAPTURE_MIN_SIZE = 32;
2
+ const createLauncherDebugLog = (...args) => {
3
+ try {
4
+ console.log('[CreateLauncherGuard]', ...args);
5
+ } catch (_) {
6
+ // ignore logging failures
7
+ }
8
+ };
2
9
 
3
10
  function createMinimalLoadingSwal () {
4
11
  if (typeof window === 'undefined' || typeof window.Swal === 'undefined') {
@@ -29,9 +36,11 @@ function createMinimalLoadingSwal () {
29
36
  return close;
30
37
  }
31
38
  function check_ready () {
39
+ createLauncherDebugLog('check_ready start');
32
40
  return fetch("/pinokio/requirements_ready").then((res) => {
33
41
  return res.json()
34
42
  }).then((res) => {
43
+ createLauncherDebugLog('check_ready response', res);
35
44
  if (res.error) {
36
45
  return false
37
46
  } else if (!res.requirements_pending) {
@@ -42,7 +51,41 @@ function check_ready () {
42
51
  }
43
52
 
44
53
  function check_dev () {
45
- return fetch('/bundle/dev').then((response) => response.json())
54
+ createLauncherDebugLog('check_dev start');
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
+ }
80
+ createLauncherDebugLog('check_dev response', payload);
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 }
88
+ })
46
89
  }
47
90
 
48
91
 
@@ -54,29 +97,20 @@ if (onfinish) {
54
97
  // The original task
55
98
  */
56
99
  function wait_ready () {
100
+ createLauncherDebugLog('wait_ready invoked');
57
101
  return new Promise((resolve, reject) => {
58
102
  check_ready().then((ready) => {
103
+ createLauncherDebugLog('wait_ready initial requirements readiness', ready);
59
104
  if (ready) {
60
- check_dev().then((data) => {
61
- if (data && data.available === false) {
62
- resolve({ closeModal: null, ready: false })
63
- } else {
64
- resolve({ closeModal: null, ready: true })
65
- }
66
- })
105
+ ensureDevReady().then(resolve)
67
106
  } else {
68
107
  let loader = createMinimalLoadingSwal();
69
108
  let interval = setInterval(() => {
70
109
  check_ready().then((ready) => {
110
+ createLauncherDebugLog('wait_ready polling requirements readiness', ready);
71
111
  if (ready) {
72
112
  clearInterval(interval)
73
- check_dev().then((data) => {
74
- if (data && data.available === false) {
75
- resolve({ ready: false, closeModal: loader })
76
- } else {
77
- resolve({ ready: true, closeModal: loader })
78
- }
79
- })
113
+ ensureDevReady(loader, 'after poll').then(resolve)
80
114
  }
81
115
  })
82
116
  }, 500)
@@ -85,6 +119,33 @@ function wait_ready () {
85
119
  })
86
120
  }
87
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
+
88
149
  function collectPostMessageTargets(contextWindow) {
89
150
  const ctx = contextWindow || window;
90
151
  const targets = new Set();
@@ -3195,8 +3256,15 @@ document.addEventListener("DOMContentLoaded", () => {
3195
3256
 
3196
3257
  function initializeCreateLauncherIntegration() {
3197
3258
  const defaults = parseCreateLauncherDefaults();
3198
- const triggerExists = document.getElementById('create-launcher-button');
3199
- if (!triggerExists && !defaults) {
3259
+ const createTrigger = document.getElementById('create-launcher-button');
3260
+ const askTriggers = Array.from(document.querySelectorAll('[data-ask-ai-trigger]'));
3261
+ createLauncherDebugLog('initializeCreateLauncherIntegration', {
3262
+ defaultsPresent: Boolean(defaults),
3263
+ triggerExists: Boolean(createTrigger),
3264
+ askTriggerCount: askTriggers.length
3265
+ });
3266
+ if (!createTrigger && askTriggers.length === 0 && !defaults) {
3267
+ createLauncherDebugLog('initializeCreateLauncherIntegration aborted (no trigger/defaults)');
3200
3268
  return;
3201
3269
  }
3202
3270
  if (defaults) {
@@ -3205,19 +3273,23 @@ document.addEventListener("DOMContentLoaded", () => {
3205
3273
  }
3206
3274
 
3207
3275
  ensureCreateLauncherModule().then((api) => {
3276
+ createLauncherDebugLog('ensureCreateLauncherModule resolved', { api: Boolean(api) });
3208
3277
  if (!api) {
3209
3278
  return;
3210
3279
  }
3211
3280
  initCreateLauncherTrigger(api);
3281
+ initAskAiTrigger(api);
3212
3282
  openPendingCreateLauncherModal(api);
3213
3283
  });
3214
3284
  }
3215
3285
 
3216
3286
  function ensureCreateLauncherModule() {
3217
3287
  if (window.CreateLauncher) {
3288
+ createLauncherDebugLog('ensureCreateLauncherModule: window.CreateLauncher already available');
3218
3289
  return Promise.resolve(window.CreateLauncher);
3219
3290
  }
3220
3291
  if (createLauncherState.loaderPromise) {
3292
+ createLauncherDebugLog('ensureCreateLauncherModule: loaderPromise already pending');
3221
3293
  return createLauncherState.loaderPromise;
3222
3294
  }
3223
3295
 
@@ -3225,12 +3297,17 @@ document.addEventListener("DOMContentLoaded", () => {
3225
3297
  const script = document.createElement('script');
3226
3298
  script.src = '/create-launcher.js';
3227
3299
  script.async = true;
3228
- script.onload = () => resolve(window.CreateLauncher || null);
3300
+ script.onload = () => {
3301
+ createLauncherDebugLog('create-launcher.js loaded', { hasModule: Boolean(window.CreateLauncher) });
3302
+ resolve(window.CreateLauncher || null);
3303
+ };
3229
3304
  script.onerror = (error) => {
3230
3305
  console.warn('Failed to load create launcher module', error);
3306
+ createLauncherDebugLog('create-launcher.js failed to load', error);
3231
3307
  resolve(null);
3232
3308
  };
3233
3309
  const target = document.head || document.body || document.documentElement;
3310
+ createLauncherDebugLog('injecting create-launcher.js <script>', { target: target ? target.nodeName : 'unknown' });
3234
3311
  target.appendChild(script);
3235
3312
  });
3236
3313
 
@@ -3240,19 +3317,110 @@ document.addEventListener("DOMContentLoaded", () => {
3240
3317
  function initCreateLauncherTrigger(api) {
3241
3318
  const trigger = document.getElementById('create-launcher-button');
3242
3319
  if (!trigger) {
3320
+ createLauncherDebugLog('initCreateLauncherTrigger: trigger not found');
3243
3321
  return;
3244
3322
  }
3245
3323
  if (trigger.dataset.createLauncherInit === 'true') {
3324
+ createLauncherDebugLog('initCreateLauncherTrigger: already initialized');
3246
3325
  return;
3247
3326
  }
3248
3327
  trigger.dataset.createLauncherInit = 'true';
3249
- trigger.addEventListener('click', () => guardCreateLauncher(api));
3328
+ createLauncherDebugLog('initCreateLauncherTrigger: binding click handler');
3329
+ trigger.addEventListener('click', () => {
3330
+ createLauncherDebugLog('create-launcher-button clicked');
3331
+ guardCreateLauncher(api);
3332
+ });
3333
+ }
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
+ }
3250
3417
  }
3251
3418
 
3252
3419
  function openPendingCreateLauncherModal(api) {
3253
3420
  if (!api || !createLauncherState.pendingDefaults) {
3254
3421
  return;
3255
3422
  }
3423
+ createLauncherDebugLog('openPendingCreateLauncherModal: running with defaults');
3256
3424
  guardCreateLauncher(api, createLauncherState.pendingDefaults);
3257
3425
  createLauncherState.pendingDefaults = null;
3258
3426
  if (createLauncherState.shouldCleanupQuery) {
@@ -3264,13 +3432,17 @@ document.addEventListener("DOMContentLoaded", () => {
3264
3432
 
3265
3433
  function guardCreateLauncher(api, defaults = null) {
3266
3434
  if (!api || typeof api.showModal !== 'function') {
3435
+ createLauncherDebugLog('guardCreateLauncher aborted: api unavailable');
3267
3436
  return;
3268
3437
  }
3438
+ createLauncherDebugLog('guardCreateLauncher invoked', { defaults: Boolean(defaults) });
3269
3439
  wait_ready().then(({ closeModal, ready }) => {
3440
+ createLauncherDebugLog('guardCreateLauncher wait_ready resolved', { ready, closeModal: Boolean(closeModal) });
3270
3441
  if (closeModal) {
3271
3442
  closeModal()
3272
3443
  }
3273
3444
  if (ready) {
3445
+ createLauncherDebugLog('guardCreateLauncher proceeding to show modal', { defaults: Boolean(defaults) });
3274
3446
  if (defaults) {
3275
3447
  api.showModal(defaults);
3276
3448
  } else {
@@ -3278,6 +3450,7 @@ document.addEventListener("DOMContentLoaded", () => {
3278
3450
  }
3279
3451
  } else {
3280
3452
  const callback = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash);
3453
+ createLauncherDebugLog('guardCreateLauncher redirecting to /setup/dev', { callback });
3281
3454
  window.location.href = `/setup/dev?callback=${callback}`;
3282
3455
  }
3283
3456
  })
@@ -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>