pinokiod 7.5.6 → 7.5.8
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/api/shell/index.js +7 -2
- package/kernel/bin/node.js +13 -8
- package/kernel/shell.js +20 -1
- package/kernel/shell_conda_runtime_guard.js +647 -0
- package/kernel/shells.js +8 -2
- package/package.json +1 -1
- package/server/index.js +3 -0
- package/server/public/common.js +11 -4
- package/server/public/logs.js +597 -4
- package/server/public/style.css +203 -42
- package/server/views/app.ejs +39 -31
- package/server/views/editor.ejs +1 -0
- package/server/views/install.ejs +1 -0
- package/server/views/logs.ejs +9 -5
- package/server/views/terminal.ejs +34 -5
- package/test/node-bin.test.js +39 -0
- package/test/shell-api.test.js +99 -9
- package/test/shell-conda-runtime-guard.test.js +558 -0
- package/test/shells-live-error.test.js +24 -2
package/server/public/logs.js
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
const DRAFT_TITLE_STRONG_PATTERN = /\b(?:[A-Za-z_][A-Za-z0-9_.]*(?:Error|Exception)|ERROR|FATAL|ERR!|exit code\s+\d+)\b/i
|
|
21
21
|
const DRAFT_TITLE_STACK_PATTERN = /^\s*(?:File\s+"[^"]+",\s+line\s+\d+|at\s+\S+|from\s+\S+\s+import\s+|return\s+|await\s+|sys\.exit\b)/i
|
|
22
22
|
const DRAFT_TITLE_NOISE_PATTERN = /^\s*(?:<<PINOKIO_SHELL>>|={6,}|-{6,}|\[api\s+local\.set\]|The default interactive shell is now|To update your account|For more details, please visit)/i
|
|
23
|
+
const ASK_AI_DEFAULT_PROMPT = 'Investigate what went wrong. Inspect the app logs and explain the likely root cause and next fix.'
|
|
24
|
+
const TOOL_PREFERENCE_KEY = 'pinokio.universalLauncher.tool'
|
|
23
25
|
const textEncoder = typeof TextEncoder === 'function' ? new TextEncoder() : null
|
|
24
26
|
|
|
25
27
|
const safeJsonParse = (value) => {
|
|
@@ -59,6 +61,115 @@
|
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
const isPluginLauncherPath = (pathname) => {
|
|
65
|
+
return typeof pathname === 'string'
|
|
66
|
+
&& (
|
|
67
|
+
pathname.startsWith('/run/plugin/')
|
|
68
|
+
|| pathname.startsWith('/pinokio/run/plugin/')
|
|
69
|
+
|| (pathname.startsWith('/run/api/') && /\/pinokio\.js$/i.test(pathname))
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const pluginToolCategory = (plugin) => {
|
|
74
|
+
const explicitCategory = typeof plugin?.category === 'string' ? plugin.category.trim().toLowerCase() : ''
|
|
75
|
+
if (explicitCategory === 'ide') return 'Desktop'
|
|
76
|
+
if (explicitCategory === 'cli') return 'Terminal'
|
|
77
|
+
const launchType = typeof plugin?.launch_type === 'string' ? plugin.launch_type.trim().toLowerCase() : ''
|
|
78
|
+
if (launchType === 'desktop') return 'Desktop'
|
|
79
|
+
if (launchType === 'terminal') return 'Terminal'
|
|
80
|
+
return plugin && plugin.categoryTitle ? String(plugin.categoryTitle) : 'Plugin'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const pluginToolValue = (href) => {
|
|
84
|
+
const normalized = String(href || '').replace(/^\/run/, '').replace(/^\/+/, '')
|
|
85
|
+
const parts = normalized.split('/').filter(Boolean)
|
|
86
|
+
let value = ''
|
|
87
|
+
if (parts[0] === 'plugin' && parts.length >= 3) {
|
|
88
|
+
value = parts.slice(1, -1).join('/')
|
|
89
|
+
} else {
|
|
90
|
+
value = normalized
|
|
91
|
+
}
|
|
92
|
+
if (value.endsWith('/pinokio.js')) {
|
|
93
|
+
value = value.replace(/\/pinokio\.js$/i, '')
|
|
94
|
+
}
|
|
95
|
+
return value
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const getStoredToolPreference = () => {
|
|
99
|
+
try {
|
|
100
|
+
const value = window.localStorage.getItem(TOOL_PREFERENCE_KEY)
|
|
101
|
+
return typeof value === 'string' ? value.trim() : ''
|
|
102
|
+
} catch (_) {
|
|
103
|
+
return ''
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const setStoredToolPreference = (value) => {
|
|
108
|
+
try {
|
|
109
|
+
const normalized = typeof value === 'string' ? value.trim() : ''
|
|
110
|
+
if (normalized) {
|
|
111
|
+
window.localStorage.setItem(TOOL_PREFERENCE_KEY, normalized)
|
|
112
|
+
} else {
|
|
113
|
+
window.localStorage.removeItem(TOOL_PREFERENCE_KEY)
|
|
114
|
+
}
|
|
115
|
+
} catch (_) {}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const mapPluginMenuToAskAiTools = (menu) => {
|
|
119
|
+
if (!Array.isArray(menu)) {
|
|
120
|
+
return []
|
|
121
|
+
}
|
|
122
|
+
return menu.map((plugin) => {
|
|
123
|
+
if (!plugin || typeof plugin !== 'object') {
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
const href = typeof plugin.href === 'string' ? plugin.href.trim() : ''
|
|
127
|
+
if (!href) {
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
let parsed
|
|
131
|
+
try {
|
|
132
|
+
parsed = new URL(href, window.location.origin)
|
|
133
|
+
} catch (_) {
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
if (parsed.origin !== window.location.origin || !isPluginLauncherPath(parsed.pathname)) {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
const label = typeof plugin.title === 'string' && plugin.title.trim()
|
|
140
|
+
? plugin.title.trim()
|
|
141
|
+
: (typeof plugin.text === 'string' && plugin.text.trim() ? plugin.text.trim() : href)
|
|
142
|
+
return {
|
|
143
|
+
href,
|
|
144
|
+
value: pluginToolValue(href),
|
|
145
|
+
label,
|
|
146
|
+
category: pluginToolCategory(plugin),
|
|
147
|
+
iconSrc: typeof plugin.image === 'string' ? plugin.image : (typeof plugin.icon === 'string' ? plugin.icon : ''),
|
|
148
|
+
pluginPath: typeof plugin.pluginPath === 'string' ? plugin.pluginPath : '',
|
|
149
|
+
detailUrl: typeof plugin.detailUrl === 'string' ? plugin.detailUrl : '',
|
|
150
|
+
hasInstall: plugin.hasInstall === true,
|
|
151
|
+
hasInstalledCheck: plugin.hasInstalledCheck === true,
|
|
152
|
+
installed: typeof plugin.installed === 'boolean' ? plugin.installed : null
|
|
153
|
+
}
|
|
154
|
+
}).filter(Boolean).sort((a, b) => {
|
|
155
|
+
const categoryDelta = String(a.category || '').localeCompare(String(b.category || ''), undefined, { sensitivity: 'base' })
|
|
156
|
+
if (categoryDelta !== 0) return categoryDelta
|
|
157
|
+
return String(a.label || '').localeCompare(String(b.label || ''), undefined, { sensitivity: 'base' })
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const askAiToolOptionLabel = (tool) => {
|
|
162
|
+
const label = String(tool?.label || '').trim()
|
|
163
|
+
const category = String(tool?.category || '').trim()
|
|
164
|
+
if (!category) {
|
|
165
|
+
return label
|
|
166
|
+
}
|
|
167
|
+
if (label.toLowerCase().includes(category.toLowerCase())) {
|
|
168
|
+
return label
|
|
169
|
+
}
|
|
170
|
+
return `${label} (${category})`
|
|
171
|
+
}
|
|
172
|
+
|
|
62
173
|
class PrivacyFilterClient {
|
|
63
174
|
constructor() {
|
|
64
175
|
this.worker = null
|
|
@@ -218,6 +329,7 @@
|
|
|
218
329
|
this.statusEl = options.statusEl
|
|
219
330
|
this.outputEl = options.outputEl
|
|
220
331
|
this.copyButton = options.copyButton
|
|
332
|
+
this.askAiButton = options.askAiButton
|
|
221
333
|
this.createDraftButton = options.createDraftButton
|
|
222
334
|
this.draftTitleInput = options.draftTitleInput
|
|
223
335
|
this.draftTitleNoteEl = options.draftTitleNoteEl
|
|
@@ -232,7 +344,12 @@
|
|
|
232
344
|
this.reviewListEl = options.reviewListEl
|
|
233
345
|
this.reviewFiltersEl = options.reviewFiltersEl
|
|
234
346
|
this.reviewCountEl = options.reviewCountEl
|
|
347
|
+
this.workspace = typeof options.workspace === 'string' ? options.workspace.trim() : ''
|
|
348
|
+
this.workspaceCwd = typeof options.workspaceCwd === 'string' ? options.workspaceCwd.trim() : ''
|
|
235
349
|
this.privacyFilter = options.privacyFilter || new PrivacyFilterClient()
|
|
350
|
+
this.askAiTools = null
|
|
351
|
+
this.askAiToolsPromise = null
|
|
352
|
+
this.askAiLauncher = null
|
|
236
353
|
this.report = null
|
|
237
354
|
this.rawMarkdown = ''
|
|
238
355
|
this.reviewMarkdown = ''
|
|
@@ -257,6 +374,9 @@
|
|
|
257
374
|
if (this.copyButton) {
|
|
258
375
|
this.copyButton.addEventListener('click', () => this.copy())
|
|
259
376
|
}
|
|
377
|
+
if (this.askAiButton) {
|
|
378
|
+
this.askAiButton.addEventListener('click', () => this.openAskAiModal())
|
|
379
|
+
}
|
|
260
380
|
if (this.createDraftButton) {
|
|
261
381
|
this.createDraftButton.addEventListener('click', () => this.createDraft())
|
|
262
382
|
}
|
|
@@ -284,6 +404,471 @@
|
|
|
284
404
|
this.refreshButton.disabled = Boolean(isBusy)
|
|
285
405
|
this.refreshButton.classList.toggle('is-busy', Boolean(isBusy))
|
|
286
406
|
}
|
|
407
|
+
setAskAiBusy(isBusy) {
|
|
408
|
+
if (!this.askAiButton) return
|
|
409
|
+
if (isBusy) {
|
|
410
|
+
this.askAiButton.disabled = true
|
|
411
|
+
this.askAiButton.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i><span>Loading…</span>'
|
|
412
|
+
} else {
|
|
413
|
+
this.askAiButton.innerHTML = '<i class="fa-solid fa-robot"></i><span>Ask AI</span>'
|
|
414
|
+
this.updateDraftSizeReview()
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
buildAskAiMenuUrl() {
|
|
418
|
+
const url = new URL('/api/plugin/menu', window.location.origin)
|
|
419
|
+
if (this.workspaceCwd) {
|
|
420
|
+
url.searchParams.set('workspace', this.workspaceCwd)
|
|
421
|
+
}
|
|
422
|
+
return `${url.pathname}${url.search}`
|
|
423
|
+
}
|
|
424
|
+
async loadAskAiTools() {
|
|
425
|
+
if (Array.isArray(this.askAiTools) && this.askAiTools.length > 0) {
|
|
426
|
+
return this.askAiTools
|
|
427
|
+
}
|
|
428
|
+
if (this.askAiToolsPromise) {
|
|
429
|
+
return this.askAiToolsPromise
|
|
430
|
+
}
|
|
431
|
+
this.askAiToolsPromise = fetch(this.buildAskAiMenuUrl(), {
|
|
432
|
+
cache: 'no-store',
|
|
433
|
+
headers: {
|
|
434
|
+
Accept: 'application/json'
|
|
435
|
+
}
|
|
436
|
+
})
|
|
437
|
+
.then((response) => {
|
|
438
|
+
if (!response.ok) {
|
|
439
|
+
throw new Error(`Failed to load plugins (${response.status})`)
|
|
440
|
+
}
|
|
441
|
+
return response.json()
|
|
442
|
+
})
|
|
443
|
+
.then((payload) => mapPluginMenuToAskAiTools(payload && Array.isArray(payload.menu) ? payload.menu : []))
|
|
444
|
+
.finally(() => {
|
|
445
|
+
this.askAiToolsPromise = null
|
|
446
|
+
})
|
|
447
|
+
const tools = await this.askAiToolsPromise
|
|
448
|
+
this.askAiTools = tools
|
|
449
|
+
return tools
|
|
450
|
+
}
|
|
451
|
+
buildAskAiLaunchHref(tool, prompt) {
|
|
452
|
+
const href = tool && typeof tool.href === 'string' ? tool.href : ''
|
|
453
|
+
if (!href) {
|
|
454
|
+
return ''
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
const parsed = new URL(href, window.location.origin)
|
|
458
|
+
if (parsed.origin !== window.location.origin || !isPluginLauncherPath(parsed.pathname)) {
|
|
459
|
+
return ''
|
|
460
|
+
}
|
|
461
|
+
if (this.workspaceCwd && !parsed.searchParams.has('cwd')) {
|
|
462
|
+
parsed.searchParams.set('cwd', this.workspaceCwd)
|
|
463
|
+
}
|
|
464
|
+
parsed.searchParams.set('ask_ai', '1')
|
|
465
|
+
const question = String(prompt || '').trim()
|
|
466
|
+
if (question) {
|
|
467
|
+
parsed.searchParams.set('prompt', question)
|
|
468
|
+
} else {
|
|
469
|
+
parsed.searchParams.delete('prompt')
|
|
470
|
+
}
|
|
471
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`
|
|
472
|
+
} catch (_) {
|
|
473
|
+
return ''
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
pluginInstallHref(tool) {
|
|
477
|
+
if (!tool || tool.hasInstall !== true || tool.hasInstalledCheck !== true || tool.installed !== false) {
|
|
478
|
+
return ''
|
|
479
|
+
}
|
|
480
|
+
const fallbackPath = tool.pluginPath || ''
|
|
481
|
+
const detailUrl = tool.detailUrl || (fallbackPath ? `/plugin?path=${encodeURIComponent(fallbackPath)}` : '')
|
|
482
|
+
if (!detailUrl) {
|
|
483
|
+
return ''
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
const parsed = new URL(detailUrl, window.location.origin)
|
|
487
|
+
parsed.searchParams.set('next', 'install')
|
|
488
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`
|
|
489
|
+
} catch (_) {
|
|
490
|
+
const separator = detailUrl.includes('?') ? '&' : '?'
|
|
491
|
+
return `${detailUrl}${separator}next=install`
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
redirectToPluginInstallIfNeeded(tool) {
|
|
495
|
+
const href = this.pluginInstallHref(tool)
|
|
496
|
+
if (!href) {
|
|
497
|
+
return false
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
if (window.parent && window.parent !== window) {
|
|
501
|
+
window.parent.location.href = href
|
|
502
|
+
return true
|
|
503
|
+
}
|
|
504
|
+
} catch (_) {}
|
|
505
|
+
window.location.href = href
|
|
506
|
+
return true
|
|
507
|
+
}
|
|
508
|
+
dispatchAskAiLaunch(tool, prompt) {
|
|
509
|
+
const launchHref = this.buildAskAiLaunchHref(tool, prompt)
|
|
510
|
+
if (!launchHref) {
|
|
511
|
+
this.setStatus('Could not launch this plugin.', true)
|
|
512
|
+
return false
|
|
513
|
+
}
|
|
514
|
+
const payload = {
|
|
515
|
+
e: 'pinokio:ask-ai-launch',
|
|
516
|
+
workspace: this.workspace,
|
|
517
|
+
workspaceCwd: this.workspaceCwd,
|
|
518
|
+
agentHref: launchHref,
|
|
519
|
+
agentLabel: tool && tool.label ? tool.label : '',
|
|
520
|
+
prompt: String(prompt || '').trim()
|
|
521
|
+
}
|
|
522
|
+
if (window.PinokioAskAiDrawer && typeof window.PinokioAskAiDrawer.openWithUrl === 'function') {
|
|
523
|
+
try {
|
|
524
|
+
const opened = window.PinokioAskAiDrawer.openWithUrl(launchHref, {
|
|
525
|
+
workspaceCwd: this.workspaceCwd,
|
|
526
|
+
prompt: payload.prompt
|
|
527
|
+
})
|
|
528
|
+
if (opened !== false) {
|
|
529
|
+
this.setStatus('Launching Ask AI…')
|
|
530
|
+
return true
|
|
531
|
+
}
|
|
532
|
+
} catch (_) {}
|
|
533
|
+
}
|
|
534
|
+
try {
|
|
535
|
+
if (window.parent && window.parent !== window && typeof window.parent.postMessage === 'function') {
|
|
536
|
+
window.parent.postMessage(payload, '*')
|
|
537
|
+
this.setStatus('Launching Ask AI…')
|
|
538
|
+
return true
|
|
539
|
+
}
|
|
540
|
+
} catch (_) {}
|
|
541
|
+
window.location.href = launchHref
|
|
542
|
+
return true
|
|
543
|
+
}
|
|
544
|
+
createAskAiLauncher() {
|
|
545
|
+
if (this.askAiLauncher) {
|
|
546
|
+
return this.askAiLauncher
|
|
547
|
+
}
|
|
548
|
+
const overlay = document.createElement('div')
|
|
549
|
+
overlay.className = 'universal-launcher-overlay logs-ask-ai-launcher'
|
|
550
|
+
overlay.hidden = true
|
|
551
|
+
|
|
552
|
+
const panel = document.createElement('section')
|
|
553
|
+
panel.className = 'universal-launcher-panel logs-ask-ai-launcher-panel'
|
|
554
|
+
panel.setAttribute('role', 'dialog')
|
|
555
|
+
panel.setAttribute('aria-modal', 'true')
|
|
556
|
+
panel.setAttribute('aria-labelledby', 'logs-ask-ai-launcher-title')
|
|
557
|
+
panel.setAttribute('aria-describedby', 'logs-ask-ai-launcher-description')
|
|
558
|
+
overlay.appendChild(panel)
|
|
559
|
+
|
|
560
|
+
const header = document.createElement('header')
|
|
561
|
+
header.className = 'universal-launcher-header'
|
|
562
|
+
panel.appendChild(header)
|
|
563
|
+
|
|
564
|
+
const heading = document.createElement('div')
|
|
565
|
+
heading.className = 'universal-launcher-heading'
|
|
566
|
+
header.appendChild(heading)
|
|
567
|
+
|
|
568
|
+
const titleRow = document.createElement('div')
|
|
569
|
+
titleRow.className = 'universal-launcher-title-row'
|
|
570
|
+
heading.appendChild(titleRow)
|
|
571
|
+
|
|
572
|
+
const brandMark = document.createElement('span')
|
|
573
|
+
brandMark.className = 'universal-launcher-brand-mark'
|
|
574
|
+
brandMark.setAttribute('aria-hidden', 'true')
|
|
575
|
+
titleRow.appendChild(brandMark)
|
|
576
|
+
|
|
577
|
+
const title = document.createElement('h3')
|
|
578
|
+
title.className = 'universal-launcher-title'
|
|
579
|
+
title.id = 'logs-ask-ai-launcher-title'
|
|
580
|
+
title.textContent = 'Ask Pinokio'
|
|
581
|
+
titleRow.appendChild(title)
|
|
582
|
+
|
|
583
|
+
const description = document.createElement('p')
|
|
584
|
+
description.className = 'universal-launcher-description'
|
|
585
|
+
description.id = 'logs-ask-ai-launcher-description'
|
|
586
|
+
description.textContent = 'Launch a local agent with this log report open.'
|
|
587
|
+
heading.appendChild(description)
|
|
588
|
+
|
|
589
|
+
const closeButton = document.createElement('button')
|
|
590
|
+
closeButton.type = 'button'
|
|
591
|
+
closeButton.className = 'universal-launcher-close'
|
|
592
|
+
closeButton.setAttribute('aria-label', 'Close Ask Pinokio')
|
|
593
|
+
closeButton.innerHTML = '<i class="fa-solid fa-xmark" aria-hidden="true"></i>'
|
|
594
|
+
header.appendChild(closeButton)
|
|
595
|
+
|
|
596
|
+
const body = document.createElement('div')
|
|
597
|
+
body.className = 'universal-launcher-body logs-ask-ai-launcher-body'
|
|
598
|
+
panel.appendChild(body)
|
|
599
|
+
|
|
600
|
+
const promptSection = document.createElement('section')
|
|
601
|
+
promptSection.className = 'universal-launcher-section universal-launcher-section-prompt'
|
|
602
|
+
body.appendChild(promptSection)
|
|
603
|
+
|
|
604
|
+
const promptHeading = document.createElement('div')
|
|
605
|
+
promptHeading.className = 'universal-launcher-section-heading'
|
|
606
|
+
promptSection.appendChild(promptHeading)
|
|
607
|
+
|
|
608
|
+
const promptTitle = document.createElement('div')
|
|
609
|
+
promptTitle.className = 'universal-launcher-section-title'
|
|
610
|
+
promptTitle.textContent = 'What should Pinokio do?'
|
|
611
|
+
promptHeading.appendChild(promptTitle)
|
|
612
|
+
|
|
613
|
+
const composer = document.createElement('div')
|
|
614
|
+
composer.className = 'universal-launcher-ask-composer is-ask-intent'
|
|
615
|
+
promptSection.appendChild(composer)
|
|
616
|
+
|
|
617
|
+
const promptTextarea = document.createElement('textarea')
|
|
618
|
+
promptTextarea.className = 'universal-launcher-textarea logs-ask-ai-launcher-textarea'
|
|
619
|
+
promptTextarea.rows = 3
|
|
620
|
+
promptTextarea.setAttribute('aria-label', 'Question')
|
|
621
|
+
composer.appendChild(promptTextarea)
|
|
622
|
+
|
|
623
|
+
const error = document.createElement('div')
|
|
624
|
+
error.className = 'universal-launcher-error'
|
|
625
|
+
body.appendChild(error)
|
|
626
|
+
|
|
627
|
+
const footer = document.createElement('footer')
|
|
628
|
+
footer.className = 'universal-launcher-footer logs-ask-ai-launcher-footer'
|
|
629
|
+
panel.appendChild(footer)
|
|
630
|
+
|
|
631
|
+
const toolSection = document.createElement('section')
|
|
632
|
+
toolSection.className = 'universal-launcher-section universal-launcher-section-tools footer-mounted logs-ask-ai-launcher-tool-section'
|
|
633
|
+
footer.appendChild(toolSection)
|
|
634
|
+
|
|
635
|
+
const toolPicker = document.createElement('div')
|
|
636
|
+
toolPicker.className = 'universal-launcher-tool-picker logs-ask-ai-tool-picker'
|
|
637
|
+
toolSection.appendChild(toolPicker)
|
|
638
|
+
|
|
639
|
+
const toolTrigger = document.createElement('div')
|
|
640
|
+
toolTrigger.className = 'universal-launcher-tool-trigger has-value logs-ask-ai-tool-trigger'
|
|
641
|
+
toolTrigger.setAttribute('aria-hidden', 'true')
|
|
642
|
+
toolPicker.appendChild(toolTrigger)
|
|
643
|
+
|
|
644
|
+
const toolIcon = document.createElement('span')
|
|
645
|
+
toolIcon.className = 'universal-launcher-tool-trigger-icon logs-ask-ai-tool-trigger-icon'
|
|
646
|
+
toolTrigger.appendChild(toolIcon)
|
|
647
|
+
|
|
648
|
+
const toolContent = document.createElement('div')
|
|
649
|
+
toolContent.className = 'universal-launcher-tool-trigger-content'
|
|
650
|
+
toolTrigger.appendChild(toolContent)
|
|
651
|
+
|
|
652
|
+
const toolLabel = document.createElement('div')
|
|
653
|
+
toolLabel.className = 'universal-launcher-tool-trigger-label'
|
|
654
|
+
toolContent.appendChild(toolLabel)
|
|
655
|
+
|
|
656
|
+
const toolMeta = document.createElement('div')
|
|
657
|
+
toolMeta.className = 'universal-launcher-tool-trigger-meta'
|
|
658
|
+
toolContent.appendChild(toolMeta)
|
|
659
|
+
|
|
660
|
+
const toolCaret = document.createElement('i')
|
|
661
|
+
toolCaret.className = 'fa-solid fa-chevron-down universal-launcher-tool-trigger-caret'
|
|
662
|
+
toolCaret.setAttribute('aria-hidden', 'true')
|
|
663
|
+
toolTrigger.appendChild(toolCaret)
|
|
664
|
+
|
|
665
|
+
const toolSelect = document.createElement('select')
|
|
666
|
+
toolSelect.className = 'logs-ask-ai-tool-select'
|
|
667
|
+
toolSelect.setAttribute('aria-label', 'Agent')
|
|
668
|
+
toolPicker.appendChild(toolSelect)
|
|
669
|
+
|
|
670
|
+
const footerActions = document.createElement('div')
|
|
671
|
+
footerActions.className = 'universal-launcher-footer-actions'
|
|
672
|
+
footer.appendChild(footerActions)
|
|
673
|
+
|
|
674
|
+
const runButton = document.createElement('button')
|
|
675
|
+
runButton.type = 'button'
|
|
676
|
+
runButton.className = 'universal-launcher-button universal-launcher-button-primary'
|
|
677
|
+
runButton.textContent = 'Run'
|
|
678
|
+
footerActions.appendChild(runButton)
|
|
679
|
+
|
|
680
|
+
let returnFocusEl = null
|
|
681
|
+
const syncRunState = () => {
|
|
682
|
+
runButton.disabled = !String(promptTextarea.value || '').trim() || !this.selectedAskAiTool()
|
|
683
|
+
}
|
|
684
|
+
const getFocusable = () => {
|
|
685
|
+
return Array.from(panel.querySelectorAll('a[href], button:not([disabled]), textarea:not([disabled]), select:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])'))
|
|
686
|
+
.filter((node) => node && !node.hidden && node.offsetParent !== null)
|
|
687
|
+
}
|
|
688
|
+
const setOpen = (isOpen) => {
|
|
689
|
+
overlay.hidden = !isOpen
|
|
690
|
+
document.documentElement.classList.toggle('universal-launcher-open', isOpen)
|
|
691
|
+
document.body.classList.toggle('universal-launcher-open', isOpen)
|
|
692
|
+
if (isOpen) {
|
|
693
|
+
returnFocusEl = document.activeElement && typeof document.activeElement.focus === 'function'
|
|
694
|
+
? document.activeElement
|
|
695
|
+
: this.askAiButton
|
|
696
|
+
window.requestAnimationFrame(() => {
|
|
697
|
+
promptTextarea.focus()
|
|
698
|
+
const cursorIndex = promptTextarea.value.length
|
|
699
|
+
try {
|
|
700
|
+
promptTextarea.setSelectionRange(cursorIndex, cursorIndex)
|
|
701
|
+
} catch (_) {}
|
|
702
|
+
})
|
|
703
|
+
} else if (returnFocusEl && typeof returnFocusEl.focus === 'function' && document.contains(returnFocusEl)) {
|
|
704
|
+
try {
|
|
705
|
+
returnFocusEl.focus()
|
|
706
|
+
} catch (_) {}
|
|
707
|
+
returnFocusEl = null
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const close = () => setOpen(false)
|
|
711
|
+
const syncTool = () => {
|
|
712
|
+
const tool = this.selectedAskAiTool()
|
|
713
|
+
toolLabel.textContent = tool ? tool.label : 'Choose agent'
|
|
714
|
+
toolMeta.textContent = tool ? (tool.category || 'Plugin') : ''
|
|
715
|
+
toolIcon.textContent = ''
|
|
716
|
+
while (toolIcon.firstChild) {
|
|
717
|
+
toolIcon.removeChild(toolIcon.firstChild)
|
|
718
|
+
}
|
|
719
|
+
if (tool && tool.iconSrc) {
|
|
720
|
+
const icon = document.createElement('img')
|
|
721
|
+
icon.src = tool.iconSrc
|
|
722
|
+
icon.alt = ''
|
|
723
|
+
icon.className = 'logs-ask-ai-tool-trigger-image'
|
|
724
|
+
toolIcon.appendChild(icon)
|
|
725
|
+
} else {
|
|
726
|
+
const icon = document.createElement('i')
|
|
727
|
+
icon.className = 'fa-solid fa-robot'
|
|
728
|
+
icon.setAttribute('aria-hidden', 'true')
|
|
729
|
+
toolIcon.appendChild(icon)
|
|
730
|
+
}
|
|
731
|
+
syncRunState()
|
|
732
|
+
}
|
|
733
|
+
const run = () => {
|
|
734
|
+
const prompt = String(promptTextarea.value || '').trim()
|
|
735
|
+
const tool = this.selectedAskAiTool()
|
|
736
|
+
if (!prompt) {
|
|
737
|
+
error.textContent = 'Enter a question for the agent.'
|
|
738
|
+
promptTextarea.focus()
|
|
739
|
+
return
|
|
740
|
+
}
|
|
741
|
+
if (!tool) {
|
|
742
|
+
error.textContent = 'Choose an agent.'
|
|
743
|
+
toolSelect.focus()
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
error.textContent = ''
|
|
747
|
+
if (this.redirectToPluginInstallIfNeeded(tool)) {
|
|
748
|
+
close()
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
if (this.dispatchAskAiLaunch(tool, prompt)) {
|
|
752
|
+
close()
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
closeButton.addEventListener('click', close)
|
|
757
|
+
overlay.addEventListener('click', (event) => {
|
|
758
|
+
if (event.target === overlay) {
|
|
759
|
+
close()
|
|
760
|
+
}
|
|
761
|
+
})
|
|
762
|
+
toolSelect.addEventListener('change', syncTool)
|
|
763
|
+
toolSelect.addEventListener('change', () => {
|
|
764
|
+
const tool = this.selectedAskAiTool()
|
|
765
|
+
setStoredToolPreference(tool && tool.value ? tool.value : '')
|
|
766
|
+
})
|
|
767
|
+
promptTextarea.addEventListener('input', syncRunState)
|
|
768
|
+
runButton.addEventListener('click', run)
|
|
769
|
+
overlay.addEventListener('keydown', (event) => {
|
|
770
|
+
event.stopPropagation()
|
|
771
|
+
if (event.key === 'Escape') {
|
|
772
|
+
event.preventDefault()
|
|
773
|
+
close()
|
|
774
|
+
} else if (event.key === 'Tab') {
|
|
775
|
+
const focusable = getFocusable()
|
|
776
|
+
if (focusable.length === 0) {
|
|
777
|
+
event.preventDefault()
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
const first = focusable[0]
|
|
781
|
+
const last = focusable[focusable.length - 1]
|
|
782
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
783
|
+
event.preventDefault()
|
|
784
|
+
last.focus()
|
|
785
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
786
|
+
event.preventDefault()
|
|
787
|
+
first.focus()
|
|
788
|
+
}
|
|
789
|
+
} else if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
|
790
|
+
event.preventDefault()
|
|
791
|
+
if (!runButton.disabled) {
|
|
792
|
+
run()
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
document.body.appendChild(overlay)
|
|
798
|
+
this.askAiLauncher = {
|
|
799
|
+
overlay,
|
|
800
|
+
panel,
|
|
801
|
+
promptTextarea,
|
|
802
|
+
toolSelect,
|
|
803
|
+
toolLabel,
|
|
804
|
+
toolMeta,
|
|
805
|
+
toolIcon,
|
|
806
|
+
error,
|
|
807
|
+
runButton,
|
|
808
|
+
setOpen,
|
|
809
|
+
syncTool,
|
|
810
|
+
syncRunState
|
|
811
|
+
}
|
|
812
|
+
return this.askAiLauncher
|
|
813
|
+
}
|
|
814
|
+
populateAskAiLauncherTools(tools) {
|
|
815
|
+
const launcher = this.createAskAiLauncher()
|
|
816
|
+
while (launcher.toolSelect.firstChild) {
|
|
817
|
+
launcher.toolSelect.removeChild(launcher.toolSelect.firstChild)
|
|
818
|
+
}
|
|
819
|
+
const placeholder = document.createElement('option')
|
|
820
|
+
placeholder.value = ''
|
|
821
|
+
placeholder.textContent = 'Choose agent'
|
|
822
|
+
launcher.toolSelect.appendChild(placeholder)
|
|
823
|
+
tools.forEach((tool, index) => {
|
|
824
|
+
const option = document.createElement('option')
|
|
825
|
+
option.value = String(index)
|
|
826
|
+
option.textContent = askAiToolOptionLabel(tool)
|
|
827
|
+
launcher.toolSelect.appendChild(option)
|
|
828
|
+
})
|
|
829
|
+
const preferredTool = getStoredToolPreference()
|
|
830
|
+
const preferredIndex = preferredTool
|
|
831
|
+
? tools.findIndex((tool) => tool && tool.value === preferredTool)
|
|
832
|
+
: -1
|
|
833
|
+
launcher.toolSelect.value = preferredIndex >= 0 ? String(preferredIndex) : ''
|
|
834
|
+
if (preferredTool && preferredIndex < 0) {
|
|
835
|
+
setStoredToolPreference('')
|
|
836
|
+
}
|
|
837
|
+
launcher.syncTool()
|
|
838
|
+
}
|
|
839
|
+
selectedAskAiTool() {
|
|
840
|
+
const launcher = this.askAiLauncher
|
|
841
|
+
if (!launcher || !Array.isArray(this.askAiTools)) {
|
|
842
|
+
return null
|
|
843
|
+
}
|
|
844
|
+
const index = Number.parseInt(launcher.toolSelect.value, 10)
|
|
845
|
+
return Number.isFinite(index) ? this.askAiTools[index] || null : null
|
|
846
|
+
}
|
|
847
|
+
async openAskAiModal() {
|
|
848
|
+
if (!this.currentMarkdown && !this.reviewMarkdown) {
|
|
849
|
+
this.updateDraftSizeReview()
|
|
850
|
+
return
|
|
851
|
+
}
|
|
852
|
+
this.setAskAiBusy(true)
|
|
853
|
+
let tools = []
|
|
854
|
+
try {
|
|
855
|
+
tools = await this.loadAskAiTools()
|
|
856
|
+
} catch (error) {
|
|
857
|
+
this.setStatus(error && error.message ? error.message : 'Failed to load plugins.', true)
|
|
858
|
+
this.setAskAiBusy(false)
|
|
859
|
+
return
|
|
860
|
+
}
|
|
861
|
+
this.setAskAiBusy(false)
|
|
862
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
863
|
+
this.setStatus('No AI plugins are available.', true)
|
|
864
|
+
return
|
|
865
|
+
}
|
|
866
|
+
const launcher = this.createAskAiLauncher()
|
|
867
|
+
this.populateAskAiLauncherTools(tools)
|
|
868
|
+
launcher.error.textContent = ''
|
|
869
|
+
launcher.promptTextarea.value = ASK_AI_DEFAULT_PROMPT
|
|
870
|
+
launcher.setOpen(true)
|
|
871
|
+
}
|
|
287
872
|
formatGenerated(value) {
|
|
288
873
|
if (!value) return '--'
|
|
289
874
|
const date = new Date(value)
|
|
@@ -393,7 +978,7 @@
|
|
|
393
978
|
updateDraftTitleNote() {
|
|
394
979
|
if (!this.draftTitleNoteEl) return
|
|
395
980
|
let note = ''
|
|
396
|
-
if (this.draftTitleInput && !this.draftTitleInput.disabled && !this.draftTitleInput.value.trim()) {
|
|
981
|
+
if (this.draftTitleInput && this.draftTitleInput.type !== 'hidden' && !this.draftTitleInput.disabled && !this.draftTitleInput.value.trim()) {
|
|
397
982
|
note = 'Title required'
|
|
398
983
|
}
|
|
399
984
|
this.draftTitleNoteEl.textContent = note
|
|
@@ -402,7 +987,7 @@
|
|
|
402
987
|
this.draftTitleSuggestion = this.suggestDraftTitle()
|
|
403
988
|
if (this.draftTitleInput) {
|
|
404
989
|
if (force || !this.draftTitleEdited) {
|
|
405
|
-
this.draftTitleInput.value = this.draftTitleSuggestion.title
|
|
990
|
+
this.draftTitleInput.value = this.draftTitleSuggestion.title || this.defaultDraftTitle()
|
|
406
991
|
this.draftTitleEdited = false
|
|
407
992
|
}
|
|
408
993
|
this.draftTitleInput.disabled = !this.reviewMarkdown
|
|
@@ -882,7 +1467,7 @@
|
|
|
882
1467
|
isError = true
|
|
883
1468
|
message = 'Add a title before posting to Community.'
|
|
884
1469
|
} else if (hasText) {
|
|
885
|
-
message = 'Ready
|
|
1470
|
+
message = 'Ready for Ask AI or Community.'
|
|
886
1471
|
}
|
|
887
1472
|
this.draftStatusEl.textContent = message
|
|
888
1473
|
this.draftStatusEl.classList.toggle('is-error', isError)
|
|
@@ -891,6 +1476,11 @@
|
|
|
891
1476
|
this.draftTitleInput.disabled = !hasText
|
|
892
1477
|
}
|
|
893
1478
|
this.updateDraftTitleNote()
|
|
1479
|
+
if (this.askAiButton) {
|
|
1480
|
+
const canAsk = hasText && !this.filtering
|
|
1481
|
+
this.askAiButton.disabled = !canAsk
|
|
1482
|
+
this.askAiButton.title = canAsk ? 'Launch a local AI agent with this report context' : 'Latest report is not ready yet'
|
|
1483
|
+
}
|
|
894
1484
|
if (this.createDraftButton && !this.importingDraft) {
|
|
895
1485
|
const canCreate = hasText && hasTitle && !this.draftOversized && !this.filtering && Boolean(this.draftUrl)
|
|
896
1486
|
this.createDraftButton.disabled = !canCreate
|
|
@@ -1766,6 +2356,7 @@
|
|
|
1766
2356
|
statusEl: document.getElementById('logs-report-status'),
|
|
1767
2357
|
outputEl: document.getElementById('logs-report-output'),
|
|
1768
2358
|
copyButton: document.getElementById('logs-copy-report'),
|
|
2359
|
+
askAiButton: document.getElementById('logs-ask-ai'),
|
|
1769
2360
|
createDraftButton: document.getElementById('logs-create-draft'),
|
|
1770
2361
|
draftTitleInput: document.getElementById('logs-draft-title'),
|
|
1771
2362
|
draftTitleNoteEl: document.getElementById('logs-draft-title-note'),
|
|
@@ -1779,7 +2370,9 @@
|
|
|
1779
2370
|
draftStatusEl: document.getElementById('logs-draft-status'),
|
|
1780
2371
|
reviewListEl: document.getElementById('logs-redaction-list'),
|
|
1781
2372
|
reviewFiltersEl: document.getElementById('logs-redaction-filters'),
|
|
1782
|
-
reviewCountEl: document.getElementById('logs-redaction-count')
|
|
2373
|
+
reviewCountEl: document.getElementById('logs-redaction-count'),
|
|
2374
|
+
workspace: this.workspace,
|
|
2375
|
+
workspaceCwd: config.workspaceCwd || ''
|
|
1783
2376
|
})
|
|
1784
2377
|
this.viewer = new LogsViewer({
|
|
1785
2378
|
outputEl: document.getElementById('logs-viewer-output'),
|