dsh-cloudq 0.1.8 → 0.2.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/lib/client.js +257 -160
- package/lib/index.js +9 -2
- package/package.json +1 -1
- package/skills/cloudq/scripts/create_role.py +29 -4
package/lib/client.js
CHANGED
|
@@ -599,14 +599,17 @@ linear-gradient(-30deg, transparent 49.5%, #2c2c31 49.5%, #2c2c31 50.5%, transpa
|
|
|
599
599
|
return !!sessionId && cloudqSessions.has(sessionId);
|
|
600
600
|
}
|
|
601
601
|
let cloudqHistorySync = null;
|
|
602
|
+
let reconcileQueued = false;
|
|
602
603
|
function reconcileCloudqSessionsFromHost() {
|
|
603
|
-
if (cloudqHistorySync)
|
|
604
|
+
if (cloudqHistorySync) {
|
|
605
|
+
reconcileQueued = true;
|
|
606
|
+
return cloudqHistorySync;
|
|
607
|
+
}
|
|
604
608
|
cloudqHistorySync = cloudqRequest(API_CLOUDQ_SESSIONS, void 0, 6e4).then((response) => {
|
|
605
609
|
const detected = new Set(Array.isArray(response?.sessionIds) ? response.sessionIds : []);
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
const
|
|
609
|
-
if (summary && !summary.blank && !detected.has(sessionId)) cloudqSessions.delete(sessionId);
|
|
610
|
+
const known = new Set(Array.isArray(response?.knownIds) ? response.knownIds : []);
|
|
611
|
+
if (known.size > 0) {
|
|
612
|
+
for (const sessionId of Array.from(cloudqSessions)) if (!known.has(sessionId)) cloudqSessions.delete(sessionId);
|
|
610
613
|
}
|
|
611
614
|
for (const sessionId of detected) cloudqSessions.add(sessionId);
|
|
612
615
|
saveCloudqSessions(cloudqSessions);
|
|
@@ -614,6 +617,10 @@ linear-gradient(-30deg, transparent 49.5%, #2c2c31 49.5%, #2c2c31 50.5%, transpa
|
|
|
614
617
|
return detected;
|
|
615
618
|
}).catch(() => /* @__PURE__ */ new Set()).finally(() => {
|
|
616
619
|
cloudqHistorySync = null;
|
|
620
|
+
if (reconcileQueued) {
|
|
621
|
+
reconcileQueued = false;
|
|
622
|
+
reconcileCloudqSessionsFromHost();
|
|
623
|
+
}
|
|
617
624
|
});
|
|
618
625
|
return cloudqHistorySync;
|
|
619
626
|
}
|
|
@@ -792,6 +799,17 @@ linear-gradient(-30deg, transparent 49.5%, #2c2c31 49.5%, #2c2c31 50.5%, transpa
|
|
|
792
799
|
* order (manual and recent-order variants). Only if a collapsed/search
|
|
793
800
|
* view prevents an exact sequence match do we fall back to title matching.
|
|
794
801
|
*/
|
|
802
|
+
function rowSessionId(row) {
|
|
803
|
+
const fiberKey = Object.keys(row).find((key) => key.startsWith("__reactFiber$"));
|
|
804
|
+
if (!fiberKey) return void 0;
|
|
805
|
+
let fiber = row[fiberKey];
|
|
806
|
+
for (let depth = 0; fiber && depth < 12; depth += 1) {
|
|
807
|
+
const props = fiber.memoizedProps;
|
|
808
|
+
const id = props?.node?.id ?? props?.sessionId;
|
|
809
|
+
if (typeof id === "string" && id.startsWith("session-")) return id;
|
|
810
|
+
fiber = fiber.return;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
795
813
|
function installSessionBadges() {
|
|
796
814
|
let disposed = false;
|
|
797
815
|
let frame = 0;
|
|
@@ -821,7 +839,8 @@ linear-gradient(-30deg, transparent 49.5%, #2c2c31 49.5%, #2c2c31 50.5%, transpa
|
|
|
821
839
|
const exact = exactOrder?.[index] ?? (selected ? snapshot.byId?.[snapshot.current] : void 0);
|
|
822
840
|
const candidates = exact ? [exact] : byTitle.get(title) ?? [];
|
|
823
841
|
const states = new Set(candidates.map((summary) => isCloudqSession(summary.id)));
|
|
824
|
-
const
|
|
842
|
+
const fiberId = rowSessionId(row);
|
|
843
|
+
const cloudq = fiberId !== void 0 ? isCloudqSession(fiberId) : candidates.length > 0 && states.size === 1 && states.has(true);
|
|
825
844
|
const existing = row.querySelector("[data-cloudq-session-badge=\"true\"]");
|
|
826
845
|
if (!cloudq) {
|
|
827
846
|
existing?.remove();
|
|
@@ -995,7 +1014,9 @@ body.dsh-cloudq-mode-active [class*=composerHero] [class*=root][class*=hero] {
|
|
|
995
1014
|
}
|
|
996
1015
|
.dsh-cloudq-convo-chip {
|
|
997
1016
|
position: fixed;
|
|
998
|
-
|
|
1017
|
+
/* Below the conversation header divider (header bottom ≈76px) so the chip
|
|
1018
|
+
never overlaps the Session log affordance in the title row. */
|
|
1019
|
+
top: 88px;
|
|
999
1020
|
right: 16px;
|
|
1000
1021
|
display: inline-flex;
|
|
1001
1022
|
align-items: center;
|
|
@@ -1586,12 +1607,24 @@ body.dsh-cloudq-mode-active [class*=composerHero] [class*=root][class*=hero] {
|
|
|
1586
1607
|
}
|
|
1587
1608
|
.dsh-cloudq-artifact__download {
|
|
1588
1609
|
justify-self: end;
|
|
1610
|
+
padding: 0;
|
|
1611
|
+
border: 0;
|
|
1612
|
+
background: none;
|
|
1589
1613
|
color: var(--dsw-alias-state-business-primary, #006eff);
|
|
1614
|
+
font: inherit;
|
|
1590
1615
|
text-decoration: none;
|
|
1616
|
+
cursor: pointer;
|
|
1591
1617
|
}
|
|
1592
1618
|
.dsh-cloudq-artifact__download:hover {
|
|
1593
1619
|
text-decoration: underline;
|
|
1594
1620
|
}
|
|
1621
|
+
.dsh-cloudq-artifact__download:disabled {
|
|
1622
|
+
cursor: default;
|
|
1623
|
+
opacity: .6;
|
|
1624
|
+
}
|
|
1625
|
+
span.dsh-cloudq-artifact__download {
|
|
1626
|
+
cursor: default;
|
|
1627
|
+
}
|
|
1595
1628
|
/* architecture library */
|
|
1596
1629
|
.dsh-cloudq-arch__toolbar {
|
|
1597
1630
|
position: relative;
|
|
@@ -2017,12 +2050,10 @@ display: none;
|
|
|
2017
2050
|
let panelBody = null;
|
|
2018
2051
|
let panelView = "usage";
|
|
2019
2052
|
let panelExpanded = false;
|
|
2020
|
-
let inspirationCache = null;
|
|
2021
2053
|
let inspirationCategory = 0;
|
|
2022
|
-
|
|
2023
|
-
let
|
|
2024
|
-
let
|
|
2025
|
-
let architectureFirstFolderId = null;
|
|
2054
|
+
const artifactCollapsedSessions = /* @__PURE__ */ new Set();
|
|
2055
|
+
let artifactRefreshTimer = 0;
|
|
2056
|
+
let artifactRetryTimer = 0;
|
|
2026
2057
|
let architectureRequestSeq = 0;
|
|
2027
2058
|
let architecturePickerCleanup = null;
|
|
2028
2059
|
function renderUsageView() {
|
|
@@ -2326,17 +2357,12 @@ display: none;
|
|
|
2326
2357
|
list.appendChild(card);
|
|
2327
2358
|
}
|
|
2328
2359
|
};
|
|
2329
|
-
if (inspirationCache) {
|
|
2330
|
-
paint(inspirationCache);
|
|
2331
|
-
return;
|
|
2332
|
-
}
|
|
2333
2360
|
const hint = document.createElement("div");
|
|
2334
2361
|
hint.className = "dsh-cloudq-panel__hint";
|
|
2335
2362
|
hint.textContent = "正在加载灵感…";
|
|
2336
2363
|
list.appendChild(hint);
|
|
2337
2364
|
requireCloudqCredential().then(() => cloudqRequest(API_INSPIRATIONS)).then((data) => {
|
|
2338
|
-
|
|
2339
|
-
paint(inspirationCache);
|
|
2365
|
+
paint(Array.isArray(data.inspirations) ? data.inspirations : []);
|
|
2340
2366
|
}).catch((error) => {
|
|
2341
2367
|
if (!panelExpanded) return;
|
|
2342
2368
|
list.textContent = "";
|
|
@@ -2346,140 +2372,181 @@ display: none;
|
|
|
2346
2372
|
list.appendChild(err);
|
|
2347
2373
|
});
|
|
2348
2374
|
}
|
|
2349
|
-
|
|
2375
|
+
/**
|
|
2376
|
+
* Refresh the artifact list in the background after a CloudQ conversation
|
|
2377
|
+
* turn completes. Never shows the loading hint: the visible list stays
|
|
2378
|
+
* untouched and is repainted synchronously once fresh data arrives. The
|
|
2379
|
+
* delayed retry covers artifacts archived with a lag.
|
|
2380
|
+
*/
|
|
2381
|
+
function scheduleArtifactSilentRefresh() {
|
|
2382
|
+
window.clearTimeout(artifactRefreshTimer);
|
|
2383
|
+
window.clearTimeout(artifactRetryTimer);
|
|
2384
|
+
const run = () => {
|
|
2385
|
+
cloudqRequest(API_ARTIFACTS).then((data) => {
|
|
2386
|
+
if (panelView !== "artifact" || !panelExpanded) return;
|
|
2387
|
+
paintArtifactList(Array.isArray(data?.sessions) ? data.sessions : [], Number(data?.total) || 0);
|
|
2388
|
+
}).catch(() => {});
|
|
2389
|
+
};
|
|
2390
|
+
artifactRefreshTimer = window.setTimeout(run, 3e3);
|
|
2391
|
+
artifactRetryTimer = window.setTimeout(run, 15e3);
|
|
2392
|
+
}
|
|
2393
|
+
function paintArtifactList(sessions, total) {
|
|
2394
|
+
if (panelView !== "artifact" || !panelExpanded) return;
|
|
2350
2395
|
panelBody.textContent = "";
|
|
2351
|
-
const
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
action.
|
|
2448
|
-
action.
|
|
2449
|
-
|
|
2450
|
-
action.
|
|
2451
|
-
action.
|
|
2452
|
-
action.
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2396
|
+
const safeSessions = Array.isArray(sessions) ? sessions : [];
|
|
2397
|
+
const artifactCount = safeSessions.reduce((sum, session) => {
|
|
2398
|
+
return sum + (Array.isArray(session?.Artifacts) ? session.Artifacts : []).length;
|
|
2399
|
+
}, 0);
|
|
2400
|
+
const toolbar = document.createElement("div");
|
|
2401
|
+
toolbar.className = "dsh-cloudq-artifact__toolbar";
|
|
2402
|
+
const summary = document.createElement("span");
|
|
2403
|
+
summary.textContent = `共 ${Number(total) || safeSessions.length} 组会话,${artifactCount} 个制品`;
|
|
2404
|
+
const refresh = document.createElement("button");
|
|
2405
|
+
refresh.type = "button";
|
|
2406
|
+
refresh.className = "dsh-cloudq-artifact__refresh";
|
|
2407
|
+
refresh.dataset.testid = "cloudq-artifact-refresh-btn";
|
|
2408
|
+
refresh.textContent = "刷新";
|
|
2409
|
+
refresh.addEventListener("click", () => renderArtifactView());
|
|
2410
|
+
toolbar.appendChild(summary);
|
|
2411
|
+
toolbar.appendChild(refresh);
|
|
2412
|
+
panelBody.appendChild(toolbar);
|
|
2413
|
+
if (safeSessions.length === 0) {
|
|
2414
|
+
const empty = document.createElement("div");
|
|
2415
|
+
empty.className = "dsh-cloudq-usage__table__empty";
|
|
2416
|
+
empty.textContent = "暂无制品";
|
|
2417
|
+
panelBody.appendChild(empty);
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
const list = document.createElement("div");
|
|
2421
|
+
list.className = "dsh-cloudq-artifact__list";
|
|
2422
|
+
const columns = document.createElement("div");
|
|
2423
|
+
columns.className = "dsh-cloudq-artifact__columns";
|
|
2424
|
+
for (const label of [
|
|
2425
|
+
"报告名称",
|
|
2426
|
+
"用户名称",
|
|
2427
|
+
"生成时间",
|
|
2428
|
+
"操作"
|
|
2429
|
+
]) {
|
|
2430
|
+
const cell = document.createElement("span");
|
|
2431
|
+
cell.textContent = label;
|
|
2432
|
+
columns.appendChild(cell);
|
|
2433
|
+
}
|
|
2434
|
+
list.appendChild(columns);
|
|
2435
|
+
for (const session of safeSessions) {
|
|
2436
|
+
const artifacts = Array.isArray(session?.Artifacts) ? session.Artifacts : [];
|
|
2437
|
+
const sessionKey = String(session?.SessionID ?? "");
|
|
2438
|
+
const group = document.createElement("section");
|
|
2439
|
+
group.className = "dsh-cloudq-artifact__session";
|
|
2440
|
+
const startCollapsed = sessionKey !== "" && artifactCollapsedSessions.has(sessionKey);
|
|
2441
|
+
if (startCollapsed) group.classList.add("is-collapsed");
|
|
2442
|
+
const sessionHead = document.createElement("button");
|
|
2443
|
+
sessionHead.type = "button";
|
|
2444
|
+
sessionHead.className = "dsh-cloudq-artifact__session-head";
|
|
2445
|
+
sessionHead.dataset.testid = `cloudq-artifact-session-${session?.SessionID ?? "unknown"}`;
|
|
2446
|
+
sessionHead.setAttribute("aria-expanded", String(!startCollapsed));
|
|
2447
|
+
const chevron = document.createElement("span");
|
|
2448
|
+
chevron.className = "dsh-cloudq-artifact__chevron";
|
|
2449
|
+
chevron.textContent = "▼";
|
|
2450
|
+
const title = document.createElement("span");
|
|
2451
|
+
title.className = "dsh-cloudq-artifact__session-title";
|
|
2452
|
+
title.textContent = session?.SessionTitle || "未命名会话";
|
|
2453
|
+
title.title = title.textContent;
|
|
2454
|
+
const latestTime = artifacts.reduce((latest, artifact) => {
|
|
2455
|
+
const time = String(artifact?.ArchiveTime ?? "");
|
|
2456
|
+
return time > latest ? time : latest;
|
|
2457
|
+
}, "");
|
|
2458
|
+
const meta = document.createElement("span");
|
|
2459
|
+
meta.className = "dsh-cloudq-artifact__session-meta";
|
|
2460
|
+
meta.textContent = `${artifacts.length} 个制品${latestTime ? ` · 最近 ${latestTime.slice(0, 10)}` : ""}`;
|
|
2461
|
+
sessionHead.appendChild(chevron);
|
|
2462
|
+
sessionHead.appendChild(title);
|
|
2463
|
+
sessionHead.appendChild(meta);
|
|
2464
|
+
const files = document.createElement("div");
|
|
2465
|
+
files.className = "dsh-cloudq-artifact__files";
|
|
2466
|
+
for (const artifact of artifacts) {
|
|
2467
|
+
const row = document.createElement("div");
|
|
2468
|
+
row.className = "dsh-cloudq-artifact__file";
|
|
2469
|
+
const main = document.createElement("div");
|
|
2470
|
+
main.className = "dsh-cloudq-artifact__file-main";
|
|
2471
|
+
const type = document.createElement("span");
|
|
2472
|
+
type.className = "dsh-cloudq-artifact__file-type";
|
|
2473
|
+
type.textContent = artifact?.FileType || "file";
|
|
2474
|
+
const name = document.createElement("span");
|
|
2475
|
+
name.className = "dsh-cloudq-artifact__file-name";
|
|
2476
|
+
name.textContent = artifact?.FileName || "未命名文件";
|
|
2477
|
+
name.title = `${name.textContent} · ${formatFileSize(artifact?.SizeBytes)}`;
|
|
2478
|
+
main.appendChild(type);
|
|
2479
|
+
main.appendChild(name);
|
|
2480
|
+
const owner = document.createElement("span");
|
|
2481
|
+
owner.className = "dsh-cloudq-artifact__owner";
|
|
2482
|
+
owner.textContent = artifact?.UserName || "—";
|
|
2483
|
+
const time = document.createElement("span");
|
|
2484
|
+
time.className = "dsh-cloudq-artifact__time";
|
|
2485
|
+
time.textContent = formatDateTime(artifact?.ArchiveTime);
|
|
2486
|
+
const downloadUrl = safeDownloadUrl(artifact?.DownloadURL);
|
|
2487
|
+
const fileName = artifact?.FileName || "未命名文件";
|
|
2488
|
+
const action = document.createElement(downloadUrl ? "button" : "span");
|
|
2489
|
+
action.className = "dsh-cloudq-artifact__download";
|
|
2490
|
+
action.textContent = downloadUrl ? "下载" : "不可用";
|
|
2491
|
+
if (downloadUrl) {
|
|
2492
|
+
action.type = "button";
|
|
2493
|
+
action.dataset.testid = `cloudq-artifact-download-${artifact?.ArtifactID ?? "unknown"}`;
|
|
2494
|
+
action.addEventListener("click", () => {
|
|
2495
|
+
if (action.disabled) return;
|
|
2496
|
+
action.disabled = true;
|
|
2497
|
+
action.textContent = "下载中…";
|
|
2498
|
+
const restore = () => {
|
|
2499
|
+
action.disabled = false;
|
|
2500
|
+
action.textContent = "下载";
|
|
2501
|
+
};
|
|
2502
|
+
fetch(downloadUrl).then((res) => {
|
|
2503
|
+
if (!res.ok) throw new Error(`download failed: ${res.status}`);
|
|
2504
|
+
return res.blob();
|
|
2505
|
+
}).then((blob) => {
|
|
2506
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
2507
|
+
const link = document.createElement("a");
|
|
2508
|
+
link.href = objectUrl;
|
|
2509
|
+
link.download = fileName;
|
|
2510
|
+
link.rel = "noopener noreferrer";
|
|
2511
|
+
link.click();
|
|
2512
|
+
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1e4);
|
|
2513
|
+
}).catch(() => {
|
|
2514
|
+
const link = document.createElement("a");
|
|
2515
|
+
link.href = downloadUrl;
|
|
2516
|
+
link.download = fileName;
|
|
2517
|
+
link.rel = "noopener noreferrer";
|
|
2518
|
+
link.click();
|
|
2519
|
+
}).finally(restore);
|
|
2520
|
+
});
|
|
2460
2521
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
group.appendChild(files);
|
|
2467
|
-
list.appendChild(group);
|
|
2522
|
+
row.appendChild(main);
|
|
2523
|
+
row.appendChild(owner);
|
|
2524
|
+
row.appendChild(time);
|
|
2525
|
+
row.appendChild(action);
|
|
2526
|
+
files.appendChild(row);
|
|
2468
2527
|
}
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2528
|
+
sessionHead.addEventListener("click", () => {
|
|
2529
|
+
const collapsed = group.classList.toggle("is-collapsed");
|
|
2530
|
+
sessionHead.setAttribute("aria-expanded", String(!collapsed));
|
|
2531
|
+
if (sessionKey) {
|
|
2532
|
+
if (collapsed) artifactCollapsedSessions.add(sessionKey);
|
|
2533
|
+
else artifactCollapsedSessions.delete(sessionKey);
|
|
2534
|
+
}
|
|
2535
|
+
});
|
|
2536
|
+
group.appendChild(sessionHead);
|
|
2537
|
+
group.appendChild(files);
|
|
2538
|
+
list.appendChild(group);
|
|
2474
2539
|
}
|
|
2540
|
+
panelBody.appendChild(list);
|
|
2541
|
+
}
|
|
2542
|
+
function renderArtifactView() {
|
|
2543
|
+
panelBody.textContent = "";
|
|
2475
2544
|
const hint = document.createElement("div");
|
|
2476
2545
|
hint.className = "dsh-cloudq-panel__hint";
|
|
2477
2546
|
hint.textContent = "正在加载制品…";
|
|
2478
2547
|
panelBody.appendChild(hint);
|
|
2479
2548
|
requireCloudqCredential().then(() => cloudqRequest(API_ARTIFACTS)).then((data) => {
|
|
2480
|
-
|
|
2481
|
-
artifactTotal = Number(data?.total) || artifactCache.length;
|
|
2482
|
-
paint(artifactCache, artifactTotal);
|
|
2549
|
+
paintArtifactList(Array.isArray(data?.sessions) ? data.sessions : [], Number(data?.total) || 0);
|
|
2483
2550
|
}).catch((error) => {
|
|
2484
2551
|
if (panelView !== "artifact" || !panelExpanded) return;
|
|
2485
2552
|
panelBody.textContent = "";
|
|
@@ -2714,14 +2781,16 @@ display: none;
|
|
|
2714
2781
|
}
|
|
2715
2782
|
for (const architecture of architectures) {
|
|
2716
2783
|
const imageUrl = safeDownloadUrl(architecture?.SvgURL);
|
|
2717
|
-
const
|
|
2784
|
+
const archId = typeof architecture?.ArchId === "string" ? architecture.ArchId.trim() : "";
|
|
2785
|
+
const consoleUrl = archId ? `https://console.cloud.tencent.com/advisor?archId=${encodeURIComponent(archId)}` : "";
|
|
2786
|
+
const card = document.createElement(consoleUrl ? "a" : "article");
|
|
2718
2787
|
card.className = "dsh-cloudq-arch__card";
|
|
2719
2788
|
card.dataset.testid = `cloudq-architecture-card-${architecture?.ArchId ?? "unknown"}`;
|
|
2720
|
-
if (
|
|
2721
|
-
card.href =
|
|
2789
|
+
if (consoleUrl) {
|
|
2790
|
+
card.href = consoleUrl;
|
|
2722
2791
|
card.target = "_blank";
|
|
2723
2792
|
card.rel = "noopener noreferrer";
|
|
2724
|
-
card.setAttribute("aria-label",
|
|
2793
|
+
card.setAttribute("aria-label", `打开架构图:${architecture?.ArchName || "未命名系统"}`);
|
|
2725
2794
|
}
|
|
2726
2795
|
const preview = document.createElement("div");
|
|
2727
2796
|
preview.className = "dsh-cloudq-arch__preview";
|
|
@@ -2740,7 +2809,7 @@ display: none;
|
|
|
2740
2809
|
image.addEventListener("load", () => placeholder.remove());
|
|
2741
2810
|
image.addEventListener("error", () => {
|
|
2742
2811
|
image.remove();
|
|
2743
|
-
placeholder.textContent = "
|
|
2812
|
+
placeholder.textContent = consoleUrl ? "预览加载失败,点击打开架构图" : "预览加载失败";
|
|
2744
2813
|
});
|
|
2745
2814
|
preview.appendChild(image);
|
|
2746
2815
|
}
|
|
@@ -2772,14 +2841,8 @@ display: none;
|
|
|
2772
2841
|
};
|
|
2773
2842
|
loadArchitectures(selectedFolderId);
|
|
2774
2843
|
};
|
|
2775
|
-
if (architectureDirectoriesCache !== null) {
|
|
2776
|
-
paintDirectories(architectureDirectoriesCache, architectureFirstFolderId);
|
|
2777
|
-
return;
|
|
2778
|
-
}
|
|
2779
2844
|
requireCloudqCredential().then(() => cloudqRequest(API_ARCH_DIRECTORIES)).then((data) => {
|
|
2780
|
-
|
|
2781
|
-
architectureFirstFolderId = Number(data?.firstFolderId) || null;
|
|
2782
|
-
paintDirectories(architectureDirectoriesCache, architectureFirstFolderId);
|
|
2845
|
+
paintDirectories(Array.isArray(data?.folders) ? data.folders : [], Number(data?.firstFolderId) || null);
|
|
2783
2846
|
}).catch((error) => {
|
|
2784
2847
|
if (panelView !== "architecture" || !panelExpanded || viewRequest !== architectureRequestSeq) return;
|
|
2785
2848
|
panelBody.textContent = "";
|
|
@@ -3054,12 +3117,7 @@ display: none;
|
|
|
3054
3117
|
panelEl = null;
|
|
3055
3118
|
panelBody = null;
|
|
3056
3119
|
}
|
|
3057
|
-
inspirationCache = null;
|
|
3058
3120
|
inspirationCategory = 0;
|
|
3059
|
-
artifactCache = null;
|
|
3060
|
-
artifactTotal = 0;
|
|
3061
|
-
architectureDirectoriesCache = null;
|
|
3062
|
-
architectureFirstFolderId = null;
|
|
3063
3121
|
architectureRequestSeq += 1;
|
|
3064
3122
|
panelView = "usage";
|
|
3065
3123
|
};
|
|
@@ -3615,12 +3673,51 @@ display: none;
|
|
|
3615
3673
|
window.dispatchEvent(new CustomEvent("dsh-cloudq:sessions-changed"));
|
|
3616
3674
|
};
|
|
3617
3675
|
window.addEventListener("storage", onStorage);
|
|
3676
|
+
const backstop = window.setInterval(() => {
|
|
3677
|
+
if (!disposed) reconcileCloudqSessionsFromHost();
|
|
3678
|
+
}, 3e4);
|
|
3618
3679
|
syncHistory();
|
|
3619
3680
|
return () => {
|
|
3620
3681
|
disposed = true;
|
|
3682
|
+
window.clearInterval(backstop);
|
|
3621
3683
|
window.removeEventListener("storage", onStorage);
|
|
3622
3684
|
};
|
|
3623
3685
|
}, "dsh-cloudq: reconcile durable session identities");
|
|
3686
|
+
ctx.effect(() => {
|
|
3687
|
+
const list = ctx?.sessions?.list;
|
|
3688
|
+
if (!list?.subscribe || !list?.getSnapshot) return void 0;
|
|
3689
|
+
const prevRunning = /* @__PURE__ */ new Map();
|
|
3690
|
+
return list.subscribe(() => {
|
|
3691
|
+
const byId = list.getSnapshot()?.byId ?? {};
|
|
3692
|
+
for (const [id, summary] of Object.entries(byId)) {
|
|
3693
|
+
const running = summary?.running === true;
|
|
3694
|
+
if (prevRunning.get(id) === true && !running && cloudqSessions.has(id)) scheduleArtifactSilentRefresh();
|
|
3695
|
+
prevRunning.set(id, running);
|
|
3696
|
+
}
|
|
3697
|
+
});
|
|
3698
|
+
}, "dsh-cloudq: refresh artifacts on turn completion");
|
|
3699
|
+
ctx.effect(() => {
|
|
3700
|
+
const markIfCloudqClaim = () => {
|
|
3701
|
+
const textarea = document.querySelector("textarea[class*=input]");
|
|
3702
|
+
const draft = typeof textarea?.value === "string" ? textarea.value : "";
|
|
3703
|
+
if (!/^\s*\/cloudq(?:\s|$)/i.test(draft)) return;
|
|
3704
|
+
const sessionId = cloudqCtx?.sessions?.selection?.getSnapshot?.()?.sessionId ?? cloudqCtx?.sessions?.list?.getSnapshot?.()?.current;
|
|
3705
|
+
if (sessionId) markCloudqSession(sessionId);
|
|
3706
|
+
};
|
|
3707
|
+
const onKeyDown = (event) => {
|
|
3708
|
+
if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
|
|
3709
|
+
if (event.target instanceof HTMLTextAreaElement) markIfCloudqClaim();
|
|
3710
|
+
};
|
|
3711
|
+
const onClick = (event) => {
|
|
3712
|
+
if (event.target?.closest?.("button[class*=primary]")) markIfCloudqClaim();
|
|
3713
|
+
};
|
|
3714
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
3715
|
+
document.addEventListener("click", onClick, true);
|
|
3716
|
+
return () => {
|
|
3717
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
3718
|
+
document.removeEventListener("click", onClick, true);
|
|
3719
|
+
};
|
|
3720
|
+
}, "dsh-cloudq: mark session on claim submit");
|
|
3624
3721
|
ctx.effect(installStyles, "dsh-cloudq: styles");
|
|
3625
3722
|
ctx.effect(() => {
|
|
3626
3723
|
const slots = ctx.slots;
|
package/lib/index.js
CHANGED
|
@@ -589,18 +589,23 @@ async function detectCloudqSessionIds(ctx) {
|
|
|
589
589
|
if (query === void 0) throw new HttpError(503, "session-query-unavailable", "当前 DSH 未启用会话查询服务。");
|
|
590
590
|
const records = await query.listSessions();
|
|
591
591
|
const ids = [];
|
|
592
|
+
const knownIds = [];
|
|
592
593
|
const queue = [...records];
|
|
593
594
|
const worker = async () => {
|
|
594
595
|
while (queue.length > 0) {
|
|
595
596
|
const sessionId = queue.shift()?.header?.id;
|
|
596
597
|
if (!sessionId) continue;
|
|
598
|
+
knownIds.push(sessionId);
|
|
597
599
|
try {
|
|
598
600
|
if (hasCloudqEvidence((await query.readSession(sessionId))?.events)) ids.push(sessionId);
|
|
599
601
|
} catch {}
|
|
600
602
|
}
|
|
601
603
|
};
|
|
602
604
|
await Promise.all(Array.from({ length: Math.min(6, queue.length) }, () => worker()));
|
|
603
|
-
return
|
|
605
|
+
return {
|
|
606
|
+
ids,
|
|
607
|
+
knownIds
|
|
608
|
+
};
|
|
604
609
|
}
|
|
605
610
|
/** Settings namespace for the browser card (must equal client card `key`). */
|
|
606
611
|
const SETTINGS_NAMESPACE = "dsh-cloudq";
|
|
@@ -825,9 +830,11 @@ function apply(ctx) {
|
|
|
825
830
|
handler: async (request, response) => {
|
|
826
831
|
try {
|
|
827
832
|
assertSafeRequest(request, "GET");
|
|
833
|
+
const { ids, knownIds } = await detectCloudqSessionIds(ctx);
|
|
828
834
|
sendJson(response, 200, {
|
|
829
835
|
ok: true,
|
|
830
|
-
sessionIds:
|
|
836
|
+
sessionIds: ids,
|
|
837
|
+
knownIds
|
|
831
838
|
});
|
|
832
839
|
} catch (error) {
|
|
833
840
|
sendError(response, error);
|
package/package.json
CHANGED
|
@@ -104,7 +104,7 @@ def find_available_role_name() -> tuple:
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
def save_config(account_uin: str, role_arn: str, role_id: str = "",
|
|
107
|
-
auto_created: bool = True):
|
|
107
|
+
auto_created: bool = True, role_name: str = ""):
|
|
108
108
|
"""保存配置到文件"""
|
|
109
109
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
110
110
|
|
|
@@ -114,15 +114,25 @@ def save_config(account_uin: str, role_arn: str, role_id: str = "",
|
|
|
114
114
|
except OSError:
|
|
115
115
|
pass
|
|
116
116
|
|
|
117
|
+
# 保留 DSH 插件写入的免密开关偏好,避免重建角色时被重置。
|
|
118
|
+
existing = {}
|
|
119
|
+
if CONFIG_FILE.exists():
|
|
120
|
+
try:
|
|
121
|
+
existing = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
|
|
122
|
+
except (json.JSONDecodeError, IOError):
|
|
123
|
+
existing = {}
|
|
124
|
+
|
|
117
125
|
config = {
|
|
118
126
|
"accountUin": account_uin,
|
|
119
|
-
"roleName": ROLE_NAME,
|
|
127
|
+
"roleName": role_name or ROLE_NAME,
|
|
120
128
|
"roleArn": role_arn,
|
|
121
129
|
"roleId": role_id,
|
|
122
130
|
"configuredAt": datetime.now(timezone.utc).isoformat(),
|
|
123
131
|
"autoCreated": auto_created,
|
|
124
132
|
"version": "1.0",
|
|
125
133
|
}
|
|
134
|
+
if isinstance(existing.get("enabled"), bool):
|
|
135
|
+
config["enabled"] = existing["enabled"]
|
|
126
136
|
|
|
127
137
|
CONFIG_FILE.write_text(
|
|
128
138
|
json.dumps(config, indent=2, ensure_ascii=False),
|
|
@@ -153,6 +163,21 @@ def main():
|
|
|
153
163
|
secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID", "")
|
|
154
164
|
secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY", "")
|
|
155
165
|
|
|
166
|
+
# 环境变量缺失时回退到凭证文件(与 login_url.py 一致),
|
|
167
|
+
# 使 DSH 插件等宿主可直接调用而无需注入环境变量。
|
|
168
|
+
if not secret_id or not secret_key:
|
|
169
|
+
try:
|
|
170
|
+
from credential_manager import get_credential
|
|
171
|
+
cred = get_credential()
|
|
172
|
+
secret_id = cred["secretId"]
|
|
173
|
+
secret_key = cred["secretKey"]
|
|
174
|
+
if cred.get("token"):
|
|
175
|
+
os.environ["TENCENTCLOUD_TOKEN"] = cred["token"]
|
|
176
|
+
except ImportError:
|
|
177
|
+
pass
|
|
178
|
+
except Exception:
|
|
179
|
+
pass
|
|
180
|
+
|
|
156
181
|
if not secret_id or not secret_key:
|
|
157
182
|
print(output_json(output_error(
|
|
158
183
|
"MissingCredentials",
|
|
@@ -210,7 +235,7 @@ def main():
|
|
|
210
235
|
attach_warnings.append(f"策略 {policy_name} 关联失败: {err_msg}")
|
|
211
236
|
print(f"WARNING: {attach_warnings[-1]}", file=sys.stderr)
|
|
212
237
|
|
|
213
|
-
save_config(account_uin, role_arn, role_id, auto_created=False)
|
|
238
|
+
save_config(account_uin, role_arn, role_id, auto_created=False, role_name=target_role)
|
|
214
239
|
result = output_success(role_arn, account_uin, role_id)
|
|
215
240
|
result["data"]["roleName"] = target_role
|
|
216
241
|
if attach_warnings:
|
|
@@ -266,7 +291,7 @@ def main():
|
|
|
266
291
|
|
|
267
292
|
# ============== 6. 保存配置 ==============
|
|
268
293
|
role_arn = f"qcs::cam::uin/{account_uin}:roleName/{target_role}"
|
|
269
|
-
save_config(account_uin, role_arn, role_id, auto_created=True)
|
|
294
|
+
save_config(account_uin, role_arn, role_id, auto_created=True, role_name=target_role)
|
|
270
295
|
|
|
271
296
|
# ============== 7. 输出结果 ==============
|
|
272
297
|
result = output_success(role_arn, account_uin, role_id)
|