@64mb/freelens-power 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/out/main/ai-ipc.js +34 -3
- package/out/main/ai-ipc.js.map +1 -1
- package/out/main/common/power-logs-limits.js +227 -0
- package/out/main/common/power-logs-limits.js.map +1 -0
- package/out/main/common/power-logs.js +32 -0
- package/out/main/common/power-logs.js.map +1 -0
- package/out/main/common/resource-search.js +61 -0
- package/out/main/common/resource-search.js.map +1 -0
- package/out/main/common/store/logs-preferences-store.js +45 -0
- package/out/main/common/store/logs-preferences-store.js.map +1 -0
- package/out/main/common/store/monitoring-preferences-store.js +5 -0
- package/out/main/common/store/monitoring-preferences-store.js.map +1 -1
- package/out/main/dashboard-icon-cache.js +1 -1
- package/out/main/index.js +6 -0
- package/out/main/index.js.map +1 -1
- package/out/main/power-logs-budget.js +126 -0
- package/out/main/power-logs-budget.js.map +1 -0
- package/out/main/power-logs-child.js +86 -0
- package/out/main/power-logs-child.js.map +1 -0
- package/out/main/power-logs-database.js +179 -0
- package/out/main/power-logs-database.js.map +1 -0
- package/out/main/power-logs-ipc.js +275 -0
- package/out/main/power-logs-ipc.js.map +1 -0
- package/out/main/power-logs-query-child.js +23 -0
- package/out/main/power-logs-query-child.js.map +1 -0
- package/out/main/power-logs-query.js +127 -0
- package/out/main/power-logs-query.js.map +1 -0
- package/out/main/power-logs-worker.js +215 -0
- package/out/main/power-logs-worker.js.map +1 -0
- package/out/main/resource-search-database.js +209 -0
- package/out/main/resource-search-database.js.map +1 -0
- package/out/main/resource-search-worker.js +56 -0
- package/out/main/resource-search-worker.js.map +1 -0
- package/out/main/resource-search.js +155 -0
- package/out/main/resource-search.js.map +1 -0
- package/out/renderer/ai-logs-sql.js +27 -0
- package/out/renderer/ai-logs-sql.js.map +1 -0
- package/out/renderer/ai-message.js +48 -27
- package/out/renderer/ai-message.js.map +1 -1
- package/out/renderer/ai-terminal-context.js +50 -40
- package/out/renderer/ai-terminal-context.js.map +1 -1
- package/out/renderer/ai.js +15 -2
- package/out/renderer/ai.js.map +1 -1
- package/out/renderer/appearance.js +2 -1
- package/out/renderer/appearance.js.map +1 -1
- package/out/renderer/gateway-relationships-client.js +107 -0
- package/out/renderer/gateway-relationships-client.js.map +1 -0
- package/out/renderer/index.js +67 -29
- package/out/renderer/index.js.map +1 -1
- package/out/renderer/power-log-json.js +29 -0
- package/out/renderer/power-log-json.js.map +1 -0
- package/out/renderer/power-logs-client.js +35 -0
- package/out/renderer/power-logs-client.js.map +1 -0
- package/out/renderer/power-logs-collector.js +316 -0
- package/out/renderer/power-logs-collector.js.map +1 -0
- package/out/renderer/power-logs-entrypoints.js +201 -0
- package/out/renderer/power-logs-entrypoints.js.map +1 -0
- package/out/renderer/power-logs-sql-row.js +52 -0
- package/out/renderer/power-logs-sql-row.js.map +1 -0
- package/out/renderer/power-logs-transport.js +122 -0
- package/out/renderer/power-logs-transport.js.map +1 -0
- package/out/renderer/power-logs.css.js +116 -0
- package/out/renderer/power-logs.css.js.map +1 -0
- package/out/renderer/power-logs.js +1352 -0
- package/out/renderer/power-logs.js.map +1 -0
- package/out/renderer/preferences/logs-preferences.js +93 -0
- package/out/renderer/preferences/logs-preferences.js.map +1 -0
- package/out/renderer/preferences/monitoring-preferences.js +10 -0
- package/out/renderer/preferences/monitoring-preferences.js.map +1 -1
- package/out/renderer/preferences/preferences.js +24 -10
- package/out/renderer/preferences/preferences.js.map +1 -1
- package/out/renderer/resource-relationships.css.js +1 -1
- package/out/renderer/resource-relationships.css.js.map +1 -1
- package/out/renderer/resource-relationships.js +46 -8
- package/out/renderer/resource-relationships.js.map +1 -1
- package/out/renderer/resource-search-client.js +116 -0
- package/out/renderer/resource-search-client.js.map +1 -0
- package/out/renderer/resource-search-discovery.js +79 -0
- package/out/renderer/resource-search-discovery.js.map +1 -0
- package/out/renderer/resource-search-model.js +2 -1
- package/out/renderer/resource-search-model.js.map +1 -1
- package/out/renderer/resource-search-routing.js +78 -0
- package/out/renderer/resource-search-routing.js.map +1 -0
- package/out/renderer/resource-search-sync.js +199 -0
- package/out/renderer/resource-search-sync.js.map +1 -0
- package/out/renderer/resource-search.js +279 -54
- package/out/renderer/resource-search.js.map +1 -1
- package/out/renderer/src/common/gateway-relationships.js +122 -0
- package/out/renderer/src/common/gateway-relationships.js.map +1 -0
- package/out/renderer/src/common/power-log-parser.js +114 -0
- package/out/renderer/src/common/power-log-parser.js.map +1 -0
- package/out/renderer/src/common/power-logs-ai.js +56 -0
- package/out/renderer/src/common/power-logs-ai.js.map +1 -0
- package/out/renderer/src/common/power-logs-limits.js +227 -0
- package/out/renderer/src/common/power-logs-limits.js.map +1 -0
- package/out/renderer/src/common/power-logs.js +32 -0
- package/out/renderer/src/common/power-logs.js.map +1 -0
- package/out/renderer/src/common/resource-relationships.js +8 -0
- package/out/renderer/src/common/resource-relationships.js.map +1 -1
- package/out/renderer/src/common/resource-search.js +6 -0
- package/out/renderer/src/common/resource-search.js.map +1 -0
- package/out/renderer/src/common/store/logs-preferences-store.js +45 -0
- package/out/renderer/src/common/store/logs-preferences-store.js.map +1 -0
- package/out/renderer/src/common/store/monitoring-preferences-store.js +5 -0
- package/out/renderer/src/common/store/monitoring-preferences-store.js.map +1 -1
- package/package.json +8 -2
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const require_extensions = require("./_virtual/_global-external_@freelensapp/extensions.js");
|
|
2
|
+
const require__global_external_mobx = require("./_virtual/_global-external_mobx.js");
|
|
3
|
+
const require_monitoring_preferences_store = require("./common/store/monitoring-preferences-store.js");
|
|
4
|
+
const require_resource_search = require("./common/resource-search.js");
|
|
5
|
+
const require_resource_search_worker = require("./resource-search-worker.js");
|
|
6
|
+
let node_crypto = require("node:crypto");
|
|
7
|
+
let electron = require("electron");
|
|
8
|
+
let node_path = require("node:path");
|
|
9
|
+
let node_fs = require("node:fs");
|
|
10
|
+
//#region src/main/resource-search.ts
|
|
11
|
+
function trustedSearchSender(event, clusterId) {
|
|
12
|
+
const window = electron.BrowserWindow.fromWebContents(event.sender);
|
|
13
|
+
const frame = event.senderFrame;
|
|
14
|
+
if (!window || window.isDestroyed() || !frame) return false;
|
|
15
|
+
try {
|
|
16
|
+
const url = new URL(frame.url);
|
|
17
|
+
const root = new URL(event.sender.getURL());
|
|
18
|
+
if (url.protocol !== "https:" || root.protocol !== "https:" || url.username || url.password || root.username || root.password) return false;
|
|
19
|
+
if (url.hostname !== `${clusterId}.renderer.freelens.app` || root.port !== url.port) return false;
|
|
20
|
+
if (frame === event.sender.mainFrame) return root.origin === url.origin;
|
|
21
|
+
return root.hostname === "renderer.freelens.app" && frame.parent === event.sender.mainFrame && frame.top === event.sender.mainFrame;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function validateSearchRequest(input) {
|
|
27
|
+
if (!require_resource_search.validSearchPartition(input)) throw new Error("Invalid search partition");
|
|
28
|
+
const request = input;
|
|
29
|
+
const partition = {
|
|
30
|
+
clusterId: request.clusterId,
|
|
31
|
+
contextId: request.contextId
|
|
32
|
+
};
|
|
33
|
+
if (request.operation === "reconcile") {
|
|
34
|
+
if (!Array.isArray(request.resources) || request.resources.length > 1e4 || request.resources.some((resource) => typeof resource !== "string" || !/^[a-zA-Z0-9_.-]{0,253}\/[a-zA-Z0-9_.-]{1,253}$/.test(resource))) throw new Error("Invalid search resource catalog");
|
|
35
|
+
return {
|
|
36
|
+
...partition,
|
|
37
|
+
operation: "reconcile",
|
|
38
|
+
resources: [...new Set(request.resources)]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if ([
|
|
42
|
+
"begin",
|
|
43
|
+
"append",
|
|
44
|
+
"complete",
|
|
45
|
+
"invalidate"
|
|
46
|
+
].includes(request.operation)) {
|
|
47
|
+
const scoped = request;
|
|
48
|
+
if (typeof scoped.scope !== "string" || !scoped.scope.length || scoped.scope.length > 512 || /[\x00-\x1f]/.test(scoped.scope)) throw new Error("Invalid search scope");
|
|
49
|
+
if (scoped.operation !== "invalidate" && (typeof scoped.scanId !== "string" || !/^[a-zA-Z0-9_.:-]{1,128}$/.test(scoped.scanId))) throw new Error("Invalid search scan");
|
|
50
|
+
if (scoped.operation === "invalidate") return {
|
|
51
|
+
...partition,
|
|
52
|
+
operation: "invalidate",
|
|
53
|
+
scope: scoped.scope
|
|
54
|
+
};
|
|
55
|
+
const scan = {
|
|
56
|
+
...partition,
|
|
57
|
+
scope: scoped.scope,
|
|
58
|
+
scanId: scoped.scanId
|
|
59
|
+
};
|
|
60
|
+
return scoped.operation === "append" ? {
|
|
61
|
+
...scan,
|
|
62
|
+
operation: "append",
|
|
63
|
+
documents: require_resource_search.projectSearchDocuments(scoped.documents)
|
|
64
|
+
} : {
|
|
65
|
+
...scan,
|
|
66
|
+
operation: scoped.operation
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (request.operation === "query") {
|
|
70
|
+
if (typeof request.query !== "string" || request.query.length > 300 || request.limit !== void 0 && (!Number.isSafeInteger(request.limit) || request.limit < 1 || request.limit > 1e3) || request.kind !== void 0 && !/^[a-zA-Z0-9_.:-]{1,253}$/.test(request.kind) || request.namespace !== void 0 && !/^[a-zA-Z0-9_.:-]{0,253}$/.test(request.namespace)) throw new Error("Invalid search query");
|
|
71
|
+
return {
|
|
72
|
+
...partition,
|
|
73
|
+
operation: "query",
|
|
74
|
+
query: request.query,
|
|
75
|
+
limit: request.limit,
|
|
76
|
+
kind: request.kind,
|
|
77
|
+
namespace: request.namespace
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (request.operation !== "status" && request.operation !== "clear" && request.operation !== "permit") throw new Error("Invalid search operation");
|
|
81
|
+
return {
|
|
82
|
+
...partition,
|
|
83
|
+
operation: request.operation
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function startResourceSearchIndex() {
|
|
87
|
+
let active = true;
|
|
88
|
+
let worker;
|
|
89
|
+
const preferences = require_monitoring_preferences_store.MonitoringPreferencesStore.getInstanceOrCreate();
|
|
90
|
+
const stopPreference = require__global_external_mobx.reaction(() => preferences.resourceSearchEnabled, (enabled) => {
|
|
91
|
+
if (!enabled) {
|
|
92
|
+
worker?.close();
|
|
93
|
+
worker = void 0;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
const nextRequest = /* @__PURE__ */ new Map();
|
|
97
|
+
const waits = /* @__PURE__ */ new Map();
|
|
98
|
+
const contextFingerprint = (request) => {
|
|
99
|
+
const cluster = require_extensions.Main.Catalog.getClusterById(request.clusterId);
|
|
100
|
+
if (!cluster) throw new Error("Unknown search cluster");
|
|
101
|
+
return (0, node_crypto.createHash)("sha256").update(request.contextId).update("\0").update(cluster.contextName).update("\0").update(cluster.kubeConfigPath).update("\0").update((0, node_fs.readFileSync)(cluster.kubeConfigPath)).digest("hex");
|
|
102
|
+
};
|
|
103
|
+
electron.ipcMain.handle(require_resource_search.RESOURCE_SEARCH_CHANNEL, async (event, input) => {
|
|
104
|
+
const request = validateSearchRequest(input);
|
|
105
|
+
if (!active || !trustedSearchSender(event, request.clusterId)) throw new Error("Search index unavailable");
|
|
106
|
+
const contextId = contextFingerprint(request);
|
|
107
|
+
if (request.operation === "permit") {
|
|
108
|
+
const start = Math.max(Date.now(), nextRequest.get(request.clusterId) ?? 0);
|
|
109
|
+
nextRequest.set(request.clusterId, start + 500);
|
|
110
|
+
if (start > Date.now()) await new Promise((resolve) => {
|
|
111
|
+
const timer = setTimeout(() => {
|
|
112
|
+
waits.delete(timer);
|
|
113
|
+
resolve();
|
|
114
|
+
}, start - Date.now());
|
|
115
|
+
waits.set(timer, resolve);
|
|
116
|
+
});
|
|
117
|
+
if (!active || !trustedSearchSender(event, request.clusterId) || contextFingerprint(request) !== contextId) throw new Error("Search context changed");
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (!preferences.resourceSearchEnabled) throw new Error("Resource search is disabled");
|
|
121
|
+
if (!worker) {
|
|
122
|
+
const directory = (0, node_path.join)(electron.app.getPath("userData"), "freelens-power-search");
|
|
123
|
+
(0, node_fs.mkdirSync)(directory, {
|
|
124
|
+
recursive: true,
|
|
125
|
+
mode: 448
|
|
126
|
+
});
|
|
127
|
+
worker = new require_resource_search_worker.ResourceSearchWorker((0, node_path.join)(directory, "resources.sqlite"));
|
|
128
|
+
}
|
|
129
|
+
const result = await worker.call({
|
|
130
|
+
...request,
|
|
131
|
+
contextId
|
|
132
|
+
});
|
|
133
|
+
if (!active || !preferences.resourceSearchEnabled || !trustedSearchSender(event, request.clusterId) || contextFingerprint(request) !== contextId) throw new Error("Search context changed");
|
|
134
|
+
return result;
|
|
135
|
+
});
|
|
136
|
+
return () => {
|
|
137
|
+
active = false;
|
|
138
|
+
stopPreference();
|
|
139
|
+
electron.ipcMain.removeHandler(require_resource_search.RESOURCE_SEARCH_CHANNEL);
|
|
140
|
+
for (const [timer, resolve] of waits) {
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
resolve();
|
|
143
|
+
}
|
|
144
|
+
waits.clear();
|
|
145
|
+
nextRequest.clear();
|
|
146
|
+
worker?.close();
|
|
147
|
+
worker = void 0;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
151
|
+
exports.startResourceSearchIndex = startResourceSearchIndex;
|
|
152
|
+
exports.trustedSearchSender = trustedSearchSender;
|
|
153
|
+
exports.validateSearchRequest = validateSearchRequest;
|
|
154
|
+
|
|
155
|
+
//# sourceMappingURL=resource-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-search.js","names":[],"sources":["../../src/main/resource-search.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { Main } from \"@freelensapp/extensions\";\nimport { app, BrowserWindow, type IpcMainInvokeEvent, ipcMain } from \"electron\";\nimport { reaction } from \"mobx\";\nimport {\n projectSearchDocuments,\n RESOURCE_SEARCH_CHANNEL,\n type SearchIndexRequest,\n validSearchPartition,\n} from \"../common/resource-search\";\nimport { MonitoringPreferencesStore } from \"../common/store/monitoring-preferences-store\";\nimport { ResourceSearchWorker } from \"./resource-search-worker\";\n\nexport function trustedSearchSender(event: IpcMainInvokeEvent, clusterId: string): boolean {\n const window = BrowserWindow.fromWebContents(event.sender);\n const frame = event.senderFrame;\n if (!window || window.isDestroyed() || !frame) return false;\n try {\n const url = new URL(frame.url);\n const root = new URL(event.sender.getURL());\n if (\n url.protocol !== \"https:\" ||\n root.protocol !== \"https:\" ||\n url.username ||\n url.password ||\n root.username ||\n root.password\n )\n return false;\n if (url.hostname !== `${clusterId}.renderer.freelens.app` || root.port !== url.port) return false;\n if (frame === event.sender.mainFrame) return root.origin === url.origin;\n return (\n root.hostname === \"renderer.freelens.app\" &&\n frame.parent === event.sender.mainFrame &&\n frame.top === event.sender.mainFrame\n );\n } catch {\n return false;\n }\n}\n\nexport function validateSearchRequest(input: unknown): SearchIndexRequest {\n if (!validSearchPartition(input)) throw new Error(\"Invalid search partition\");\n const request = input as SearchIndexRequest;\n const partition = { clusterId: request.clusterId, contextId: request.contextId };\n if (request.operation === \"reconcile\") {\n if (\n !Array.isArray(request.resources) ||\n request.resources.length > 10000 ||\n request.resources.some(\n (resource) => typeof resource !== \"string\" || !/^[a-zA-Z0-9_.-]{0,253}\\/[a-zA-Z0-9_.-]{1,253}$/.test(resource),\n )\n )\n throw new Error(\"Invalid search resource catalog\");\n return { ...partition, operation: \"reconcile\", resources: [...new Set(request.resources)] };\n }\n if ([\"begin\", \"append\", \"complete\", \"invalidate\"].includes(request.operation)) {\n const scoped = request as Extract<SearchIndexRequest, { scope: string }>;\n if (\n typeof scoped.scope !== \"string\" ||\n !scoped.scope.length ||\n scoped.scope.length > 512 ||\n /[\\x00-\\x1f]/.test(scoped.scope)\n )\n throw new Error(\"Invalid search scope\");\n if (\n scoped.operation !== \"invalidate\" &&\n (typeof scoped.scanId !== \"string\" || !/^[a-zA-Z0-9_.:-]{1,128}$/.test(scoped.scanId))\n )\n throw new Error(\"Invalid search scan\");\n if (scoped.operation === \"invalidate\") return { ...partition, operation: \"invalidate\", scope: scoped.scope };\n const scan = { ...partition, scope: scoped.scope, scanId: scoped.scanId };\n return scoped.operation === \"append\"\n ? { ...scan, operation: \"append\", documents: projectSearchDocuments(scoped.documents) }\n : { ...scan, operation: scoped.operation };\n }\n if (request.operation === \"query\") {\n if (\n typeof request.query !== \"string\" ||\n request.query.length > 300 ||\n (request.limit !== undefined &&\n (!Number.isSafeInteger(request.limit) || request.limit < 1 || request.limit > 1000)) ||\n (request.kind !== undefined && !/^[a-zA-Z0-9_.:-]{1,253}$/.test(request.kind)) ||\n (request.namespace !== undefined && !/^[a-zA-Z0-9_.:-]{0,253}$/.test(request.namespace))\n )\n throw new Error(\"Invalid search query\");\n return {\n ...partition,\n operation: \"query\",\n query: request.query,\n limit: request.limit,\n kind: request.kind,\n namespace: request.namespace,\n };\n }\n if (request.operation !== \"status\" && request.operation !== \"clear\" && request.operation !== \"permit\")\n throw new Error(\"Invalid search operation\");\n return { ...partition, operation: request.operation };\n}\n\nexport function startResourceSearchIndex(): () => void {\n let active = true;\n let worker: ResourceSearchWorker | undefined;\n const preferences = MonitoringPreferencesStore.getInstanceOrCreate<MonitoringPreferencesStore>();\n const stopPreference = reaction(\n () => preferences.resourceSearchEnabled,\n (enabled) => {\n if (!enabled) {\n worker?.close();\n worker = undefined;\n }\n },\n );\n const nextRequest = new Map<string, number>();\n const waits = new Map<ReturnType<typeof setTimeout>, () => void>();\n const contextFingerprint = (request: SearchIndexRequest) => {\n const cluster = Main.Catalog.getClusterById(request.clusterId);\n if (!cluster) throw new Error(\"Unknown search cluster\");\n // only a digest of the local configuration reaches the database partition key\n return createHash(\"sha256\")\n .update(request.contextId)\n .update(\"\\0\")\n .update(cluster.contextName)\n .update(\"\\0\")\n .update(cluster.kubeConfigPath)\n .update(\"\\0\")\n .update(readFileSync(cluster.kubeConfigPath))\n .digest(\"hex\");\n };\n ipcMain.handle(RESOURCE_SEARCH_CHANNEL, async (event, input: unknown) => {\n const request = validateSearchRequest(input);\n if (!active || !trustedSearchSender(event, request.clusterId)) throw new Error(\"Search index unavailable\");\n const contextId = contextFingerprint(request);\n if (request.operation === \"permit\") {\n const start = Math.max(Date.now(), nextRequest.get(request.clusterId) ?? 0);\n nextRequest.set(request.clusterId, start + 500);\n if (start > Date.now())\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n waits.delete(timer);\n resolve();\n }, start - Date.now());\n waits.set(timer, resolve);\n });\n if (!active || !trustedSearchSender(event, request.clusterId) || contextFingerprint(request) !== contextId)\n throw new Error(\"Search context changed\");\n return true;\n }\n if (!preferences.resourceSearchEnabled) throw new Error(\"Resource search is disabled\");\n if (!worker) {\n const directory = join(app.getPath(\"userData\"), \"freelens-power-search\");\n mkdirSync(directory, { recursive: true, mode: 0o700 });\n worker = new ResourceSearchWorker(join(directory, \"resources.sqlite\"));\n }\n const result = await worker.call({ ...request, contextId });\n if (\n !active ||\n !preferences.resourceSearchEnabled ||\n !trustedSearchSender(event, request.clusterId) ||\n contextFingerprint(request) !== contextId\n )\n throw new Error(\"Search context changed\");\n return result;\n });\n return () => {\n active = false;\n stopPreference();\n ipcMain.removeHandler(RESOURCE_SEARCH_CHANNEL);\n for (const [timer, resolve] of waits) {\n clearTimeout(timer);\n resolve();\n }\n waits.clear();\n nextRequest.clear();\n worker?.close();\n worker = undefined;\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const require__global_external_react = require("./_virtual/_global-external_react.js");
|
|
2
|
+
//#region src/renderer/ai-logs-sql.ts
|
|
3
|
+
var POWER_LOGS_SQL_DRAFT_PROMPT = `On the Power Logs workspace, help draft SQLite SELECT queries for captured logs.
|
|
4
|
+
The table is logs(timestamp INTEGER, pod TEXT, podUid TEXT, container TEXT, level TEXT, message TEXT, fieldsJson TEXT, previous INTEGER).
|
|
5
|
+
Timestamp is Unix epoch milliseconds and can be NULL. previous is 0 or 1. fieldsJson contains JSON text or NULL, usable with SQLite json_extract.
|
|
6
|
+
Return proposed queries in fenced sql blocks. Draft queries only: never execute them, call a query tool, or claim they ran. The user can insert a draft into the SQL editor, review it, and explicitly run it.`;
|
|
7
|
+
function isPowerLogsWorkspace() {
|
|
8
|
+
return !!document.querySelector("[data-testid=\"power-logs-page\"]") || /\/power-logs\/?$/.test(window.location.pathname);
|
|
9
|
+
}
|
|
10
|
+
function usePowerLogsWorkspace() {
|
|
11
|
+
const [active, setActive] = require__global_external_react.useState(isPowerLogsWorkspace);
|
|
12
|
+
require__global_external_react.useEffect(() => {
|
|
13
|
+
const update = (event) => {
|
|
14
|
+
const value = event.detail?.active;
|
|
15
|
+
if (typeof value === "boolean") setActive(value);
|
|
16
|
+
};
|
|
17
|
+
window.addEventListener("power-logs-workspace-change", update);
|
|
18
|
+
return () => window.removeEventListener("power-logs-workspace-change", update);
|
|
19
|
+
}, []);
|
|
20
|
+
return active;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
exports.POWER_LOGS_SQL_DRAFT_PROMPT = POWER_LOGS_SQL_DRAFT_PROMPT;
|
|
24
|
+
exports.isPowerLogsWorkspace = isPowerLogsWorkspace;
|
|
25
|
+
exports.usePowerLogsWorkspace = usePowerLogsWorkspace;
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=ai-logs-sql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-logs-sql.js","names":[],"sources":["../../src/renderer/ai-logs-sql.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport const POWER_LOGS_SQL_DRAFT_PROMPT = `On the Power Logs workspace, help draft SQLite SELECT queries for captured logs.\nThe table is logs(timestamp INTEGER, pod TEXT, podUid TEXT, container TEXT, level TEXT, message TEXT, fieldsJson TEXT, previous INTEGER).\nTimestamp is Unix epoch milliseconds and can be NULL. previous is 0 or 1. fieldsJson contains JSON text or NULL, usable with SQLite json_extract.\nReturn proposed queries in fenced sql blocks. Draft queries only: never execute them, call a query tool, or claim they ran. The user can insert a draft into the SQL editor, review it, and explicitly run it.`;\n\nexport function isPowerLogsWorkspace(): boolean {\n return (\n !!document.querySelector('[data-testid=\"power-logs-page\"]') || /\\/power-logs\\/?$/.test(window.location.pathname)\n );\n}\n\nexport function usePowerLogsWorkspace(): boolean {\n const [active, setActive] = useState(isPowerLogsWorkspace);\n useEffect(() => {\n const update = (event: Event) => {\n const value = (event as CustomEvent<{ active?: unknown }>).detail?.active;\n if (typeof value === \"boolean\") setActive(value);\n };\n window.addEventListener(\"power-logs-workspace-change\", update);\n return () => window.removeEventListener(\"power-logs-workspace-change\", update);\n }, []);\n return active;\n}\n"],"mappings":";;AAEA,IAAa,8BAA8B;;;;AAK3C,SAAgB,uBAAgC;CAC9C,OACE,CAAC,CAAC,SAAS,cAAc,mCAAiC,KAAK,mBAAmB,KAAK,OAAO,SAAS,QAAQ;AAEnH;AAEA,SAAgB,wBAAiC;CAC/C,MAAM,CAAC,QAAQ,aAAa,+BAAA,SAAS,oBAAoB;CACzD,+BAAA,gBAAgB;EACd,MAAM,UAAU,UAAiB;GAC/B,MAAM,QAAS,MAA4C,QAAQ;GACnE,IAAI,OAAO,UAAU,WAAW,UAAU,KAAK;EACjD;EACA,OAAO,iBAAiB,+BAA+B,MAAM;EAC7D,aAAa,OAAO,oBAAoB,+BAA+B,MAAM;CAC/E,GAAG,CAAC,CAAC;CACL,OAAO;AACT"}
|
|
@@ -3,6 +3,7 @@ const require__global_external_mobx = require("./_virtual/_global-external_mobx.
|
|
|
3
3
|
const require__global_external_react = require("./_virtual/_global-external_react.js");
|
|
4
4
|
const require_jsx_runtime = require("./_virtual/_global-external_react/jsx-runtime.js");
|
|
5
5
|
const require_ai_icon = require("./ai-icon.js");
|
|
6
|
+
const require_ai_logs_sql = require("./ai-logs-sql.js");
|
|
6
7
|
const require_markdown_it = require("./node_modules/.pnpm/markdown-it@15.0.2/node_modules/markdown-it/dist/markdown-it.js");
|
|
7
8
|
let electron = require("electron");
|
|
8
9
|
//#region src/renderer/ai-message.tsx
|
|
@@ -185,6 +186,8 @@ function CodeBlock({ text, language }) {
|
|
|
185
186
|
"terminal"
|
|
186
187
|
].includes(language.toLowerCase());
|
|
187
188
|
const command = terminalInsertText(text);
|
|
189
|
+
const logsWorkspace = require_ai_logs_sql.usePowerLogsWorkspace();
|
|
190
|
+
const sqlDraft = ["sql", "sqlite"].includes(language.toLowerCase()) && logsWorkspace;
|
|
188
191
|
const insert = async () => {
|
|
189
192
|
const activeTerminal = visibleTerminalId();
|
|
190
193
|
if (!shell || !command || !activeTerminal || inserting) return;
|
|
@@ -206,33 +209,51 @@ function CodeBlock({ text, language }) {
|
|
|
206
209
|
children: [
|
|
207
210
|
/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
208
211
|
className: "power-ai-code-toolbar",
|
|
209
|
-
children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: language || "code" }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", { children: [
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
212
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: language || "code" }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", { children: [
|
|
213
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("button", {
|
|
214
|
+
type: "button",
|
|
215
|
+
"aria-label": "Copy code",
|
|
216
|
+
title: "Copy code to clipboard",
|
|
217
|
+
onClick: () => {
|
|
218
|
+
try {
|
|
219
|
+
electron.clipboard.writeText(text);
|
|
220
|
+
setFeedback("Copied");
|
|
221
|
+
} catch {
|
|
222
|
+
setFeedback("Unable to copy code.");
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
|
|
226
|
+
material: "content_copy",
|
|
227
|
+
size: 14
|
|
228
|
+
}), " Copy"]
|
|
229
|
+
}),
|
|
230
|
+
sqlDraft && /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
|
|
231
|
+
type: "button",
|
|
232
|
+
"aria-label": "Insert into SQL",
|
|
233
|
+
title: "Insert into the SQL editor without running",
|
|
234
|
+
disabled: !text.trim() || text.length > 65536,
|
|
235
|
+
onClick: () => {
|
|
236
|
+
if (!require_ai_logs_sql.isPowerLogsWorkspace() || !text.trim() || text.length > 65536) return;
|
|
237
|
+
window.dispatchEvent(new CustomEvent("power-logs-sql-draft", { detail: { sql: text } }));
|
|
238
|
+
window.dispatchEvent(new Event("power-ai-close"));
|
|
239
|
+
},
|
|
240
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
|
|
241
|
+
material: "post_add",
|
|
242
|
+
size: 14
|
|
243
|
+
}), " Insert into SQL"]
|
|
244
|
+
}),
|
|
245
|
+
shell && terminalId && /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
|
|
246
|
+
type: "button",
|
|
247
|
+
"aria-label": "Insert into terminal",
|
|
248
|
+
disabled: !command || !terminalId || inserting,
|
|
249
|
+
title: !command ? "Only a single line without control characters can be inserted" : !terminalId ? "Focus an open terminal tab to insert" : "Insert without running",
|
|
250
|
+
onClick: () => void insert(),
|
|
251
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
|
|
252
|
+
material: "terminal",
|
|
253
|
+
size: 14
|
|
254
|
+
}), " Insert"]
|
|
255
|
+
})
|
|
256
|
+
] })]
|
|
236
257
|
}),
|
|
237
258
|
/* @__PURE__ */ require_jsx_runtime.jsx("pre", { children: /* @__PURE__ */ require_jsx_runtime.jsx("code", { children: text }) }),
|
|
238
259
|
shell && !command && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
@@ -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 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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"}
|
|
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\";\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 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 <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 {sqlDraft && (\n <button\n type=\"button\"\n aria-label=\"Insert into SQL\"\n title=\"Insert into the SQL editor without running\"\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 )}\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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"}
|
|
@@ -5,6 +5,7 @@ const require_ai_icon = require("./ai-icon.js");
|
|
|
5
5
|
const require_ai_terminal_context = require("./src/common/ai-terminal-context.js");
|
|
6
6
|
const require_date_time = require("./src/common/date-time.js");
|
|
7
7
|
//#region src/renderer/ai-terminal-context.tsx
|
|
8
|
+
var nextTerminalTooltip = 0;
|
|
8
9
|
function activeAiTerminal(clusterId) {
|
|
9
10
|
if (!clusterId || require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid !== clusterId) return;
|
|
10
11
|
for (const element of document.querySelectorAll(`[data-testid^="dock-tab-content-for-"]`)) {
|
|
@@ -51,6 +52,7 @@ function captureAiTerminal(clusterId, expectedId) {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
function AiTerminalContext({ clusterId, attachment, onChange, disabled }) {
|
|
55
|
+
const [tooltipId] = require__global_external_react.useState(() => `power-terminal-tooltip-${++nextTerminalTooltip}`);
|
|
54
56
|
const [terminalId, setTerminalId] = require__global_external_react.useState(() => activeAiTerminal(clusterId));
|
|
55
57
|
const [preview, setPreview] = require__global_external_react.useState();
|
|
56
58
|
const [unavailable, setUnavailable] = require__global_external_react.useState(false);
|
|
@@ -108,50 +110,58 @@ function AiTerminalContext({ clusterId, attachment, onChange, disabled }) {
|
|
|
108
110
|
clusterId,
|
|
109
111
|
onChange
|
|
110
112
|
]);
|
|
113
|
+
const tooltip = attached ? "Detach terminal output from this question" : terminalId ? "Attach terminal output to this question" : "Open a connected terminal in this cluster";
|
|
111
114
|
return /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
112
115
|
className: "power-ai-terminal-context",
|
|
113
116
|
style: { fontSize: 11 },
|
|
114
117
|
children: [
|
|
115
|
-
/* @__PURE__ */ require_jsx_runtime.jsx("
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
118
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("style", { children: `.power-ai-terminal-trigger{position:absolute;top:8px;right:8px;display:inline-flex}.power-ai-terminal-tooltip{position:absolute;right:0;bottom:calc(100% + 8px);z-index:20;width:max-content;max-width:240px;padding:6px 8px;border:1px solid var(--borderFaintColor);border-radius:4px;background:var(--mainBackground);color:var(--textColorPrimary);font-size:12px;line-height:1.4;visibility:hidden;opacity:0;pointer-events:none}.power-ai-terminal-trigger:hover .power-ai-terminal-tooltip,.power-ai-terminal-trigger:focus-within .power-ai-terminal-tooltip{visibility:visible;opacity:1}` }),
|
|
119
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("span", {
|
|
120
|
+
className: "power-ai-terminal-trigger",
|
|
121
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx("button", {
|
|
122
|
+
type: "button",
|
|
123
|
+
"aria-describedby": tooltipId,
|
|
124
|
+
"aria-label": "Attach terminal output",
|
|
125
|
+
"aria-pressed": attached,
|
|
126
|
+
style: {
|
|
127
|
+
display: "inline-flex",
|
|
128
|
+
alignItems: "center",
|
|
129
|
+
justifyContent: "center",
|
|
130
|
+
width: 26,
|
|
131
|
+
height: 26,
|
|
132
|
+
minHeight: 26,
|
|
133
|
+
padding: 0,
|
|
134
|
+
color: "var(--textColorSecondary)",
|
|
135
|
+
borderColor: "var(--borderFaintColor)",
|
|
136
|
+
background: attached ? "color-mix(in srgb, var(--textColorPrimary) 8%, transparent)" : "transparent"
|
|
137
|
+
},
|
|
138
|
+
disabled: disabled || !terminalId,
|
|
139
|
+
title: tooltip,
|
|
140
|
+
onClick: () => {
|
|
141
|
+
if (attached) {
|
|
142
|
+
onChange(void 0);
|
|
143
|
+
setPreview(void 0);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const snapshot = captureAiTerminal(clusterId, terminalId);
|
|
147
|
+
setUnavailable(!snapshot);
|
|
148
|
+
if (!snapshot || !clusterId || !terminalId) return;
|
|
149
|
+
setPreview(snapshot);
|
|
150
|
+
onChange({
|
|
151
|
+
clusterId,
|
|
152
|
+
terminalId
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
|
|
156
|
+
material: "terminal",
|
|
157
|
+
size: 16
|
|
158
|
+
})
|
|
159
|
+
}), /* @__PURE__ */ require_jsx_runtime.jsx("span", {
|
|
160
|
+
role: "tooltip",
|
|
161
|
+
id: tooltipId,
|
|
162
|
+
className: "power-ai-terminal-tooltip",
|
|
163
|
+
children: tooltip
|
|
164
|
+
})]
|
|
155
165
|
}),
|
|
156
166
|
attached && preview && /* @__PURE__ */ require_jsx_runtime.jsxs("details", {
|
|
157
167
|
style: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-terminal-context.js","names":[],"sources":["../../src/renderer/ai-terminal-context.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { useEffect, useState } from \"react\";\nimport { type AiTerminalSnapshot, sanitizeAiTerminalSnapshot } from \"../common/ai-terminal-context\";\nimport { formatDateTime } from \"../common/date-time\";\nimport { AiIcon } from \"./ai-icon\";\n\ntype TerminalBuffer = {\n length: number;\n baseY: number;\n cursorY: number;\n getLine(index: number): { isWrapped?: boolean; translateToString(trimRight?: boolean): string } | undefined;\n};\n\nexport function activeAiTerminal(clusterId?: string): string | undefined {\n if (!clusterId || Renderer.Catalog.activeCluster.get()?.metadata.uid !== clusterId) return;\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 const id = element.dataset.testid?.slice(prefix.length);\n if (!visible || !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\nexport function captureAiTerminal(clusterId?: string, expectedId?: string): AiTerminalSnapshot | undefined {\n const id = activeAiTerminal(clusterId);\n if (!id || (expectedId && id !== expectedId)) return;\n try {\n // [xterm] is a guarded host implementation detail with no public buffer accessor\n const terminal = Renderer.Component.terminalStore.getTerminal(id) as unknown as\n | {\n xterm?: { buffer?: { active?: TerminalBuffer } };\n }\n | undefined;\n const buffer = terminal?.xterm?.buffer?.active;\n if (!buffer || !Number.isSafeInteger(buffer.length) || buffer.length > 10_000 || buffer.length < 0) return;\n const end = buffer.length;\n if (!Number.isSafeInteger(end) || end < 0) return;\n const logical: string[] = [];\n let characters = 0;\n for (let index = 0; index < end; index++) {\n const line = buffer.getLine(index);\n if (!line || (index === 0 && line.isWrapped)) return;\n const text = line.translateToString(!buffer.getLine(index + 1)?.isWrapped);\n characters += text.length + 1;\n if (characters > 1_000_000) return;\n if (line.isWrapped && logical.length) logical[logical.length - 1] += text;\n else logical.push(text);\n }\n if (activeAiTerminal(clusterId) !== id) return;\n return sanitizeAiTerminalSnapshot({ text: logical.join(\"\\n\"), capturedAt: new Date().toISOString() });\n } catch {\n return;\n }\n}\n\nexport interface AiTerminalAttachment {\n clusterId: string;\n terminalId: string;\n}\n\nexport function AiTerminalContext({\n clusterId,\n attachment,\n onChange,\n disabled,\n}: {\n clusterId?: string;\n attachment?: AiTerminalAttachment;\n onChange(value: AiTerminalAttachment | undefined): void;\n disabled: boolean;\n}) {\n const [terminalId, setTerminalId] = useState(() => activeAiTerminal(clusterId));\n const [preview, setPreview] = useState<AiTerminalSnapshot>();\n const [unavailable, setUnavailable] = useState(false);\n useEffect(() => {\n const update = () => setTerminalId(activeAiTerminal(clusterId));\n update();\n const timer = window.setInterval(update, 500);\n return () => window.clearInterval(timer);\n }, [clusterId]);\n const attached = Boolean(attachment && attachment.clusterId === clusterId && attachment.terminalId === terminalId);\n useEffect(() => {\n if (!attached) {\n if (attachment) onChange(undefined);\n setPreview(undefined);\n return;\n }\n let stopped = false;\n let scheduled: number | undefined;\n let polling: number | undefined;\n let subscription: { dispose(): void } | undefined;\n const stop = () => {\n stopped = true;\n window.clearTimeout(scheduled);\n window.clearInterval(polling);\n subscription?.dispose();\n subscription = undefined;\n };\n const update = () => {\n if (stopped) return;\n scheduled = undefined;\n const snapshot = captureAiTerminal(clusterId, attachment?.terminalId);\n setUnavailable(!snapshot);\n if (!snapshot) {\n setPreview(undefined);\n if (activeAiTerminal(clusterId) !== attachment?.terminalId) {\n stop();\n onChange(undefined);\n }\n return;\n }\n setPreview((previous) =>\n previous?.text === snapshot.text && previous.truncated === snapshot.truncated ? previous : snapshot,\n );\n };\n update();\n if (stopped) return stop;\n try {\n const terminal = Renderer.Component.terminalStore.getTerminal(attachment!.terminalId) as unknown as\n | {\n xterm?: { onWriteParsed?: (listener: () => void) => { dispose(): void } };\n }\n | undefined;\n subscription = terminal?.xterm?.onWriteParsed?.(() => {\n if (!stopped && scheduled === undefined) scheduled = window.setTimeout(update, 250);\n });\n } catch {}\n if (!subscription) polling = window.setInterval(update, 1000);\n return stop;\n }, [attached, attachment, clusterId, onChange]);\n return (\n <div className=\"power-ai-terminal-context\" style={{ fontSize: 11 }}>\n <button\n type=\"button\"\n aria-label=\"Attach terminal output\"\n aria-pressed={attached}\n style={{\n position: \"absolute\",\n top: 8,\n right: 8,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: 26,\n height: 26,\n minHeight: 26,\n padding: 0,\n color: \"var(--textColorSecondary)\",\n borderColor: \"var(--borderFaintColor)\",\n background: attached ? \"color-mix(in srgb, var(--textColorPrimary) 8%, transparent)\" : \"transparent\",\n }}\n disabled={disabled || !terminalId}\n title={\n attached\n ? \"Detach terminal output from this question\"\n : terminalId\n ? \"Attach terminal output to this question\"\n : \"Open a connected terminal in this cluster\"\n }\n onClick={() => {\n if (attached) {\n onChange(undefined);\n setPreview(undefined);\n return;\n }\n const snapshot = captureAiTerminal(clusterId, terminalId);\n setUnavailable(!snapshot);\n if (!snapshot || !clusterId || !terminalId) return;\n setPreview(snapshot);\n onChange({ clusterId, terminalId });\n }}\n >\n <AiIcon material=\"terminal\" size={16} />\n </button>\n {attached && preview && (\n <details style={{ padding: \"0 16px 4px\", margin: 0 }}>\n <summary>\n Terminal · {preview.text.split(\"\\n\").length} rows{preview.truncated ? \" · truncated\" : \"\"}\n </summary>\n <p>\n Preview captured <span style={{ whiteSpace: \"pre-wrap\" }}>{formatDateTime(preview.capturedAt)}</span> ·\n captured again when sent · up to 300 rows / 32 KiB · Common credentials masked. Review before sending.\n </p>\n <pre style={{ maxHeight: 160, overflow: \"auto\", whiteSpace: \"pre-wrap\" }}>{preview.text}</pre>\n <p>The attachment is not stored in chat history. Assistant replies may quote it.</p>\n </details>\n )}\n {unavailable && (\n <div role=\"status\" style={{ padding: \"4px 16px 8px\" }}>\n Terminal output is unavailable or exceeds the safe capture limit\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ai-terminal-context.js","names":[],"sources":["../../src/renderer/ai-terminal-context.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { useEffect, useState } from \"react\";\nimport { type AiTerminalSnapshot, sanitizeAiTerminalSnapshot } from \"../common/ai-terminal-context\";\nimport { formatDateTime } from \"../common/date-time\";\nimport { AiIcon } from \"./ai-icon\";\n\nlet nextTerminalTooltip = 0;\n\ntype TerminalBuffer = {\n length: number;\n baseY: number;\n cursorY: number;\n getLine(index: number): { isWrapped?: boolean; translateToString(trimRight?: boolean): string } | undefined;\n};\n\nexport function activeAiTerminal(clusterId?: string): string | undefined {\n if (!clusterId || Renderer.Catalog.activeCluster.get()?.metadata.uid !== clusterId) return;\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 const id = element.dataset.testid?.slice(prefix.length);\n if (!visible || !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\nexport function captureAiTerminal(clusterId?: string, expectedId?: string): AiTerminalSnapshot | undefined {\n const id = activeAiTerminal(clusterId);\n if (!id || (expectedId && id !== expectedId)) return;\n try {\n // [xterm] is a guarded host implementation detail with no public buffer accessor\n const terminal = Renderer.Component.terminalStore.getTerminal(id) as unknown as\n | {\n xterm?: { buffer?: { active?: TerminalBuffer } };\n }\n | undefined;\n const buffer = terminal?.xterm?.buffer?.active;\n if (!buffer || !Number.isSafeInteger(buffer.length) || buffer.length > 10_000 || buffer.length < 0) return;\n const end = buffer.length;\n if (!Number.isSafeInteger(end) || end < 0) return;\n const logical: string[] = [];\n let characters = 0;\n for (let index = 0; index < end; index++) {\n const line = buffer.getLine(index);\n if (!line || (index === 0 && line.isWrapped)) return;\n const text = line.translateToString(!buffer.getLine(index + 1)?.isWrapped);\n characters += text.length + 1;\n if (characters > 1_000_000) return;\n if (line.isWrapped && logical.length) logical[logical.length - 1] += text;\n else logical.push(text);\n }\n if (activeAiTerminal(clusterId) !== id) return;\n return sanitizeAiTerminalSnapshot({ text: logical.join(\"\\n\"), capturedAt: new Date().toISOString() });\n } catch {\n return;\n }\n}\n\nexport interface AiTerminalAttachment {\n clusterId: string;\n terminalId: string;\n}\n\nexport function AiTerminalContext({\n clusterId,\n attachment,\n onChange,\n disabled,\n}: {\n clusterId?: string;\n attachment?: AiTerminalAttachment;\n onChange(value: AiTerminalAttachment | undefined): void;\n disabled: boolean;\n}) {\n const [tooltipId] = useState(() => `power-terminal-tooltip-${++nextTerminalTooltip}`);\n const [terminalId, setTerminalId] = useState(() => activeAiTerminal(clusterId));\n const [preview, setPreview] = useState<AiTerminalSnapshot>();\n const [unavailable, setUnavailable] = useState(false);\n useEffect(() => {\n const update = () => setTerminalId(activeAiTerminal(clusterId));\n update();\n const timer = window.setInterval(update, 500);\n return () => window.clearInterval(timer);\n }, [clusterId]);\n const attached = Boolean(attachment && attachment.clusterId === clusterId && attachment.terminalId === terminalId);\n useEffect(() => {\n if (!attached) {\n if (attachment) onChange(undefined);\n setPreview(undefined);\n return;\n }\n let stopped = false;\n let scheduled: number | undefined;\n let polling: number | undefined;\n let subscription: { dispose(): void } | undefined;\n const stop = () => {\n stopped = true;\n window.clearTimeout(scheduled);\n window.clearInterval(polling);\n subscription?.dispose();\n subscription = undefined;\n };\n const update = () => {\n if (stopped) return;\n scheduled = undefined;\n const snapshot = captureAiTerminal(clusterId, attachment?.terminalId);\n setUnavailable(!snapshot);\n if (!snapshot) {\n setPreview(undefined);\n if (activeAiTerminal(clusterId) !== attachment?.terminalId) {\n stop();\n onChange(undefined);\n }\n return;\n }\n setPreview((previous) =>\n previous?.text === snapshot.text && previous.truncated === snapshot.truncated ? previous : snapshot,\n );\n };\n update();\n if (stopped) return stop;\n try {\n const terminal = Renderer.Component.terminalStore.getTerminal(attachment!.terminalId) as unknown as\n | {\n xterm?: { onWriteParsed?: (listener: () => void) => { dispose(): void } };\n }\n | undefined;\n subscription = terminal?.xterm?.onWriteParsed?.(() => {\n if (!stopped && scheduled === undefined) scheduled = window.setTimeout(update, 250);\n });\n } catch {}\n if (!subscription) polling = window.setInterval(update, 1000);\n return stop;\n }, [attached, attachment, clusterId, onChange]);\n const tooltip = attached\n ? \"Detach terminal output from this question\"\n : terminalId\n ? \"Attach terminal output to this question\"\n : \"Open a connected terminal in this cluster\";\n return (\n <div className=\"power-ai-terminal-context\" style={{ fontSize: 11 }}>\n <style>{`.power-ai-terminal-trigger{position:absolute;top:8px;right:8px;display:inline-flex}.power-ai-terminal-tooltip{position:absolute;right:0;bottom:calc(100% + 8px);z-index:20;width:max-content;max-width:240px;padding:6px 8px;border:1px solid var(--borderFaintColor);border-radius:4px;background:var(--mainBackground);color:var(--textColorPrimary);font-size:12px;line-height:1.4;visibility:hidden;opacity:0;pointer-events:none}.power-ai-terminal-trigger:hover .power-ai-terminal-tooltip,.power-ai-terminal-trigger:focus-within .power-ai-terminal-tooltip{visibility:visible;opacity:1}`}</style>\n <span className=\"power-ai-terminal-trigger\">\n <button\n type=\"button\"\n aria-describedby={tooltipId}\n aria-label=\"Attach terminal output\"\n aria-pressed={attached}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: 26,\n height: 26,\n minHeight: 26,\n padding: 0,\n color: \"var(--textColorSecondary)\",\n borderColor: \"var(--borderFaintColor)\",\n background: attached ? \"color-mix(in srgb, var(--textColorPrimary) 8%, transparent)\" : \"transparent\",\n }}\n disabled={disabled || !terminalId}\n title={tooltip}\n onClick={() => {\n if (attached) {\n onChange(undefined);\n setPreview(undefined);\n return;\n }\n const snapshot = captureAiTerminal(clusterId, terminalId);\n setUnavailable(!snapshot);\n if (!snapshot || !clusterId || !terminalId) return;\n setPreview(snapshot);\n onChange({ clusterId, terminalId });\n }}\n >\n <AiIcon material=\"terminal\" size={16} />\n </button>\n <span role=\"tooltip\" id={tooltipId} className=\"power-ai-terminal-tooltip\">\n {tooltip}\n </span>\n </span>\n {attached && preview && (\n <details style={{ padding: \"0 16px 4px\", margin: 0 }}>\n <summary>\n Terminal · {preview.text.split(\"\\n\").length} rows{preview.truncated ? \" · truncated\" : \"\"}\n </summary>\n <p>\n Preview captured <span style={{ whiteSpace: \"pre-wrap\" }}>{formatDateTime(preview.capturedAt)}</span> ·\n captured again when sent · up to 300 rows / 32 KiB · Common credentials masked. Review before sending.\n </p>\n <pre style={{ maxHeight: 160, overflow: \"auto\", whiteSpace: \"pre-wrap\" }}>{preview.text}</pre>\n <p>The attachment is not stored in chat history. Assistant replies may quote it.</p>\n </details>\n )}\n {unavailable && (\n <div role=\"status\" style={{ padding: \"4px 16px 8px\" }}>\n Terminal output is unavailable or exceeds the safe capture limit\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
package/out/renderer/ai.js
CHANGED
|
@@ -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_diagnostic_handoff = require("./ai-diagnostic-handoff.js");
|
|
15
15
|
const require_ai_extras$1 = require("./ai-extras.js");
|
|
16
|
+
const require_ai_logs_sql = require("./ai-logs-sql.js");
|
|
16
17
|
const require_ai_message = require("./ai-message.js");
|
|
17
18
|
const require_ai_provider_icon = require("./ai-provider-icon.js");
|
|
18
19
|
const require_ai_resize = require("./ai-resize.js");
|
|
@@ -306,9 +307,15 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
|
|
|
306
307
|
};
|
|
307
308
|
window.addEventListener(require_ai_diagnostic_handoff.AI_DIAGNOSTIC_EVENT, onDiagnostic);
|
|
308
309
|
window.addEventListener("power-ai-open", updateAndOpen);
|
|
309
|
-
|
|
310
|
+
window.addEventListener("power-ai-open-only", openOnly);
|
|
311
|
+
const closePanel = () => setOpen(false);
|
|
312
|
+
window.addEventListener("power-ai-close", closePanel);
|
|
313
|
+
function openOnly() {
|
|
310
314
|
update();
|
|
311
315
|
setOpen(true);
|
|
316
|
+
}
|
|
317
|
+
function updateAndOpen() {
|
|
318
|
+
openOnly();
|
|
312
319
|
clusterReview.current();
|
|
313
320
|
}
|
|
314
321
|
return () => {
|
|
@@ -321,6 +328,8 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
|
|
|
321
328
|
window.clearInterval(timer);
|
|
322
329
|
observer.disconnect();
|
|
323
330
|
window.removeEventListener("power-ai-open", updateAndOpen);
|
|
331
|
+
window.removeEventListener("power-ai-open-only", openOnly);
|
|
332
|
+
window.removeEventListener("power-ai-close", closePanel);
|
|
324
333
|
window.removeEventListener(require_ai_diagnostic_handoff.AI_DIAGNOSTIC_EVENT, onDiagnostic);
|
|
325
334
|
};
|
|
326
335
|
}, []);
|
|
@@ -465,7 +474,11 @@ var AiChat = require__global_external_mobx_react.observer(({ isClusterDocument }
|
|
|
465
474
|
}, []),
|
|
466
475
|
context: requestContext,
|
|
467
476
|
...terminalSnapshot ? { terminalSnapshot } : {},
|
|
468
|
-
systemPrompt: [
|
|
477
|
+
systemPrompt: [
|
|
478
|
+
preferences.systemPrompt,
|
|
479
|
+
requestContext.clusterId ? preferences.clusterPrompt : "",
|
|
480
|
+
require_ai_logs_sql.isPowerLogsWorkspace() ? require_ai_logs_sql.POWER_LOGS_SQL_DRAFT_PROMPT : ""
|
|
481
|
+
].filter(Boolean).join("\n\n"),
|
|
469
482
|
model: preferences.model,
|
|
470
483
|
speed: preferences.speed,
|
|
471
484
|
reasoningEffort: preferences.reasoningEffort
|