pinokiod 3.297.0 → 3.300.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 +50 -0
- package/package.json +1 -1
- package/server/index.js +1 -0
- package/server/public/common.js +150 -22
- package/server/public/create-launcher.js +82 -16
- package/server/public/urldropdown.css +1 -1
- package/server/views/app.ejs +16 -8
- package/server/views/connect/index.ejs +169 -58
- package/server/views/editor.ejs +4 -0
- package/server/views/terminal.ejs +4 -0
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
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,
|
package/server/public/common.js
CHANGED
|
@@ -52,9 +52,39 @@ function check_ready () {
|
|
|
52
52
|
|
|
53
53
|
function check_dev () {
|
|
54
54
|
createLauncherDebugLog('check_dev start');
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
3263
|
+
triggerExists: Boolean(createTrigger),
|
|
3264
|
+
askTriggerCount: askTriggers.length
|
|
3222
3265
|
});
|
|
3223
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
688
|
-
|
|
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
|
|
757
|
+
if (ui.currentVariant !== MODAL_VARIANTS.ASK && ui.folderInput) {
|
|
758
|
+
ui.folderInput.select();
|
|
759
|
+
}
|
|
694
760
|
ui.promptTextarea.focus();
|
|
695
761
|
});
|
|
696
762
|
});
|
package/server/views/app.ejs
CHANGED
|
@@ -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
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
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:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
.
|
|
32
|
-
|
|
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:
|
|
110
|
+
box-shadow: var(--connect-card-shadow);
|
|
111
|
+
border: 1px solid var(--connect-card-border);
|
|
112
|
+
color: var(--connect-text);
|
|
38
113
|
}
|
|
39
|
-
|
|
114
|
+
|
|
115
|
+
body[data-page="connect"] header.head h1 {
|
|
40
116
|
text-transform: capitalize;
|
|
41
|
-
color:
|
|
117
|
+
color: var(--connect-heading);
|
|
42
118
|
margin: 0;
|
|
43
119
|
font-size: 40px;
|
|
44
120
|
margin-top: 10px;
|
|
45
121
|
}
|
|
46
|
-
|
|
122
|
+
|
|
123
|
+
body[data-page="connect"] .btn {
|
|
47
124
|
text-decoration: none;
|
|
48
|
-
display: inline-
|
|
49
|
-
|
|
125
|
+
display: inline-flex;
|
|
126
|
+
justify-content: center;
|
|
127
|
+
background: var(--connect-btn-bg);
|
|
50
128
|
width: 100%;
|
|
51
|
-
color:
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
143
|
+
|
|
144
|
+
body[data-page="connect"] .btn.secondary {
|
|
145
|
+
background: var(--connect-secondary-btn-bg);
|
|
65
146
|
}
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
155
|
+
|
|
156
|
+
body[data-page="connect"] .user-info {
|
|
157
|
+
background: var(--connect-user-info-bg);
|
|
71
158
|
padding: 15px;
|
|
72
|
-
border-left: 4px solid
|
|
73
|
-
/*
|
|
74
|
-
margin-top: 20px;
|
|
75
|
-
*/
|
|
159
|
+
border-left: 4px solid var(--connect-user-info-border);
|
|
76
160
|
}
|
|
77
|
-
|
|
161
|
+
|
|
162
|
+
body[data-page="connect"] #login-section p {
|
|
78
163
|
padding: 5px 0;
|
|
79
164
|
}
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
.status.
|
|
92
|
-
.status.
|
|
93
|
-
.
|
|
94
|
-
|
|
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:
|
|
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
|
-
|
|
201
|
+
|
|
202
|
+
body[data-page="connect"] .loader-overlay.hidden {
|
|
109
203
|
display: none;
|
|
110
204
|
}
|
|
111
|
-
|
|
205
|
+
|
|
206
|
+
body[data-page="connect"] .loader-spinner {
|
|
112
207
|
width: 48px;
|
|
113
208
|
height: 48px;
|
|
114
|
-
border: 4px solid
|
|
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
|
-
|
|
218
|
+
|
|
219
|
+
body[data-page="connect"] .loader-message {
|
|
123
220
|
font-size: 16px;
|
|
124
|
-
color:
|
|
221
|
+
color: var(--connect-loader-text);
|
|
125
222
|
text-align: center;
|
|
126
223
|
}
|
|
127
|
-
|
|
224
|
+
|
|
225
|
+
body[data-page="connect"] .loader-cancel {
|
|
128
226
|
background: transparent;
|
|
129
|
-
border: 1px solid
|
|
130
|
-
color:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
249
|
+
|
|
250
|
+
body[data-page="connect"] .profile td {
|
|
147
251
|
font-size: 14px;
|
|
148
252
|
padding: 0 10px 0 0;
|
|
149
253
|
}
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
267
|
+
|
|
268
|
+
body[data-page="connect"] header.head h1 {
|
|
161
269
|
justify-content: center;
|
|
162
270
|
}
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
package/server/views/editor.ejs
CHANGED
|
@@ -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>
|