@64mb/freelens-power 1.0.1 → 1.0.3

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.
Files changed (54) hide show
  1. package/README.md +4 -4
  2. package/THIRD_PARTY_LICENSES +41 -0
  3. package/out/main/ai.js +9 -6
  4. package/out/main/ai.js.map +1 -1
  5. package/out/main/common/ai-extras.js +17 -0
  6. package/out/main/common/ai-extras.js.map +1 -1
  7. package/out/main/common/dashboard-readiness.js +88 -0
  8. package/out/main/common/dashboard-readiness.js.map +1 -0
  9. package/out/main/common/dashboard-selector.js +9 -1
  10. package/out/main/common/dashboard-selector.js.map +1 -1
  11. package/out/main/common/dashboard-views.js +2 -0
  12. package/out/main/common/dashboard-views.js.map +1 -1
  13. package/out/main/dashboard-network.js +104 -0
  14. package/out/main/dashboard-network.js.map +1 -0
  15. package/out/main/dashboard-views.js +98 -21
  16. package/out/main/dashboard-views.js.map +1 -1
  17. package/out/renderer/ai-extras.js +41 -12
  18. package/out/renderer/ai-extras.js.map +1 -1
  19. package/out/renderer/ai-message.js +1 -0
  20. package/out/renderer/ai-message.js.map +1 -1
  21. package/out/renderer/ai-provider-icon.js +44 -0
  22. package/out/renderer/ai-provider-icon.js.map +1 -0
  23. package/out/renderer/ai-shortcuts.js +1 -1
  24. package/out/renderer/ai-shortcuts.js.map +1 -1
  25. package/out/renderer/ai.js +53 -10
  26. package/out/renderer/ai.js.map +1 -1
  27. package/out/renderer/appearance.js +2 -1
  28. package/out/renderer/appearance.js.map +1 -1
  29. package/out/renderer/assets/claude.js +6 -0
  30. package/out/renderer/assets/claude.js.map +1 -0
  31. package/out/renderer/dashboard.js +39 -4
  32. package/out/renderer/dashboard.js.map +1 -1
  33. package/out/renderer/index.js +2 -0
  34. package/out/renderer/index.js.map +1 -1
  35. package/out/renderer/menus/secure-node-shell-menu.js +1 -1
  36. package/out/renderer/menus/secure-node-shell-menu.js.map +1 -1
  37. package/out/renderer/monitoring.js +672 -91
  38. package/out/renderer/monitoring.js.map +1 -1
  39. package/out/renderer/pod-monitoring.js +282 -0
  40. package/out/renderer/pod-monitoring.js.map +1 -0
  41. package/out/renderer/preferences/ai-preferences.js +2 -2
  42. package/out/renderer/preferences/ai-preferences.js.map +1 -1
  43. package/out/renderer/preferences/dashboard-preferences.js +1 -8
  44. package/out/renderer/preferences/dashboard-preferences.js.map +1 -1
  45. package/out/renderer/preferences/preferences.js +1 -1
  46. package/out/renderer/preferences/preferences.js.map +1 -1
  47. package/out/renderer/preferences/settings-icon-action.js +3 -1
  48. package/out/renderer/preferences/settings-icon-action.js.map +1 -1
  49. package/out/renderer/src/common/ai-extras.js +17 -0
  50. package/out/renderer/src/common/ai-extras.js.map +1 -1
  51. package/out/renderer/src/common/dashboard-selector.js.map +1 -1
  52. package/out/renderer/src/common/dashboard-views.js +2 -0
  53. package/out/renderer/src/common/dashboard-views.js.map +1 -1
  54. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ai-extras.js","names":[],"sources":["../../src/renderer/ai-extras.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { useState } from \"react\";\nimport { type AiResourceReference, normalizeAiReferences, validAiResourceReference } from \"../common/ai-extras\";\n\nimport type { AiChatRequest } from \"../common/ai\";\n\nexport function AiContextPreview({ context }: { context: AiChatRequest[\"context\"] }) {\n return (\n <details className=\"power-ai-context-preview\" style={{ padding: \"8px 16px\", fontSize: 11 }}>\n <summary>Attached context</summary>\n <pre style={{ whiteSpace: \"pre-wrap\", overflowWrap: \"anywhere\", maxHeight: 160, overflowY: \"auto\" }}>\n {JSON.stringify(context, null, 2)}\n </pre>\n <p>Screen and resource metadata only. Resource reads happen after sending.</p>\n </details>\n );\n}\n\nexport async function openAiResourceReference(clusterId: string, reference: AiResourceReference): Promise<void> {\n if (!validAiResourceReference(reference) || Renderer.Catalog.activeCluster.get()?.metadata.uid !== clusterId)\n throw new Error(\"Select the original cluster to open this resource\");\n const api = Renderer.K8sApi.apiManager.getApiByKind(reference.kind, reference.apiVersion);\n if (!api || api.isNamespaced !== Boolean(reference.namespace)) throw new Error(\"Resource details are unavailable\");\n const resource = await api.get({ name: reference.name, namespace: reference.namespace });\n if (!resource || Renderer.Catalog.activeCluster.get()?.metadata.uid !== clusterId)\n throw new Error(\"Resource details are unavailable\");\n Renderer.Navigation.showDetails(api.getUrl({ name: reference.name, namespace: reference.namespace }));\n}\n\nexport function AiResourceReferences({ clusterId, references }: { clusterId: string; references: unknown }) {\n const [error, setError] = useState(\"\");\n const items = normalizeAiReferences(references);\n if (!items.length) return null;\n return (\n <div className=\"power-ai-resource-references\" style={{ marginTop: 12 }}>\n <span style={{ fontSize: 11, color: \"var(--textColorSecondary)\" }}>Related resources</span>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 6, marginTop: 6 }}>\n {items.map((reference) => (\n <button\n type=\"button\"\n key={JSON.stringify(reference)}\n title={`${reference.kind} · ${reference.namespace ?? \"cluster scoped\"}`}\n onClick={() => {\n setError(\"\");\n void openAiResourceReference(clusterId, reference).catch(() =>\n setError(\"Unable to open this resource. Check the selected cluster and your access.\"),\n );\n }}\n >\n {reference.kind}/{reference.name}\n {reference.namespace ? ` · ${reference.namespace}` : \"\"}\n </button>\n ))}\n </div>\n {error && <p role=\"alert\">{error}</p>}\n </div>\n );\n}\n"],"mappings":";;;;;AAMA,SAAgB,iBAAiB,EAAE,WAAkD;CACnF,OACE,oCAAA,KAAC,WAAD;EAAS,WAAU;EAA2B,OAAO;GAAE,SAAS;GAAY,UAAU;EAAG;YAAzF;GACE,oCAAA,IAAC,WAAD,EAAA,UAAS,mBAAyB,CAAA;GAClC,oCAAA,IAAC,OAAD;IAAK,OAAO;KAAE,YAAY;KAAY,cAAc;KAAY,WAAW;KAAK,WAAW;IAAO;cAC/F,KAAK,UAAU,SAAS,MAAM,CAAC;GAC7B,CAAA;GACL,oCAAA,IAAC,KAAD,EAAA,UAAG,0EAA0E,CAAA;EACtE;;AAEb;AAEA,eAAsB,wBAAwB,WAAmB,WAA+C;CAC9G,IAAI,CAAC,kBAAA,yBAAyB,SAAS,KAAK,mBAAA,SAAS,QAAQ,cAAc,IAAI,CAAC,EAAE,SAAS,QAAQ,WACjG,MAAM,IAAI,MAAM,mDAAmD;CACrE,MAAM,MAAM,mBAAA,SAAS,OAAO,WAAW,aAAa,UAAU,MAAM,UAAU,UAAU;CACxF,IAAI,CAAC,OAAO,IAAI,iBAAiB,QAAQ,UAAU,SAAS,GAAG,MAAM,IAAI,MAAM,kCAAkC;CAEjH,IAAI,CAAC,MADkB,IAAI,IAAI;EAAE,MAAM,UAAU;EAAM,WAAW,UAAU;CAAU,CAAC,KACtE,mBAAA,SAAS,QAAQ,cAAc,IAAI,CAAC,EAAE,SAAS,QAAQ,WACtE,MAAM,IAAI,MAAM,kCAAkC;CACpD,mBAAA,SAAS,WAAW,YAAY,IAAI,OAAO;EAAE,MAAM,UAAU;EAAM,WAAW,UAAU;CAAU,CAAC,CAAC;AACtG;AAEA,SAAgB,qBAAqB,EAAE,WAAW,cAA0D;CAC1G,MAAM,CAAC,OAAO,YAAY,+BAAA,SAAS,EAAE;CACrC,MAAM,QAAQ,kBAAA,sBAAsB,UAAU;CAC9C,IAAI,CAAC,MAAM,QAAQ,OAAO;CAC1B,OACE,oCAAA,KAAC,OAAD;EAAK,WAAU;EAA+B,OAAO,EAAE,WAAW,GAAG;YAArE;GACE,oCAAA,IAAC,QAAD;IAAM,OAAO;KAAE,UAAU;KAAI,OAAO;IAA4B;cAAG;GAAuB,CAAA;GAC1F,oCAAA,IAAC,OAAD;IAAK,OAAO;KAAE,SAAS;KAAQ,UAAU;KAAQ,KAAK;KAAG,WAAW;IAAE;cACnE,MAAM,KAAK,cACV,oCAAA,KAAC,UAAD;KACE,MAAK;KAEL,OAAO,GAAG,UAAU,KAAK,KAAK,UAAU,aAAa;KACrD,eAAe;MACb,SAAS,EAAE;MACX,wBAA6B,WAAW,SAAS,CAAC,CAAC,YACjD,SAAS,2EAA2E,CACtF;KACF;eATF;MAWG,UAAU;MAAK;MAAE,UAAU;MAC3B,UAAU,YAAY,MAAM,UAAU,cAAc;KAC/C;OAXD,KAAK,UAAU,SAAS,CAWvB,CACT;GACE,CAAA;GACJ,SAAS,oCAAA,IAAC,KAAD;IAAG,MAAK;cAAS;GAAS,CAAA;EACjC;;AAET"}
1
+ {"version":3,"file":"ai-extras.js","names":[],"sources":["../../src/renderer/ai-extras.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { useState } from \"react\";\nimport { type AiResourceReference, referencedAiResources, validAiResourceReference } from \"../common/ai-extras\";\nimport { AiIcon } from \"./ai-icon\";\n\nimport type { AiChatRequest } from \"../common/ai\";\n\nexport function AiContextPreview({ context }: { context: AiChatRequest[\"context\"] }) {\n return (\n <details className=\"power-ai-context-preview\" style={{ padding: \"8px 16px\", fontSize: 11 }}>\n <summary title=\"Show the screen and resource metadata attached to your next message\">Attached context</summary>\n <pre style={{ whiteSpace: \"pre-wrap\", overflowWrap: \"anywhere\", maxHeight: 160, overflowY: \"auto\" }}>\n {JSON.stringify(context, null, 2)}\n </pre>\n <p>Screen and resource metadata only. Resource reads happen after sending.</p>\n </details>\n );\n}\n\nexport async function openAiResourceReference(clusterId: string, reference: AiResourceReference): Promise<void> {\n if (!validAiResourceReference(reference) || Renderer.Catalog.activeCluster.get()?.metadata.uid !== clusterId)\n throw new Error(\"Select the original cluster to open this resource\");\n const api = Renderer.K8sApi.apiManager.getApiByKind(reference.kind, reference.apiVersion);\n if (!api || api.isNamespaced !== Boolean(reference.namespace)) throw new Error(\"Resource details are unavailable\");\n const resource = await api.get({ name: reference.name, namespace: reference.namespace });\n if (!resource || Renderer.Catalog.activeCluster.get()?.metadata.uid !== clusterId)\n throw new Error(\"Resource details are unavailable\");\n await Renderer.Navigation.showDetails(api.getUrl({ name: reference.name, namespace: reference.namespace }));\n}\n\nexport function AiResourceReferences({\n clusterId,\n references,\n text,\n onOpened,\n}: {\n clusterId: string;\n references: unknown;\n text: string;\n onOpened?: () => void;\n}) {\n const [error, setError] = useState(\"\");\n const items = referencedAiResources(text, references);\n if (!items.length) return null;\n return (\n <div className=\"power-ai-resource-references\" style={{ marginTop: 12 }}>\n <style>{`.power-ai-resource-references button.power-ai-resource-reference:hover { color: var(--primary); border-color: var(--primary); background: color-mix(in srgb, var(--primary) 10%, var(--contentColor)); } .power-ai-resource-references button.power-ai-resource-reference:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }`}</style>\n <span style={{ fontSize: 11, color: \"var(--textColorSecondary)\" }}>Related resources</span>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 6, marginTop: 6 }}>\n {items.map((reference) => (\n <button\n type=\"button\"\n key={JSON.stringify(reference)}\n className=\"power-ai-resource-reference\"\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n maxWidth: \"100%\",\n minWidth: 0,\n padding: \"4px 8px\",\n fontSize: 12,\n lineHeight: 1.4,\n border: \"1px solid var(--borderFaintColor)\",\n borderRadius: 6,\n }}\n title={`Open ${reference.kind}/${reference.name} details · ${reference.namespace ?? \"cluster scoped\"}`}\n onClick={() => {\n setError(\"\");\n void openAiResourceReference(clusterId, reference)\n .then(onOpened)\n .catch(() => setError(\"Unable to open this resource. Check the selected cluster and your access.\"));\n }}\n >\n <span style={{ minWidth: 0, overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}>\n {reference.kind}/{reference.name}\n {reference.namespace ? ` · ${reference.namespace}` : \"\"}\n </span>\n <AiIcon material=\"arrow_forward\" size={14} />\n </button>\n ))}\n </div>\n {error && <p role=\"alert\">{error}</p>}\n </div>\n );\n}\n"],"mappings":";;;;;;AAOA,SAAgB,iBAAiB,EAAE,WAAkD;CACnF,OACE,oCAAA,KAAC,WAAD;EAAS,WAAU;EAA2B,OAAO;GAAE,SAAS;GAAY,UAAU;EAAG;YAAzF;GACE,oCAAA,IAAC,WAAD;IAAS,OAAM;cAAsE;GAAyB,CAAA;GAC9G,oCAAA,IAAC,OAAD;IAAK,OAAO;KAAE,YAAY;KAAY,cAAc;KAAY,WAAW;KAAK,WAAW;IAAO;cAC/F,KAAK,UAAU,SAAS,MAAM,CAAC;GAC7B,CAAA;GACL,oCAAA,IAAC,KAAD,EAAA,UAAG,0EAA0E,CAAA;EACtE;;AAEb;AAEA,eAAsB,wBAAwB,WAAmB,WAA+C;CAC9G,IAAI,CAAC,kBAAA,yBAAyB,SAAS,KAAK,mBAAA,SAAS,QAAQ,cAAc,IAAI,CAAC,EAAE,SAAS,QAAQ,WACjG,MAAM,IAAI,MAAM,mDAAmD;CACrE,MAAM,MAAM,mBAAA,SAAS,OAAO,WAAW,aAAa,UAAU,MAAM,UAAU,UAAU;CACxF,IAAI,CAAC,OAAO,IAAI,iBAAiB,QAAQ,UAAU,SAAS,GAAG,MAAM,IAAI,MAAM,kCAAkC;CAEjH,IAAI,CAAC,MADkB,IAAI,IAAI;EAAE,MAAM,UAAU;EAAM,WAAW,UAAU;CAAU,CAAC,KACtE,mBAAA,SAAS,QAAQ,cAAc,IAAI,CAAC,EAAE,SAAS,QAAQ,WACtE,MAAM,IAAI,MAAM,kCAAkC;CACpD,MAAM,mBAAA,SAAS,WAAW,YAAY,IAAI,OAAO;EAAE,MAAM,UAAU;EAAM,WAAW,UAAU;CAAU,CAAC,CAAC;AAC5G;AAEA,SAAgB,qBAAqB,EACnC,WACA,YACA,MACA,YAMC;CACD,MAAM,CAAC,OAAO,YAAY,+BAAA,SAAS,EAAE;CACrC,MAAM,QAAQ,kBAAA,sBAAsB,MAAM,UAAU;CACpD,IAAI,CAAC,MAAM,QAAQ,OAAO;CAC1B,OACE,oCAAA,KAAC,OAAD;EAAK,WAAU;EAA+B,OAAO,EAAE,WAAW,GAAG;YAArE;GACE,oCAAA,IAAC,SAAD,EAAA,UAAQ,sVAA6V,CAAA;GACrW,oCAAA,IAAC,QAAD;IAAM,OAAO;KAAE,UAAU;KAAI,OAAO;IAA4B;cAAG;GAAuB,CAAA;GAC1F,oCAAA,IAAC,OAAD;IAAK,OAAO;KAAE,SAAS;KAAQ,UAAU;KAAQ,KAAK;KAAG,WAAW;IAAE;cACnE,MAAM,KAAK,cACV,oCAAA,KAAC,UAAD;KACE,MAAK;KAEL,WAAU;KACV,OAAO;MACL,SAAS;MACT,YAAY;MACZ,KAAK;MACL,UAAU;MACV,UAAU;MACV,SAAS;MACT,UAAU;MACV,YAAY;MACZ,QAAQ;MACR,cAAc;KAChB;KACA,OAAO,QAAQ,UAAU,KAAK,GAAG,UAAU,KAAK,aAAa,UAAU,aAAa;KACpF,eAAe;MACb,SAAS,EAAE;MACX,wBAA6B,WAAW,SAAS,CAAC,CAC/C,KAAK,QAAQ,CAAC,CACd,YAAY,SAAS,2EAA2E,CAAC;KACtG;eAtBF,CAwBE,oCAAA,KAAC,QAAD;MAAM,OAAO;OAAE,UAAU;OAAG,UAAU;OAAU,cAAc;OAAY,YAAY;MAAS;gBAA/F;OACG,UAAU;OAAK;OAAE,UAAU;OAC3B,UAAU,YAAY,MAAM,UAAU,cAAc;MACjD;SACN,oCAAA,IAAC,gBAAA,QAAD;MAAQ,UAAS;MAAgB,MAAM;KAAK,CAAA,CACtC;OA3BD,KAAK,UAAU,SAAS,CA2BvB,CACT;GACE,CAAA;GACJ,SAAS,oCAAA,IAAC,KAAD;IAAG,MAAK;cAAS;GAAS,CAAA;EACjC;;AAET"}
@@ -209,6 +209,7 @@ function CodeBlock({ text, language }) {
209
209
  children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: language || "code" }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", { children: [/* @__PURE__ */ require_jsx_runtime.jsxs("button", {
210
210
  type: "button",
211
211
  "aria-label": "Copy code",
212
+ title: "Copy code to clipboard",
212
213
  onClick: () => {
213
214
  try {
214
215
  electron.clipboard.writeText(text);
@@ -1 +1 @@
1
- {"version":3,"file":"ai-message.js","names":[],"sources":["../../src/renderer/ai-message.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { clipboard, shell } from \"electron\";\nimport MarkdownIt, { type Token } from \"markdown-it\";\nimport { autorun } from \"mobx\";\nimport { createElement, Fragment, type ReactNode, useEffect, useMemo, useState } from \"react\";\nimport { AiIcon } from \"./ai-icon\";\n\nconst markdown = new MarkdownIt({ html: false, linkify: true, breaks: true });\nconst markdownTags = new Set([\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"ul\",\n \"ol\",\n \"li\",\n \"blockquote\",\n \"table\",\n \"thead\",\n \"tbody\",\n \"tr\",\n \"th\",\n \"td\",\n \"strong\",\n \"em\",\n \"s\",\n \"a\",\n]);\n\nfunction externalLink(value: string | number | null): string | undefined {\n if (typeof value !== \"string\" || !value) return;\n try {\n const url = new URL(value);\n if ([\"https:\", \"http:\", \"mailto:\"].includes(url.protocol) && !url.username && !url.password) return url.href;\n } catch {}\n return;\n}\n\nfunction renderTokens(tokens: Token[]): ReactNode[] {\n type Container = { token?: Token; key: number; children: ReactNode[] };\n const stack: Container[] = [{ key: 0, children: [] }];\n const append = (node: ReactNode) => stack[stack.length - 1].children.push(node);\n tokens.forEach((token, index) => {\n if (token.nesting === 1) {\n stack.push({ token, key: index, children: [] });\n return;\n }\n if (token.nesting === -1) {\n if (stack.length === 1) return;\n const container = stack.pop()!;\n const opening = container.token!;\n const props: Record<string, unknown> = { key: container.key };\n let tag = markdownTags.has(opening.tag) && !opening.hidden ? opening.tag : Fragment;\n if (tag === \"a\") {\n const href = externalLink(opening.attrGet(\"href\"));\n if (!href) tag = Fragment;\n else {\n props.href = href;\n props.title = opening.attrGet(\"title\") ?? undefined;\n props.rel = \"noopener noreferrer\";\n props.target = \"_blank\";\n const open = (event: React.MouseEvent<HTMLAnchorElement>) => {\n event.preventDefault();\n if (event.type === \"auxclick\" && event.button !== 1) return;\n void shell.openExternal(href).catch(() => {});\n };\n props.onClick = open;\n props.onAuxClick = open;\n }\n }\n if (tag === \"ol\") {\n const start = Number(opening.attrGet(\"start\"));\n if (Number.isSafeInteger(start) && start > 0) props.start = start;\n }\n if (tag === \"th\" || tag === \"td\") {\n const alignment = String(opening.attrGet(\"style\") ?? \"\").match(/^text-align:(left|right|center)$/)?.[1];\n if (alignment) props.style = { textAlign: alignment };\n }\n append(createElement(tag, props, ...container.children));\n return;\n }\n switch (token.type) {\n case \"inline\":\n append(<Fragment key={index}>{renderTokens(token.children ?? [])}</Fragment>);\n break;\n case \"code_inline\":\n append(<code key={index}>{token.content}</code>);\n break;\n case \"fence\":\n case \"code_block\":\n append(<CodeBlock key={index} language={token.info.trim().split(/\\s+/)[0]} text={token.content} />);\n break;\n case \"hardbreak\":\n case \"softbreak\":\n append(<br key={index} />);\n break;\n case \"hr\":\n append(<hr key={index} />);\n break;\n default:\n append(<Fragment key={index}>{token.content}</Fragment>);\n }\n });\n return stack[0].children;\n}\n\nexport function terminalInsertText(text: string): string | undefined {\n const command = text.replace(/^[\\r\\n]+|[\\r\\n]+$/g, \"\");\n // surrounding line endings are formatting, embedded control characters must never reach the terminal\n if (!command.trim() || /[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]/u.test(command)) return undefined;\n return command;\n}\n\nfunction visibleTerminalId(): string | undefined {\n const prefix = \"dock-tab-content-for-\";\n for (const element of document.querySelectorAll<HTMLElement>(`[data-testid^=\"${prefix}\"]`)) {\n let visible = true;\n for (let node: HTMLElement | null = element; node; node = node.parentElement) {\n const style = getComputedStyle(node);\n if (\n node.hidden ||\n node.getAttribute(\"aria-hidden\") === \"true\" ||\n style.display === \"none\" ||\n style.visibility === \"hidden\" ||\n style.visibility === \"collapse\"\n ) {\n visible = false;\n break;\n }\n }\n if (!visible) continue;\n const id = element.dataset.testid?.slice(prefix.length);\n if (!id || !Renderer.Component.terminalStore.isConnected(id)) continue;\n const api = Renderer.Component.terminalStore.getTerminalApi(id);\n if (api?.isReady && api.isConnected()) return id;\n }\n return;\n}\n\nfunction useActiveTerminalId(): string | undefined {\n const [terminalId, setTerminalId] = useState<string>();\n useEffect(() => {\n let stop: (() => void) | undefined;\n const update = () => {\n stop?.();\n stop = autorun(() => setTerminalId(visibleTerminalId()));\n };\n update();\n const observer = new MutationObserver(update);\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"class\", \"style\", \"hidden\", \"aria-hidden\", \"data-testid\"],\n });\n return () => {\n observer.disconnect();\n stop?.();\n };\n }, []);\n return terminalId;\n}\n\nfunction CodeBlock({ text, language }: { text: string; language: string }) {\n const [feedback, setFeedback] = useState(\"\");\n const terminalId = useActiveTerminalId();\n const [inserting, setInserting] = useState(false);\n const shell = [\"\", \"bash\", \"sh\", \"shell\", \"zsh\", \"console\", \"terminal\"].includes(language.toLowerCase());\n const command = terminalInsertText(text);\n const insert = async () => {\n const activeTerminal = visibleTerminalId();\n if (!shell || !command || !activeTerminal || inserting) return;\n setInserting(true);\n try {\n await Renderer.Component.terminalStore.sendCommand(command, { enter: false, tabId: activeTerminal });\n setFeedback(\"Inserted into terminal. Review the command before running it.\");\n } catch {\n setFeedback(\"Unable to insert into terminal.\");\n } finally {\n setInserting(false);\n }\n };\n return (\n <div className=\"power-ai-code\">\n <div className=\"power-ai-code-toolbar\">\n <span>{language || \"code\"}</span>\n <div>\n <button\n type=\"button\"\n aria-label=\"Copy code\"\n onClick={() => {\n try {\n clipboard.writeText(text);\n setFeedback(\"Copied\");\n } catch {\n setFeedback(\"Unable to copy code.\");\n }\n }}\n >\n <AiIcon material=\"content_copy\" size={14} /> Copy\n </button>\n {shell && terminalId && (\n <button\n type=\"button\"\n aria-label=\"Insert into terminal\"\n disabled={!command || !terminalId || inserting}\n title={\n !command\n ? \"Only a single line without control characters can be inserted\"\n : !terminalId\n ? \"Focus an open terminal tab to insert\"\n : \"Insert without running\"\n }\n onClick={() => void insert()}\n >\n <AiIcon material=\"terminal\" size={14} /> Insert\n </button>\n )}\n </div>\n </div>\n <pre>\n <code>{text}</code>\n </pre>\n {shell && !command && (\n <div className=\"power-ai-code-hint\">Terminal insertion requires a single line without control characters.</div>\n )}\n <div role=\"status\" className=\"power-ai-code-feedback\">\n {feedback}\n </div>\n </div>\n );\n}\n\nexport function AiMessageContent({ text }: { text: string }) {\n const content = useMemo(() => renderTokens(markdown.parse(text, {})), [text]);\n return <div className=\"power-ai-markdown\">{content}</div>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ai-message.js","names":[],"sources":["../../src/renderer/ai-message.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { clipboard, shell } from \"electron\";\nimport MarkdownIt, { type Token } from \"markdown-it\";\nimport { autorun } from \"mobx\";\nimport { createElement, Fragment, type ReactNode, useEffect, useMemo, useState } from \"react\";\nimport { AiIcon } from \"./ai-icon\";\n\nconst markdown = new MarkdownIt({ html: false, linkify: true, breaks: true });\nconst markdownTags = new Set([\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"ul\",\n \"ol\",\n \"li\",\n \"blockquote\",\n \"table\",\n \"thead\",\n \"tbody\",\n \"tr\",\n \"th\",\n \"td\",\n \"strong\",\n \"em\",\n \"s\",\n \"a\",\n]);\n\nfunction externalLink(value: string | number | null): string | undefined {\n if (typeof value !== \"string\" || !value) return;\n try {\n const url = new URL(value);\n if ([\"https:\", \"http:\", \"mailto:\"].includes(url.protocol) && !url.username && !url.password) return url.href;\n } catch {}\n return;\n}\n\nfunction renderTokens(tokens: Token[]): ReactNode[] {\n type Container = { token?: Token; key: number; children: ReactNode[] };\n const stack: Container[] = [{ key: 0, children: [] }];\n const append = (node: ReactNode) => stack[stack.length - 1].children.push(node);\n tokens.forEach((token, index) => {\n if (token.nesting === 1) {\n stack.push({ token, key: index, children: [] });\n return;\n }\n if (token.nesting === -1) {\n if (stack.length === 1) return;\n const container = stack.pop()!;\n const opening = container.token!;\n const props: Record<string, unknown> = { key: container.key };\n let tag = markdownTags.has(opening.tag) && !opening.hidden ? opening.tag : Fragment;\n if (tag === \"a\") {\n const href = externalLink(opening.attrGet(\"href\"));\n if (!href) tag = Fragment;\n else {\n props.href = href;\n props.title = opening.attrGet(\"title\") ?? undefined;\n props.rel = \"noopener noreferrer\";\n props.target = \"_blank\";\n const open = (event: React.MouseEvent<HTMLAnchorElement>) => {\n event.preventDefault();\n if (event.type === \"auxclick\" && event.button !== 1) return;\n void shell.openExternal(href).catch(() => {});\n };\n props.onClick = open;\n props.onAuxClick = open;\n }\n }\n if (tag === \"ol\") {\n const start = Number(opening.attrGet(\"start\"));\n if (Number.isSafeInteger(start) && start > 0) props.start = start;\n }\n if (tag === \"th\" || tag === \"td\") {\n const alignment = String(opening.attrGet(\"style\") ?? \"\").match(/^text-align:(left|right|center)$/)?.[1];\n if (alignment) props.style = { textAlign: alignment };\n }\n append(createElement(tag, props, ...container.children));\n return;\n }\n switch (token.type) {\n case \"inline\":\n append(<Fragment key={index}>{renderTokens(token.children ?? [])}</Fragment>);\n break;\n case \"code_inline\":\n append(<code key={index}>{token.content}</code>);\n break;\n case \"fence\":\n case \"code_block\":\n append(<CodeBlock key={index} language={token.info.trim().split(/\\s+/)[0]} text={token.content} />);\n break;\n case \"hardbreak\":\n case \"softbreak\":\n append(<br key={index} />);\n break;\n case \"hr\":\n append(<hr key={index} />);\n break;\n default:\n append(<Fragment key={index}>{token.content}</Fragment>);\n }\n });\n return stack[0].children;\n}\n\nexport function terminalInsertText(text: string): string | undefined {\n const command = text.replace(/^[\\r\\n]+|[\\r\\n]+$/g, \"\");\n // surrounding line endings are formatting, embedded control characters must never reach the terminal\n if (!command.trim() || /[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]/u.test(command)) return undefined;\n return command;\n}\n\nfunction visibleTerminalId(): string | undefined {\n const prefix = \"dock-tab-content-for-\";\n for (const element of document.querySelectorAll<HTMLElement>(`[data-testid^=\"${prefix}\"]`)) {\n let visible = true;\n for (let node: HTMLElement | null = element; node; node = node.parentElement) {\n const style = getComputedStyle(node);\n if (\n node.hidden ||\n node.getAttribute(\"aria-hidden\") === \"true\" ||\n style.display === \"none\" ||\n style.visibility === \"hidden\" ||\n style.visibility === \"collapse\"\n ) {\n visible = false;\n break;\n }\n }\n if (!visible) continue;\n const id = element.dataset.testid?.slice(prefix.length);\n if (!id || !Renderer.Component.terminalStore.isConnected(id)) continue;\n const api = Renderer.Component.terminalStore.getTerminalApi(id);\n if (api?.isReady && api.isConnected()) return id;\n }\n return;\n}\n\nfunction useActiveTerminalId(): string | undefined {\n const [terminalId, setTerminalId] = useState<string>();\n useEffect(() => {\n let stop: (() => void) | undefined;\n const update = () => {\n stop?.();\n stop = autorun(() => setTerminalId(visibleTerminalId()));\n };\n update();\n const observer = new MutationObserver(update);\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"class\", \"style\", \"hidden\", \"aria-hidden\", \"data-testid\"],\n });\n return () => {\n observer.disconnect();\n stop?.();\n };\n }, []);\n return terminalId;\n}\n\nfunction CodeBlock({ text, language }: { text: string; language: string }) {\n const [feedback, setFeedback] = useState(\"\");\n const terminalId = useActiveTerminalId();\n const [inserting, setInserting] = useState(false);\n const shell = [\"\", \"bash\", \"sh\", \"shell\", \"zsh\", \"console\", \"terminal\"].includes(language.toLowerCase());\n const command = terminalInsertText(text);\n const insert = async () => {\n const activeTerminal = visibleTerminalId();\n if (!shell || !command || !activeTerminal || inserting) return;\n setInserting(true);\n try {\n await Renderer.Component.terminalStore.sendCommand(command, { enter: false, tabId: activeTerminal });\n setFeedback(\"Inserted into terminal. Review the command before running it.\");\n } catch {\n setFeedback(\"Unable to insert into terminal.\");\n } finally {\n setInserting(false);\n }\n };\n return (\n <div className=\"power-ai-code\">\n <div className=\"power-ai-code-toolbar\">\n <span>{language || \"code\"}</span>\n <div>\n <button\n type=\"button\"\n aria-label=\"Copy code\"\n title=\"Copy code to clipboard\"\n onClick={() => {\n try {\n clipboard.writeText(text);\n setFeedback(\"Copied\");\n } catch {\n setFeedback(\"Unable to copy code.\");\n }\n }}\n >\n <AiIcon material=\"content_copy\" size={14} /> Copy\n </button>\n {shell && terminalId && (\n <button\n type=\"button\"\n aria-label=\"Insert into terminal\"\n disabled={!command || !terminalId || inserting}\n title={\n !command\n ? \"Only a single line without control characters can be inserted\"\n : !terminalId\n ? \"Focus an open terminal tab to insert\"\n : \"Insert without running\"\n }\n onClick={() => void insert()}\n >\n <AiIcon material=\"terminal\" size={14} /> Insert\n </button>\n )}\n </div>\n </div>\n <pre>\n <code>{text}</code>\n </pre>\n {shell && !command && (\n <div className=\"power-ai-code-hint\">Terminal insertion requires a single line without control characters.</div>\n )}\n <div role=\"status\" className=\"power-ai-code-feedback\">\n {feedback}\n </div>\n </div>\n );\n}\n\nexport function AiMessageContent({ text }: { text: string }) {\n const content = useMemo(() => renderTokens(markdown.parse(text, {})), [text]);\n return <div className=\"power-ai-markdown\">{content}</div>;\n}\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ const require_jsx_runtime = require("./_virtual/_global-external_react/jsx-runtime.js");
2
+ const require_claude = require("./assets/claude.js");
3
+ //#region src/renderer/ai-provider-icon.tsx
4
+ function AiProviderIcon({ provider, size = 16 }) {
5
+ if (provider === "codex") return /* @__PURE__ */ require_jsx_runtime.jsxs("svg", {
6
+ className: "power-ai-provider-icon",
7
+ "data-provider": "codex",
8
+ width: size,
9
+ height: size,
10
+ viewBox: "0 0 24 24",
11
+ "aria-hidden": "true",
12
+ fill: "currentColor",
13
+ style: {
14
+ color: "var(--textColorPrimary)",
15
+ flexShrink: 0,
16
+ verticalAlign: "middle"
17
+ },
18
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx("title", { children: "Codex" }), /* @__PURE__ */ require_jsx_runtime.jsx("path", {
19
+ fillRule: "evenodd",
20
+ d: "M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"
21
+ })]
22
+ });
23
+ return /* @__PURE__ */ require_jsx_runtime.jsx("img", {
24
+ className: "power-ai-provider-icon",
25
+ "data-provider": provider,
26
+ src: require_claude.default,
27
+ width: size,
28
+ height: size,
29
+ title: provider === "claude" ? "Claude" : "Codex",
30
+ alt: "",
31
+ "aria-hidden": "true",
32
+ draggable: false,
33
+ style: {
34
+ display: "inline-block",
35
+ flexShrink: 0,
36
+ objectFit: "contain",
37
+ verticalAlign: "middle"
38
+ }
39
+ });
40
+ }
41
+ //#endregion
42
+ exports.AiProviderIcon = AiProviderIcon;
43
+
44
+ //# sourceMappingURL=ai-provider-icon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-provider-icon.js","names":[],"sources":["../../src/renderer/ai-provider-icon.tsx"],"sourcesContent":["import claudeIcon from \"./assets/claude.svg?inline\";\n\nexport function AiProviderIcon({ provider, size = 16 }: { provider: \"codex\" | \"claude\"; size?: number }) {\n if (provider === \"codex\")\n return (\n <svg\n className=\"power-ai-provider-icon\"\n data-provider=\"codex\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n fill=\"currentColor\"\n style={{ color: \"var(--textColorPrimary)\", flexShrink: 0, verticalAlign: \"middle\" }}\n >\n <title>Codex</title>\n <path\n fillRule=\"evenodd\"\n d=\"M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z\"\n />\n </svg>\n );\n return (\n <img\n className=\"power-ai-provider-icon\"\n data-provider={provider}\n src={claudeIcon}\n width={size}\n height={size}\n title={provider === \"claude\" ? \"Claude\" : \"Codex\"}\n alt=\"\"\n aria-hidden=\"true\"\n draggable={false}\n style={{ display: \"inline-block\", flexShrink: 0, objectFit: \"contain\", verticalAlign: \"middle\" }}\n />\n );\n}\n"],"mappings":";;;AAEA,SAAgB,eAAe,EAAE,UAAU,OAAO,MAAuD;CACvG,IAAI,aAAa,SACf,OACE,oCAAA,KAAC,OAAD;EACE,WAAU;EACV,iBAAc;EACd,OAAO;EACP,QAAQ;EACR,SAAQ;EACR,eAAY;EACZ,MAAK;EACL,OAAO;GAAE,OAAO;GAA2B,YAAY;GAAG,eAAe;EAAS;YARpF,CAUE,oCAAA,IAAC,SAAD,EAAA,UAAO,QAAY,CAAA,GACnB,oCAAA,IAAC,QAAD;GACE,UAAS;GACT,GAAE;EACH,CAAA,CACE;;CAET,OACE,oCAAA,IAAC,OAAD;EACE,WAAU;EACV,iBAAe;EACf,KAAK,eAAA;EACL,OAAO;EACP,QAAQ;EACR,OAAO,aAAa,WAAW,WAAW;EAC1C,KAAI;EACJ,eAAY;EACZ,WAAW;EACX,OAAO;GAAE,SAAS;GAAgB,YAAY;GAAG,WAAW;GAAW,eAAe;EAAS;CAChG,CAAA;AAEL"}
@@ -22,7 +22,7 @@ var AiStarterPrompts = require__global_external_mobx_react.observer(({ onSelect,
22
22
  minHeight: 32
23
23
  },
24
24
  disabled,
25
- title: `/${shortcut.command}`,
25
+ title: `Insert /${shortcut.command}: ${shortcut.label}`,
26
26
  onClick: () => onSelect(shortcut.prompt),
27
27
  children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, { material: {
28
28
  health: "favorite_border",
@@ -1 +1 @@
1
- {"version":3,"file":"ai-shortcuts.js","names":[],"sources":["../../src/renderer/ai-shortcuts.tsx"],"sourcesContent":["import { observer } from \"mobx-react\";\nimport { AiShortcutsStore } from \"../common/store/ai-shortcuts-store\";\nimport { AiIcon } from \"./ai-icon\";\n\nexport const AiStarterPrompts = observer(\n ({ onSelect, disabled = false }: { onSelect: (prompt: string) => void; disabled?: boolean }) => {\n const preferences = AiShortcutsStore.getInstanceOrCreate<AiShortcutsStore>();\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"flex-start\", gap: 10 }}>\n {preferences.shortcuts.map((shortcut) => (\n <button\n type=\"button\"\n className=\"Button\"\n style={{ display: \"inline-flex\", alignItems: \"center\", gap: 10, padding: \"6px 10px\", minHeight: 32 }}\n key={shortcut.id}\n disabled={disabled}\n title={`/${shortcut.command}`}\n onClick={() => onSelect(shortcut.prompt)}\n >\n <AiIcon\n material={\n ({ health: \"favorite_border\", pods: \"view_module\", logs: \"subject\" } as Record<string, string>)[\n shortcut.command\n ] ?? \"chat_bubble_outline\"\n }\n />\n {shortcut.label}\n </button>\n ))}\n </div>\n );\n },\n);\n"],"mappings":";;;;;AAIA,IAAa,mBAAmB,oCAAA,UAC7B,EAAE,UAAU,WAAW,YAAwE;CAE9F,OACE,oCAAA,IAAC,OAAD;EAAK,OAAO;GAAE,SAAS;GAAQ,eAAe;GAAU,YAAY;GAAc,KAAK;EAAG;YAFxE,2BAAA,iBAAiB,oBAGhC,CAAA,CAAY,UAAU,KAAK,aAC1B,oCAAA,KAAC,UAAD;GACE,MAAK;GACL,WAAU;GACV,OAAO;IAAE,SAAS;IAAe,YAAY;IAAU,KAAK;IAAI,SAAS;IAAY,WAAW;GAAG;GAEzF;GACV,OAAO,IAAI,SAAS;GACpB,eAAe,SAAS,SAAS,MAAM;aAPzC,CASE,oCAAA,IAAC,gBAAA,QAAD,EACE,UACG;IAAE,QAAQ;IAAmB,MAAM;IAAe,MAAM;GAAU,EACjE,SAAS,YACN,sBAER,CAAA,GACA,SAAS,KACJ;KAbD,SAAS,EAaR,CACT;CACE,CAAA;AAET,CACF"}
1
+ {"version":3,"file":"ai-shortcuts.js","names":[],"sources":["../../src/renderer/ai-shortcuts.tsx"],"sourcesContent":["import { observer } from \"mobx-react\";\nimport { AiShortcutsStore } from \"../common/store/ai-shortcuts-store\";\nimport { AiIcon } from \"./ai-icon\";\n\nexport const AiStarterPrompts = observer(\n ({ onSelect, disabled = false }: { onSelect: (prompt: string) => void; disabled?: boolean }) => {\n const preferences = AiShortcutsStore.getInstanceOrCreate<AiShortcutsStore>();\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"flex-start\", gap: 10 }}>\n {preferences.shortcuts.map((shortcut) => (\n <button\n type=\"button\"\n className=\"Button\"\n style={{ display: \"inline-flex\", alignItems: \"center\", gap: 10, padding: \"6px 10px\", minHeight: 32 }}\n key={shortcut.id}\n disabled={disabled}\n title={`Insert /${shortcut.command}: ${shortcut.label}`}\n onClick={() => onSelect(shortcut.prompt)}\n >\n <AiIcon\n material={\n ({ health: \"favorite_border\", pods: \"view_module\", logs: \"subject\" } as Record<string, string>)[\n shortcut.command\n ] ?? \"chat_bubble_outline\"\n }\n />\n {shortcut.label}\n </button>\n ))}\n </div>\n );\n },\n);\n"],"mappings":";;;;;AAIA,IAAa,mBAAmB,oCAAA,UAC7B,EAAE,UAAU,WAAW,YAAwE;CAE9F,OACE,oCAAA,IAAC,OAAD;EAAK,OAAO;GAAE,SAAS;GAAQ,eAAe;GAAU,YAAY;GAAc,KAAK;EAAG;YAFxE,2BAAA,iBAAiB,oBAGhC,CAAA,CAAY,UAAU,KAAK,aAC1B,oCAAA,KAAC,UAAD;GACE,MAAK;GACL,WAAU;GACV,OAAO;IAAE,SAAS;IAAe,YAAY;IAAU,KAAK;IAAI,SAAS;IAAY,WAAW;GAAG;GAEzF;GACV,OAAO,WAAW,SAAS,QAAQ,IAAI,SAAS;GAChD,eAAe,SAAS,SAAS,MAAM;aAPzC,CASE,oCAAA,IAAC,gBAAA,QAAD,EACE,UACG;IAAE,QAAQ;IAAmB,MAAM;IAAe,MAAM;GAAU,EACjE,SAAS,YACN,sBAER,CAAA,GACA,SAAS,KACJ;KAbD,SAAS,EAaR,CACT;CACE,CAAA;AAET,CACF"}
@@ -13,6 +13,7 @@ const require_ai_icon = require("./ai-icon.js");
13
13
  const require_ai_copy_answer = require("./ai-copy-answer.js");
14
14
  const require_ai_extras$1 = require("./ai-extras.js");
15
15
  const require_ai_message = require("./ai-message.js");
16
+ const require_ai_provider_icon = require("./ai-provider-icon.js");
16
17
  const require_ai_resize = require("./ai-resize.js");
17
18
  const require_ai_shortcuts$1 = require("./ai-shortcuts.js");
18
19
  let electron = require("electron");
@@ -89,6 +90,7 @@ var styles = `
89
90
  @keyframes power-ai-enter { from { transform: translateX(100%); } to { transform: translateX(0); } }
90
91
  :root .power-ai-launcher { position: fixed; bottom: 16px; left: auto; right: 16px; z-index: 100; display: inline-flex; align-items: center; gap: 6px; min-width: 0; min-height: 0; height: 34px; border: 1px solid var(--primary, #5896ff); border-radius: 24px; padding: 0 13px; background: var(--contentColor, #242424); color: var(--textColorAccent); box-shadow: 0 3px 12px #0003; font: inherit; font-size: 12px; font-weight: 500; line-height: 1; cursor: pointer; transition: opacity 180ms ease, background-color 140ms ease, box-shadow 140ms ease, transform 180ms ease; }
91
92
  .power-ai-overview-actions { position: relative; z-index: 5; height: 0; min-height: 0; flex: 0 0 0; pointer-events: none; }
93
+ .power-ai-overview-actions + [data-testid="cluster-overview-page"] [data-monitoring-summaries] { --power-monitoring-summary-top: 56px; }
92
94
  .power-ai-overview-actions button { transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease; position: absolute; top: 12px; right: 12px; pointer-events: auto; box-shadow: 0 2px 8px #0002; display: inline-flex; flex: 0 0 auto; width: auto; align-items: center; gap: 6px; min-height: 32px; padding: 6px 12px; font: inherit; font-size: 12px; border: 1px solid var(--borderFaintColor); border-radius: 10px; color: var(--textColorPrimary); background: var(--contentColor); cursor: pointer; }
93
95
  .power-ai-overview-actions button:hover { border-color: var(--primary); color: var(--textColorAccent); background: var(--contentColor); }
94
96
  .power-ai-overview-actions button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
@@ -112,6 +114,10 @@ var styles = `
112
114
  :root .power-ai-panel button.power-ai-copy-answer { width: 24px; height: 24px; padding: 0; margin-left: auto; flex-shrink: 0; justify-content: center; border: 0; background: transparent; color: var(--textColorSecondary); }
113
115
  :root .power-ai-panel button.power-ai-copy-answer:not(:disabled):not(.disabled):hover { color: var(--primary); background: color-mix(in srgb, var(--primary) 10%, transparent); }
114
116
  :root .power-ai-panel button.power-ai-copy-answer:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
117
+ .power-ai-cancellation { display: flex; align-items: center; gap: 6px; }
118
+ :root .power-ai-panel button.power-ai-dismiss { width: 24px; height: 24px; padding: 0; justify-content: center; border: 0; border-radius: 6px; background: transparent; color: var(--textColorSecondary); }
119
+ :root .power-ai-panel button.power-ai-dismiss:hover { color: var(--primary); background: color-mix(in srgb, var(--primary) 10%, transparent); }
120
+ :root .power-ai-panel button.power-ai-dismiss:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
115
121
  .power-ai-panel button:disabled { opacity: 0.5; cursor: default; }
116
122
  .power-ai-panel textarea { display: block; width: 100%; box-sizing: border-box; min-width: 0; min-height: 64px; max-height: 180px; field-sizing: content; padding: 12px 16px 6px; border: 0; border-radius: 12px; background: transparent; color: var(--textColorAccent); font: inherit; font-size: var(--power-ai-font-size, 14px); line-height: 1.5; resize: none; }
117
123
  .power-ai-panel textarea::placeholder { color: var(--textColorSecondary); opacity: 1; }
@@ -503,6 +509,7 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
503
509
  children: [
504
510
  /* @__PURE__ */ require_jsx_runtime.jsxs("span", {
505
511
  className: "power-ai-brand",
512
+ title: "AI assistant",
506
513
  children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
507
514
  material: "auto_awesome",
508
515
  size: 18
@@ -516,6 +523,7 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
516
523
  type: "button",
517
524
  disabled: busy,
518
525
  "aria-label": "New chat",
526
+ title: "Clear this conversation and start a new chat",
519
527
  onClick: () => {
520
528
  preferences.setMessages(key, []);
521
529
  setStopped((keys) => keys.filter((entry) => entry !== key));
@@ -552,6 +560,7 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
552
560
  },
553
561
  className: "power-ai-close",
554
562
  "aria-label": "Close AI assistant",
563
+ title: "Close AI assistant",
555
564
  children: /* @__PURE__ */ require_jsx_runtime.jsx("span", {
556
565
  "aria-hidden": "true",
557
566
  children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
@@ -597,17 +606,22 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
597
606
  className: `power-ai-message power-ai-message-${message.role}`,
598
607
  children: [
599
608
  /* @__PURE__ */ require_jsx_runtime.jsxs("strong", { children: [
600
- /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
601
- material: message.role === "user" ? "person_outline" : "auto_awesome",
602
- size: 16
603
- }),
609
+ message.role === "user" ? /* @__PURE__ */ require_jsx_runtime.jsx("span", {
610
+ title: "You",
611
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
612
+ material: "person_outline",
613
+ size: 16
614
+ })
615
+ }) : /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_provider_icon.AiProviderIcon, { provider: message.provider === "claude" ? "claude" : "codex" }),
604
616
  message.role === "user" ? "You" : message.provider === "claude" ? "Claude" : "Codex",
605
617
  message.role === "assistant" && /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_copy_answer.AiCopyAnswer, { text: message.text })
606
618
  ] }),
607
619
  /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_message.AiMessageContent, { text: message.text }),
608
620
  message.role === "assistant" && context.clusterId && /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_extras$1.AiResourceReferences, {
609
621
  clusterId: context.clusterId,
610
- references: message.references
622
+ references: message.references,
623
+ text: message.text,
624
+ onOpened: () => setOpen(false)
611
625
  })
612
626
  ]
613
627
  }, index)),
@@ -619,6 +633,7 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
619
633
  margin: "8px 16px"
620
634
  },
621
635
  children: readActivity.map((read, index) => /* @__PURE__ */ require_jsx_runtime.jsxs("span", {
636
+ title: `${read.method} · ${read.status === "reading" ? "reading" : read.status === "ok" ? "read" : "unavailable"}`,
622
637
  "data-method": read.method,
623
638
  "data-status": read.status,
624
639
  style: { marginRight: 10 },
@@ -649,6 +664,7 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
649
664
  })]
650
665
  }),
651
666
  !busy && stopped.includes(key) && /* @__PURE__ */ require_jsx_runtime.jsxs("p", {
667
+ className: "power-ai-cancellation",
652
668
  role: "status",
653
669
  children: [
654
670
  "Response stopped",
@@ -656,8 +672,13 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
656
672
  /* @__PURE__ */ require_jsx_runtime.jsx("button", {
657
673
  type: "button",
658
674
  "aria-label": "Dismiss cancellation",
675
+ className: "power-ai-dismiss",
676
+ title: "Dismiss",
659
677
  onClick: () => setStopped((keys) => keys.filter((entry) => entry !== key)),
660
- children: "Dismiss"
678
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
679
+ material: "close",
680
+ size: 14
681
+ })
661
682
  })
662
683
  ]
663
684
  }),
@@ -683,6 +704,7 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
683
704
  role: "option",
684
705
  id: `power-ai-command-${index}`,
685
706
  "aria-selected": index === suggestionIndex % suggestions.length,
707
+ title: `Insert /${shortcut.command}: ${shortcut.label}`,
686
708
  onMouseDown: (event) => event.preventDefault(),
687
709
  onClick: () => selectSuggestion(index),
688
710
  children: [/* @__PURE__ */ require_jsx_runtime.jsxs("span", { children: ["/", shortcut.command] }), /* @__PURE__ */ require_jsx_runtime.jsx("span", { children: shortcut.label })]
@@ -729,7 +751,6 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
729
751
  children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "Enter to send · Shift+Enter for newline" }), busy ? /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
730
752
  type: "button",
731
753
  "aria-label": "Stop response",
732
- title: "Stop response",
733
754
  disabled: stopped.includes(key),
734
755
  onClick: stopResponse,
735
756
  children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
@@ -740,7 +761,6 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
740
761
  type: "submit",
741
762
  disabled: !draft.trim(),
742
763
  "aria-label": "Send",
743
- title: "Send",
744
764
  children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
745
765
  material: "send",
746
766
  size: 16
@@ -760,9 +780,21 @@ function startAiChat(isClusterDocument = isAiClusterDocument) {
760
780
  let mounted = false;
761
781
  let overviewActions;
762
782
  let overviewIcon;
783
+ let overviewLabel;
784
+ let overviewResize;
785
+ const updateOverviewLabel = () => {
786
+ const overview = overviewActions?.nextElementSibling;
787
+ const compact = Boolean(overview?.querySelector("[data-freelens-power-monitoring]") && overview.clientWidth > 960);
788
+ const label = compact ? "Ask AI" : "Ask about cluster";
789
+ if (overviewLabel && overviewLabel.textContent !== label) overviewLabel.textContent = label;
790
+ overviewActions?.querySelector("button")?.setAttribute("aria-label", compact ? "Ask AI about cluster" : label);
791
+ };
763
792
  const removeOverview = () => {
793
+ overviewResize?.disconnect();
794
+ overviewResize = void 0;
764
795
  if (overviewIcon) require__global_external_react_dom.default.unmountComponentAtNode(overviewIcon);
765
796
  overviewIcon = void 0;
797
+ overviewLabel = void 0;
766
798
  overviewActions?.remove();
767
799
  overviewActions = void 0;
768
800
  };
@@ -773,18 +805,29 @@ function startAiChat(isClusterDocument = isAiClusterDocument) {
773
805
  removeOverview();
774
806
  return;
775
807
  }
776
- if (overviewActions?.isConnected && overviewActions.nextElementSibling === overview) return;
808
+ if (overviewActions?.isConnected && overviewActions.nextElementSibling === overview) {
809
+ updateOverviewLabel();
810
+ return;
811
+ }
777
812
  removeOverview();
778
813
  overviewActions = document.createElement("div");
779
814
  overviewActions.className = "power-ai-overview-actions";
780
815
  const overviewButton = document.createElement("button");
781
816
  overviewButton.type = "button";
817
+ overviewButton.setAttribute("aria-label", "Ask about cluster");
818
+ overviewButton.title = "Open the AI assistant to ask about this cluster";
782
819
  overviewIcon = document.createElement("span");
783
820
  overviewIcon.setAttribute("aria-hidden", "true");
784
- overviewButton.append(overviewIcon, document.createTextNode("Ask about cluster"));
821
+ overviewLabel = document.createElement("span");
822
+ overviewButton.append(overviewIcon, overviewLabel);
785
823
  overviewButton.onclick = () => window.dispatchEvent(new Event("power-ai-open"));
786
824
  overviewActions.append(overviewButton);
787
825
  overview.before(overviewActions);
826
+ updateOverviewLabel();
827
+ if (typeof ResizeObserver !== "undefined") {
828
+ overviewResize = new ResizeObserver(updateOverviewLabel);
829
+ overviewResize.observe(overview);
830
+ }
788
831
  require__global_external_react_dom.default.render(/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
789
832
  material: "auto_awesome",
790
833
  size: 16