@64mb/freelens-power 2.2.1 → 2.4.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.
Files changed (36) hide show
  1. package/README.md +2 -2
  2. package/THIRD_PARTY_LICENSES +26 -0
  3. package/out/main/ai-models.js +2 -1
  4. package/out/main/ai-models.js.map +1 -1
  5. package/out/main/ai.js +13 -5
  6. package/out/main/ai.js.map +1 -1
  7. package/out/main/power-logs-query.js +4 -3
  8. package/out/main/power-logs-query.js.map +1 -1
  9. package/out/renderer/ai-extras.js +21 -16
  10. package/out/renderer/ai-extras.js.map +1 -1
  11. package/out/renderer/ai-icon.js +23 -0
  12. package/out/renderer/ai-icon.js.map +1 -1
  13. package/out/renderer/ai-logs-sql.js +1 -0
  14. package/out/renderer/ai-logs-sql.js.map +1 -1
  15. package/out/renderer/ai-message.js +8 -2
  16. package/out/renderer/ai-message.js.map +1 -1
  17. package/out/renderer/ai.js +91 -13
  18. package/out/renderer/ai.js.map +1 -1
  19. package/out/renderer/appearance.js +20 -2
  20. package/out/renderer/appearance.js.map +1 -1
  21. package/out/renderer/details/resource-health-details.js +2 -2
  22. package/out/renderer/details/resource-health-details.js.map +1 -1
  23. package/out/renderer/host-layout.js +15 -0
  24. package/out/renderer/host-layout.js.map +1 -0
  25. package/out/renderer/index.js +2 -0
  26. package/out/renderer/index.js.map +1 -1
  27. package/out/renderer/power-log-json-cell.js +2 -2
  28. package/out/renderer/power-logs.css.js +11 -2
  29. package/out/renderer/power-logs.css.js.map +1 -1
  30. package/out/renderer/power-logs.js +76 -33
  31. package/out/renderer/power-logs.js.map +1 -1
  32. package/out/renderer/preferences/ai-preferences.js +6 -3
  33. package/out/renderer/preferences/ai-preferences.js.map +1 -1
  34. package/out/renderer/preferences/monitoring-preferences.js +14 -6
  35. package/out/renderer/preferences/monitoring-preferences.js.map +1 -1
  36. package/package.json +1 -1
@@ -6,6 +6,7 @@ const require_ai_icon = require("./ai-icon.js");
6
6
  const require_native_tooltip = require("./native-tooltip.js");
7
7
  const require_ai_logs_sql = require("./ai-logs-sql.js");
8
8
  const require_markdown_it = require("./node_modules/.pnpm/markdown-it@15.0.2/node_modules/markdown-it/dist/markdown-it.js");
9
+ const require_power_log_sql_editor = require("./power-log-sql-editor.js");
9
10
  let electron = require("electron");
10
11
  //#region src/renderer/ai-message.tsx
11
12
  var markdown = new require_markdown_it.default({
@@ -192,7 +193,9 @@ function CodeBlock({ text, language }) {
192
193
  ].includes(language.toLowerCase());
193
194
  const command = terminalInsertText(text);
194
195
  const logsWorkspace = require_ai_logs_sql.usePowerLogsWorkspace();
195
- const sqlDraft = ["sql", "sqlite"].includes(language.toLowerCase()) && logsWorkspace;
196
+ const sql = ["sql", "sqlite"].includes(language.toLowerCase());
197
+ const sqlTokens = require__global_external_react.useMemo(() => sql ? require_power_log_sql_editor.tokenizeSql(text) : void 0, [sql, text]);
198
+ const sqlDraft = sql && logsWorkspace;
196
199
  const insert = async () => {
197
200
  const activeTerminal = visibleTerminalId();
198
201
  if (!shell || !command || !activeTerminal || inserting) return;
@@ -266,7 +269,10 @@ function CodeBlock({ text, language }) {
266
269
  })
267
270
  ] })]
268
271
  }),
269
- /* @__PURE__ */ require_jsx_runtime.jsx("pre", { children: /* @__PURE__ */ require_jsx_runtime.jsx("code", { children: text }) }),
272
+ /* @__PURE__ */ require_jsx_runtime.jsx("pre", { children: /* @__PURE__ */ require_jsx_runtime.jsx("code", { children: sqlTokens ? sqlTokens.map((token, index) => /* @__PURE__ */ require_jsx_runtime.jsx("span", {
273
+ "data-sql-token": token.kind,
274
+ children: token.text
275
+ }, index)) : text }) }),
270
276
  shell && !command && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
271
277
  className: "power-ai-code-hint",
272
278
  children: "Terminal insertion requires a single line without control characters."
@@ -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\";\nimport { isPowerLogsWorkspace, usePowerLogsWorkspace } from \"./ai-logs-sql\";\nimport { NativeTooltip } from \"./native-tooltip\";\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.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 const element = createElement(tag, props, ...container.children);\n const title = tag === \"a\" ? opening.attrGet(\"title\") : undefined;\n append(\n title ? (\n <NativeTooltip key={container.key} title={title}>\n {element}\n </NativeTooltip>\n ) : (\n element\n ),\n );\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 logsWorkspace = usePowerLogsWorkspace();\n const sqlDraft = [\"sql\", \"sqlite\"].includes(language.toLowerCase()) && logsWorkspace;\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 <NativeTooltip title=\"Copy code to clipboard\">\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 </NativeTooltip>\n {sqlDraft && (\n <NativeTooltip title=\"Insert into the SQL editor without running\">\n <button\n type=\"button\"\n aria-label=\"Insert into SQL\"\n disabled={!text.trim() || text.length > 65536}\n onClick={() => {\n if (!isPowerLogsWorkspace() || !text.trim() || text.length > 65536) return;\n window.dispatchEvent(new CustomEvent(\"power-logs-sql-draft\", { detail: { sql: text } }));\n window.dispatchEvent(new Event(\"power-ai-close\"));\n }}\n >\n <AiIcon material=\"post_add\" size={14} /> Insert into SQL\n </button>\n </NativeTooltip>\n )}\n {shell && terminalId && (\n <NativeTooltip\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 >\n <button\n type=\"button\"\n aria-label=\"Insert into terminal\"\n disabled={!command || !terminalId || inserting}\n onClick={() => void insert()}\n >\n <AiIcon material=\"terminal\" size={14} /> Insert\n </button>\n </NativeTooltip>\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\";\nimport { isPowerLogsWorkspace, usePowerLogsWorkspace } from \"./ai-logs-sql\";\nimport { NativeTooltip } from \"./native-tooltip\";\nimport { tokenizeSql } from \"./power-log-sql-editor\";\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.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 const element = createElement(tag, props, ...container.children);\n const title = tag === \"a\" ? opening.attrGet(\"title\") : undefined;\n append(\n title ? (\n <NativeTooltip key={container.key} title={title}>\n {element}\n </NativeTooltip>\n ) : (\n element\n ),\n );\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 logsWorkspace = usePowerLogsWorkspace();\n const sql = [\"sql\", \"sqlite\"].includes(language.toLowerCase());\n const sqlTokens = useMemo(() => (sql ? tokenizeSql(text) : undefined), [sql, text]);\n const sqlDraft = sql && logsWorkspace;\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 <NativeTooltip title=\"Copy code to clipboard\">\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 </NativeTooltip>\n {sqlDraft && (\n <NativeTooltip title=\"Insert into the SQL editor without running\">\n <button\n type=\"button\"\n aria-label=\"Insert into SQL\"\n disabled={!text.trim() || text.length > 65536}\n onClick={() => {\n if (!isPowerLogsWorkspace() || !text.trim() || text.length > 65536) return;\n window.dispatchEvent(new CustomEvent(\"power-logs-sql-draft\", { detail: { sql: text } }));\n window.dispatchEvent(new Event(\"power-ai-close\"));\n }}\n >\n <AiIcon material=\"post_add\" size={14} /> Insert into SQL\n </button>\n </NativeTooltip>\n )}\n {shell && terminalId && (\n <NativeTooltip\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 >\n <button\n type=\"button\"\n aria-label=\"Insert into terminal\"\n disabled={!command || !terminalId || inserting}\n onClick={() => void insert()}\n >\n <AiIcon material=\"terminal\" size={14} /> Insert\n </button>\n </NativeTooltip>\n )}\n </div>\n </div>\n <pre>\n <code>\n {sqlTokens\n ? sqlTokens.map((token, index) => (\n <span key={index} data-sql-token={token.kind}>\n {token.text}\n </span>\n ))\n : text}\n </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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"}
@@ -137,6 +137,7 @@ body:has(.AddRemoveButtons .remove-button:not([data-freelens-power-read-only]))
137
137
  :root .power-ai-panel form button[type="submit"]:disabled { background-color: var(--contentColor); border-color: var(--borderFaintColor); color: var(--textColorSecondary); }
138
138
  .power-ai-message-user { border-color: color-mix(in srgb, var(--primary) 28%, var(--borderFaintColor)); background: color-mix(in srgb, var(--primary) 7%, var(--mainBackground)); }
139
139
  .power-ai-context { padding: 10px 20px; line-height: 1.5; border-bottom: 1px solid var(--borderFaintColor); font-size: 12px; color: var(--textColorSecondary); overflow-wrap: anywhere; }
140
+ .power-ai-attached-resource { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
140
141
  .power-ai-title, .power-ai-panel button { display: inline-flex; align-items: center; gap: 6px; }
141
142
  .power-ai-title { margin-right: auto; white-space: nowrap; color: var(--textColorAccent); }
142
143
  .power-ai-brand { display: inline-flex; align-items: center; gap: 6px; color: var(--primary); }
@@ -145,6 +146,15 @@ body:has(.AddRemoveButtons .remove-button:not([data-freelens-power-read-only]))
145
146
  .power-ai-panel header > button { flex-shrink: 0; }
146
147
  .power-ai-panel form { position: relative; flex-shrink: 0; margin: 0 16px 16px; border: 1px solid var(--borderFaintColor); border-radius: 12px; background: var(--mainBackground); transition: border-color 160ms ease; }
147
148
  .power-ai-panel form > .power-ai-context-preview { padding: 8px 48px 4px 16px !important; min-height: 34px; margin: 0; box-sizing: border-box; }
149
+ .power-ai-context-preview pre { white-space: pre-wrap; overflow-wrap: anywhere; overflow: auto; }
150
+ .power-ai-finding-content pre { max-height: 140px; }
151
+ .power-ai-context-content pre { max-height: 160px; }
152
+ .power-ai-context-preview .power-log-json { --json-key:#9cdcfe; --json-string:#ce9178; --json-number:#b5cea8; --json-literal:#c586c0; }
153
+ .theme-light .power-ai-context-preview .power-log-json { --json-key:#005a9e; --json-string:#9e2b1a; --json-number:#17642d; --json-literal:#7a288c; }
154
+ .power-ai-context-preview .power-log-json [data-token=key] { color:var(--json-key); }
155
+ .power-ai-context-preview .power-log-json [data-token=string] { color:var(--json-string); }
156
+ .power-ai-context-preview .power-log-json [data-token=number] { color:var(--json-number); }
157
+ .power-ai-context-preview .power-log-json [data-token=literal] { color:var(--json-literal); }
148
158
  .power-ai-context-finding { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 4px; min-width: 0; }
149
159
  .power-ai-context-finding > .Icon { flex-shrink: 0; margin-top: 7px; }
150
160
  .power-ai-context-finding > details { flex: 1; min-width: 0; }
@@ -178,12 +188,19 @@ body:has(.AddRemoveButtons .remove-button:not([data-freelens-power-read-only]))
178
188
  .power-ai-markdown a { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
179
189
  .power-ai-markdown hr { margin: 20px 0; border: 0; border-top: 1px solid var(--borderFaintColor); }
180
190
  .power-ai-markdown code { font-family: var(--font-monospace, monospace); font-size: max(10px, calc(var(--power-ai-font-size, 14px) - 1px)); background: color-mix(in srgb, var(--textColorAccent) 8%, transparent); padding: 1px 4px; border-radius: 4px; }
181
- .power-ai-code { margin: 12px 0; border: 1px solid var(--borderFaintColor); border-radius: 8px; overflow: hidden; background: var(--mainBackground); }
191
+ .power-ai-code { --sql-string:#e6a28c; --sql-number:#98c78e; --sql-function:#81b9e6; --sql-identifier:var(--textColorPrimary); margin: 12px 0; border: 1px solid var(--borderFaintColor); border-radius: 8px; overflow: hidden; background: var(--mainBackground); }
182
192
  .power-ai-code-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 8px; border-bottom: 1px solid var(--borderFaintColor); color: var(--textColorSecondary); font-size: 11px; }
183
193
  .power-ai-code-toolbar > div { display: flex; gap: 4px; }
184
194
  .power-ai-code-toolbar button { padding: 4px 6px; font-size: 11px; }
185
- .power-ai-code pre { margin: 0; padding: 12px; overflow-x: auto; white-space: pre; }
186
- .power-ai-code pre code { padding: 0; background: transparent; }
195
+ .power-ai-code pre { color:var(--textColorPrimary); margin: 0; padding: 12px; overflow-x: auto; white-space: pre; }
196
+ .power-ai-code pre code { padding: 0; background: transparent; color:inherit; }
197
+ .theme-light .power-ai-code { --sql-string:#9e2b1a; --sql-number:#17642d; --sql-function:#005a9e; --sql-identifier:#684c9b; }
198
+ .power-ai-code [data-sql-token=keyword] { color:var(--primary); }
199
+ .power-ai-code [data-sql-token=function] { color:var(--sql-function); }
200
+ .power-ai-code [data-sql-token=string] { color:var(--sql-string); }
201
+ .power-ai-code [data-sql-token=identifier] { color:var(--sql-identifier); }
202
+ .power-ai-code [data-sql-token=number] { color:var(--sql-number); }
203
+ .power-ai-code [data-sql-token=comment] { color:var(--textColorSecondary); }
187
204
  .power-ai-code-hint, .power-ai-code-feedback:not(:empty) { padding: 0 12px 10px; color: var(--textColorSecondary); font-size: 11px; }
188
205
  .power-ai-autocomplete { position: absolute; bottom: calc(100% + 8px); left: 0; right: 0; max-height: 240px; overflow-y: auto; padding: 6px; border: 1px solid var(--borderColor); border-radius: 10px; background: var(--contentColor); box-shadow: 0 4px 16px #0003; }
189
206
  .power-ai-autocomplete button { display: flex; width: 100%; justify-content: space-between; gap: 12px; border: 0; text-align: left; }
@@ -202,6 +219,28 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
202
219
  const [launcherHidden, setLauncherHidden] = require__global_external_react.useState(false);
203
220
  const [draft, setDraft] = require__global_external_react.useState("");
204
221
  const [finding, setFinding] = require__global_external_react.useState();
222
+ const [resourceAttachment, setResourceAttachment] = require__global_external_react.useState();
223
+ const dismissedResource = require__global_external_react.useRef();
224
+ const panelOpen = require__global_external_react.useRef(open);
225
+ panelOpen.current = open;
226
+ const captureScreenResource = (current = readAiContext()) => {
227
+ if (current.clusterId && current.resource && dismissedResource.current !== JSON.stringify({
228
+ clusterId: current.clusterId,
229
+ resource: current.resource
230
+ })) setResourceAttachment(current);
231
+ };
232
+ const attachScreenResource = () => {
233
+ dismissedResource.current = void 0;
234
+ captureScreenResource();
235
+ };
236
+ const removeResource = () => {
237
+ const current = resourceAttachment ?? readAiContext();
238
+ dismissedResource.current = JSON.stringify({
239
+ clusterId: current.clusterId,
240
+ resource: current.resource
241
+ });
242
+ setResourceAttachment(void 0);
243
+ };
205
244
  const [terminalAttachment, setTerminalAttachment] = require__global_external_react.useState();
206
245
  const [suggestionIndex, setSuggestionIndex] = require__global_external_react.useState(0);
207
246
  const [dismissedDraft, setDismissedDraft] = require__global_external_react.useState();
@@ -257,6 +296,8 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
257
296
  updateDock();
258
297
  const nextContext = readAiContext();
259
298
  setContext(nextContext);
299
+ if (panelOpen.current) captureScreenResource(nextContext);
300
+ setResourceAttachment((current) => current?.clusterId === nextContext.clusterId ? current : void 0);
260
301
  setFinding((current) => current && current.clusterId !== nextContext.clusterId ? void 0 : current);
261
302
  setResourceDetailsOpen(hasAiResourceDetails());
262
303
  setCoveredByOverlay(hasAiOverlay());
@@ -318,6 +359,7 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
318
359
  const closePanel = () => setOpen(false);
319
360
  window.addEventListener("power-ai-close", closePanel);
320
361
  function openOnly() {
362
+ attachScreenResource();
321
363
  update();
322
364
  setOpen(true);
323
365
  }
@@ -376,6 +418,12 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
376
418
  }, [open, pinned]);
377
419
  const availablePanelWidth = Math.max(1, viewportWidth - Math.max(0, launcherLeft - 16));
378
420
  const panelWidth = require_ai_resize.clampAiPanelWidth(preferences.chatPanelWidth ?? 560, availablePanelWidth);
421
+ const attachedResource = resourceAttachment && resourceAttachment.clusterId === context.clusterId ? resourceAttachment.resource : void 0;
422
+ const previewContext = require_ai_extras.attachedAiContext({
423
+ clusterId: context.clusterId,
424
+ screen: context.screen,
425
+ ...finding && finding.clusterId === context.clusterId && finding.resource ? { resource: finding.resource } : attachedResource ? { resource: attachedResource } : {}
426
+ });
379
427
  const key = context.clusterId ?? "global";
380
428
  const messages = preferences.conversations[key] ?? [];
381
429
  const busy = pending.includes(key);
@@ -433,9 +481,12 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
433
481
  setError("Message and attached finding are too long. Shorten the message or remove the finding.");
434
482
  return;
435
483
  }
484
+ const currentContext = readAiContext();
485
+ const requestResource = activeFinding?.resource ?? (resourceAttachment && resourceAttachment.clusterId === currentContext.clusterId ? resourceAttachment.resource : void 0);
436
486
  const requestContext = require_ai_extras.attachedAiContext({
437
- ...readAiContext(),
438
- ...activeFinding?.resource ? { resource: activeFinding.resource } : {}
487
+ clusterId: currentContext.clusterId,
488
+ screen: currentContext.screen,
489
+ ...requestResource ? { resource: requestResource } : {}
439
490
  });
440
491
  if ((requestContext.clusterId ?? "global") !== key) {
441
492
  setContext(requestContext);
@@ -555,7 +606,12 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
555
606
  left: resourceDetailsOpen ? launcherLeft : "auto",
556
607
  right: resourceDetailsOpen ? "auto" : 16
557
608
  },
558
- onClick: () => setOpen(true),
609
+ onPointerDown: attachScreenResource,
610
+ onMouseDown: attachScreenResource,
611
+ onClick: () => {
612
+ attachScreenResource();
613
+ setOpen(true);
614
+ },
559
615
  "aria-label": "Open AI assistant",
560
616
  children: [
561
617
  /* @__PURE__ */ require_jsx_runtime.jsx("span", {
@@ -573,6 +629,9 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
573
629
  open && /* @__PURE__ */ require_jsx_runtime.jsxs("aside", {
574
630
  className: "power-ai-panel",
575
631
  "aria-label": "AI assistant",
632
+ onPointerDownCapture: () => captureScreenResource(),
633
+ onMouseDownCapture: () => captureScreenResource(),
634
+ onFocusCapture: () => captureScreenResource(),
576
635
  style: {
577
636
  "--power-ai-font-size": `${preferences.chatFontSize ?? 14}px`,
578
637
  width: panelWidth
@@ -610,6 +669,7 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
610
669
  onClick: () => {
611
670
  setTerminalAttachment(void 0);
612
671
  setFinding(void 0);
672
+ removeResource();
613
673
  preferences.setMessages(key, []);
614
674
  setStopped((keys) => keys.filter((entry) => entry !== key));
615
675
  setActivity({
@@ -632,8 +692,8 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
632
692
  "aria-label": "Pin AI assistant",
633
693
  "aria-pressed": pinned,
634
694
  onClick: () => setPinned((value) => !value),
635
- children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
636
- material: "push_pin",
695
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiPinIcon, {
696
+ pinned,
637
697
  size: 18
638
698
  })
639
699
  })
@@ -666,7 +726,28 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
666
726
  context.clusterId ? "Cluster conversation" : "General conversation",
667
727
  " · ",
668
728
  context.screen,
669
- context.resource && ` · ${context.resource.kind}/${context.resource.name}`,
729
+ attachedResource && !(finding?.clusterId === context.clusterId && finding?.resource) && /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
730
+ className: "power-ai-attached-resource",
731
+ "aria-label": "Attached resource",
732
+ children: [/* @__PURE__ */ require_jsx_runtime.jsxs("span", { children: [
733
+ attachedResource.kind,
734
+ "/",
735
+ attachedResource.name,
736
+ attachedResource.namespace ? ` · ${attachedResource.namespace}` : ""
737
+ ] }), /* @__PURE__ */ require_jsx_runtime.jsx(require_native_tooltip.NativeTooltip, {
738
+ title: "Remove resource from attached context",
739
+ children: /* @__PURE__ */ require_jsx_runtime.jsx("button", {
740
+ type: "button",
741
+ className: "power-ai-remove-context",
742
+ "aria-label": "Remove attached resource",
743
+ onClick: removeResource,
744
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
745
+ material: "close",
746
+ size: 14
747
+ })
748
+ })
749
+ })]
750
+ }),
670
751
  /* @__PURE__ */ require_jsx_runtime.jsx("br", {}),
671
752
  "Only screen and resource metadata are attached automatically."
672
753
  ]
@@ -809,10 +890,7 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
809
890
  /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_extras$1.AiContextPreview, {
810
891
  finding: finding?.clusterId === context.clusterId ? finding : void 0,
811
892
  onRemoveFinding: () => setFinding(void 0),
812
- context: require_ai_extras.attachedAiContext({
813
- ...context,
814
- ...finding && finding.clusterId === context.clusterId && finding.resource ? { resource: finding.resource } : {}
815
- })
893
+ context: previewContext
816
894
  }),
817
895
  /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_terminal_context.AiTerminalContext, {
818
896
  clusterId: context.clusterId,