minecodex 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +149 -0
- package/apps/dashboard/DESIGN.md +45 -0
- package/features/images/DESIGN.md +71 -0
- package/features/images/README.md +2 -0
- package/features/images/codex-feature.json +2 -1
- package/features/images/src/adapters/codex/cloud-import.mjs +52 -0
- package/features/images/src/adapters/codex/image-prompt.mjs +22 -0
- package/features/images/src/adapters/codex/library-api.mjs +55 -0
- package/features/images/src/adapters/codex/library-viewer.mjs +18 -0
- package/features/images/src/adapters/codex/prompt-resolver-source.mjs +8 -0
- package/features/images/src/backend/cloud-library.mjs +10 -0
- package/features/images/src/domain/library-prompt.mjs +7 -0
- package/features/images/src/library-prompt-page.mjs +8 -76
- package/features/images/src/library-prompt-source.mjs +10 -0
- package/features/images/web/app.js +9 -9
- package/features/images/web/styles.css +219 -219
- package/features/model-slider/DESIGN.md +34 -0
- package/features/model-slider/src/adapters/codex/controller.mjs +93 -0
- package/features/model-slider/src/domain/model-rules.mjs +143 -0
- package/features/model-slider/src/frontend/install.mjs +2110 -0
- package/features/model-slider/src/injection-source.mjs +10 -0
- package/features/notes/DESIGN.md +19 -15
- package/features/notes/README.md +10 -2
- package/features/notes/assets/default-prompts.json +170 -0
- package/features/notes/assets/dsh-prompt-LICENSE.txt +24 -0
- package/features/notes/assets/icons/book-open-nav-fill.svg +1 -0
- package/features/notes/codex-feature.json +5 -1
- package/features/notes/src/backend/default-prompts.mjs +29 -0
- package/features/notes/src/{product.mjs → backend/product.mjs} +3 -1
- package/features/notes/src/{quick-prompts-server.mjs → backend/quick-prompts-server.mjs} +2 -2
- package/features/notes/src/{quick-prompts-page.mjs → frontend/entry.mjs} +78 -266
- package/features/notes/src/frontend/icons.json +13 -0
- package/features/notes/src/frontend/styles.css +110 -0
- package/features/notes/src/main.mjs +2 -2
- package/features/notes/vendor/phosphor-LICENSE +21 -0
- package/features/session-status/DESIGN.md +7 -7
- package/features/session-status/README.md +4 -0
- package/features/session-status/codex-feature.json +1 -1
- package/features/session-status/src/adapters/codex/context-usage.mjs +39 -0
- package/features/session-status/src/adapters/codex/session-log.mjs +81 -0
- package/features/session-status/src/adapters/codex/session-reader.mjs +106 -0
- package/features/session-status/src/domain/session-stats.mjs +141 -0
- package/features/session-status/src/injection-source.mjs +6 -0
- package/features/session-status/src/session-status.mjs +43 -103
- package/features/sidebar-focus/DESIGN.md +6 -5
- package/features/sidebar-focus/codex-feature.json +2 -1
- package/features/sidebar-focus/src/adapters/codex/renderer.mjs +28 -0
- package/features/sidebar-focus/src/injection-source.mjs +6 -0
- package/features/sidebar-focus/src/sidebar-focus.mjs +19 -66
- package/features/sites/DESIGN.md +60 -0
- package/features/sites/README.md +27 -0
- package/features/sites/codex-feature.json +28 -0
- package/features/sites/src/adapters/codex/catalog.mjs +26 -0
- package/features/sites/src/adapters/codex/renderer.mjs +83 -0
- package/features/sites/src/backend/discovery.mjs +73 -0
- package/features/sites/src/backend/project-sites.mjs +34 -0
- package/features/sites/src/backend/site-launcher.mjs +54 -0
- package/features/sites/src/backend/sites-preview.mjs +53 -0
- package/features/sites/src/backend/sites-service.mjs +162 -0
- package/features/sites/src/domain/site.mjs +40 -0
- package/features/sites/src/frontend/entry.mjs +383 -0
- package/features/sites/src/frontend/icons.json +12 -0
- package/features/sites/src/frontend/styles.css +72 -0
- package/features/sites/src/injection-source.mjs +7 -0
- package/features/sites/vendor/lucide-LICENSE +35 -0
- package/features/unread-inbox/DESIGN.md +6 -2
- package/features/unread-inbox/codex-feature.json +2 -1
- package/features/unread-inbox/src/adapters/codex/renderer.mjs +66 -0
- package/features/unread-inbox/src/injection-source.mjs +6 -0
- package/features/unread-inbox/src/unread-inbox.mjs +36 -91
- package/features/usage-meter/DESIGN.md +9 -9
- package/features/usage-meter/README.md +2 -1
- package/features/usage-meter/codex-feature.json +3 -3
- package/features/usage-meter/src/adapters/codex/renderer.mjs +61 -0
- package/features/usage-meter/src/injection-source.mjs +6 -0
- package/features/usage-meter/src/usage-meter.mjs +27 -66
- package/package.json +17 -4
- package/packages/cli/src/config.mjs +2 -1
- package/packages/codex-adapter/README.md +15 -0
- package/packages/codex-adapter/package.json +1 -0
- package/packages/codex-adapter/src/renderer/environment.mjs +53 -0
- package/packages/runtime-host/README.md +27 -0
- package/packages/runtime-host/src/codex-injection.mjs +36 -2393
- package/packages/runtime-host/src/codex-runtime.mjs +21 -3
- package/packages/runtime-host/src/feature-registry.mjs +28 -3
- package/packages/ui/DESIGN.md +31 -0
- package/packages/ui/package.json +8 -0
- package/packages/ui/src/components/button.mjs +21 -0
- package/packages/ui/src/components/composer-menu.mjs +18 -0
- package/packages/ui/src/components/dialog.mjs +55 -0
- package/packages/ui/src/components/navigation-rail.mjs +77 -0
- package/packages/ui/src/components/page.mjs +8 -0
- package/packages/ui/src/components/tooltip.mjs +96 -0
- package/packages/ui/src/foundation/layers.mjs +14 -0
- package/packages/ui/src/foundation/surfaces.mjs +34 -0
- package/packages/ui/src/install.mjs +22 -0
- package/packages/ui/src/native/navigation-rail.mjs +33 -0
- package/packages/ui/src/native/theme.mjs +35 -0
- package/packages/{runtime-host/src/codex-design-contract.mjs → ui/src/native/tokens.mjs} +67 -24
- package/packages/runtime-host/src/cloud-images.mjs +0 -123
- package/packages/runtime-host/src/session-status-reader.mjs +0 -340
- /package/features/notes/src/{prompt-schema.mjs → backend/prompt-schema.mjs} +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export function createThreadAdapter(codex) {
|
|
2
|
+
function isUnread(row) {
|
|
3
|
+
let fiber = codex.currentFiber(row);
|
|
4
|
+
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
5
|
+
if (typeof fiber.memoizedProps?.isUnread === "boolean") return fiber.memoizedProps.isUnread;
|
|
6
|
+
}
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
function isWorking(row) {
|
|
10
|
+
let fiber = codex.currentFiber(row);
|
|
11
|
+
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
12
|
+
const status = fiber.memoizedProps?.statusState;
|
|
13
|
+
if (status?.type) return status.type === "loading";
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function projectForRow(row) {
|
|
18
|
+
let fiber = codex.currentFiber(row);
|
|
19
|
+
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
20
|
+
const group = fiber.memoizedProps?.secondaryContent?.props?.projectGroup;
|
|
21
|
+
if (group?.projectId) return { id:group.projectId, name:group.label };
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function attentionState(row) {
|
|
26
|
+
let fiber = codex.currentFiber(row);
|
|
27
|
+
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
28
|
+
const props = fiber.memoizedProps;
|
|
29
|
+
// 复用原生状态契约,不从摘要文案推断待确认状态。
|
|
30
|
+
if (props?.chips?.some(chip => chip.id === "awaiting-approval")) return "approval";
|
|
31
|
+
if (props?.statusPill?.label?.props?.id === "codex.localTaskRow.needsInput") return "input";
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function threadDetails(row) {
|
|
36
|
+
let fiber = codex.currentFiber(row);
|
|
37
|
+
let summary, hostConfig;
|
|
38
|
+
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
39
|
+
summary ||= fiber.memoizedProps?.threadSummary;
|
|
40
|
+
hostConfig ||= fiber.memoizedProps?.hoverCardHostConfig;
|
|
41
|
+
}
|
|
42
|
+
const hostId = summary?.hostId || row.dataset.appActionSidebarThreadHostId || "local";
|
|
43
|
+
const nativeTime = summary?.recencyAt ?? summary?.updatedAt ?? null;
|
|
44
|
+
return {
|
|
45
|
+
title:row.dataset.appActionSidebarThreadTitle || row.querySelector("[data-thread-title]")?.textContent || "",
|
|
46
|
+
summary:row.querySelector("[data-thread-secondary-title]")?.textContent || "",
|
|
47
|
+
// 与原生 Thread hover card 相同,优先使用任务 recency 时间。
|
|
48
|
+
timestamp:nativeTime,
|
|
49
|
+
hostId, hostName:hostConfig?.display_name || hostId,
|
|
50
|
+
project:projectForRow(row),
|
|
51
|
+
working:isWorking(row),
|
|
52
|
+
attention:attentionState(row),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function threadRecord(row) {
|
|
56
|
+
codex.report("thread-status",Boolean(codex.currentFiber(row)),codex.currentFiber(row) ? null : "thread-props-unavailable");
|
|
57
|
+
let fiber = codex.currentFiber(row);
|
|
58
|
+
let activate = null;
|
|
59
|
+
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
60
|
+
const props = fiber.memoizedProps;
|
|
61
|
+
if (props?.statusState && typeof props.onClick === "function") { activate = props.onClick; break; }
|
|
62
|
+
}
|
|
63
|
+
return { ...threadDetails(row), key:row.dataset.appActionSidebarThreadId, unread:isUnread(row), activate, spinner:row.querySelector?.('[class~="motion-safe:animate-spin"]')?.outerHTML ?? "" };
|
|
64
|
+
}
|
|
65
|
+
return {read:threadRecord};
|
|
66
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createThreadAdapter } from "./adapters/codex/renderer.mjs";
|
|
3
|
+
export async function createPageScriptSource() {
|
|
4
|
+
const source=await readFile(new URL("./unread-inbox.mjs",import.meta.url),"utf8");
|
|
5
|
+
return `const createThreadAdapter=${createThreadAdapter.toString()};\n${source}`;
|
|
6
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
const nativeThreads = createThreadAdapter(config.codex);
|
|
2
|
+
function threadRecord(row) {
|
|
3
|
+
const item=nativeThreads.read(row), previous=messageUpdates.get(item.key);
|
|
4
|
+
if (item.working && (!previous || previous.message !== item.summary || !previous.working))
|
|
5
|
+
messageUpdates.set(item.key,{message:item.summary,working:true,time:Math.max(item.timestamp || 0,Date.now())});
|
|
6
|
+
else if (previous) previous.working=item.working;
|
|
7
|
+
return {...item,timestamp:Math.max(item.timestamp || 0,messageUpdates.get(item.key)?.time || 0) || null};
|
|
8
|
+
}
|
|
1
9
|
// 原生完整侧栏图标,取自当前 Codex 16px/20px 资源。
|
|
2
10
|
const sidebarGlyphs = {"16": "<svg aria-hidden=\"true\" class=\"_Icon_uttcz_1\" focusable=\"false\" height=\"16\" viewBox=\"0 0 16 16\" width=\"16\"><path d=\"M4.66699 4.80859C4.95683 4.80873 5.19238 5.04412 5.19238 5.33398V10.667C5.19238 10.9569 4.95683 11.1923 4.66699 11.1924C4.37704 11.1924 4.1416 10.9569 4.1416 10.667V5.33398C4.1416 5.04403 4.37704 4.80859 4.66699 4.80859Z\" fill=\"currentColor\"></path> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M11.5 2.30762C13.1707 2.30762 14.5254 3.66235 14.5254 5.33301V10.666C14.5254 12.3367 13.1707 13.6914 11.5 13.6914H4.5C2.82934 13.6914 1.47461 12.3367 1.47461 10.666V5.33301C1.47461 3.66235 2.82934 2.30762 4.5 2.30762H11.5ZM4.5 3.3584C3.40924 3.3584 2.52539 4.24225 2.52539 5.33301V10.666C2.52539 11.7568 3.40924 12.6416 4.5 12.6416H11.5C12.5908 12.6416 13.4746 11.7568 13.4746 10.666V5.33301C13.4746 4.24225 12.5908 3.3584 11.5 3.3584H4.5Z\" fill=\"currentColor\"></path></svg>", "20": "<svg aria-hidden=\"true\" class=\"_Icon_uttcz_1\" focusable=\"false\" height=\"20\" viewBox=\"0 0 20 20\" width=\"20\"><path d=\"M5.83301 6.00098C6.20028 6.00098 6.49805 6.29875 6.49805 6.66602V13.333C6.49787 13.7001 6.20017 13.998 5.83301 13.998C5.46596 13.9979 5.16814 13.7 5.16797 13.333V6.66602C5.16797 6.29883 5.46585 6.00111 5.83301 6.00098Z\" fill=\"currentColor\"></path> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M14.5 2.87695C16.5241 2.87695 18.165 4.51787 18.165 6.54199V13.459C18.1649 15.483 16.524 17.124 14.5 17.124H5.5C3.47598 17.124 1.83514 15.483 1.83496 13.459V6.54199C1.83496 4.51787 3.47588 2.87695 5.5 2.87695H14.5ZM5.5 4.20703C4.21042 4.20703 3.16504 5.25241 3.16504 6.54199V13.459C3.16521 14.7484 4.21052 15.7939 5.5 15.7939H14.5C15.7895 15.7939 16.8348 14.7484 16.835 13.459V6.54199C16.835 5.25241 15.7896 4.20703 14.5 4.20703H5.5Z\" fill=\"currentColor\"></path></svg>"};
|
|
3
11
|
const taskGlyph = "<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\"><path d=\"M13.3336 11.4749C13.6234 11.475 13.858 11.7104 13.858 12.0002C13.8579 12.2899 13.6233 12.5255 13.3336 12.5256H6.66663C6.37678 12.5256 6.1414 12.2901 6.14124 12.0002C6.14124 11.7103 6.37668 11.4749 6.66663 11.4749H13.3336Z\" fill=\"currentColor\"/> <path d=\"M3.07874 10.9827C3.50002 10.9827 3.84226 11.3241 3.84241 11.7454C3.84241 12.1667 3.50011 12.509 3.07874 12.509C2.65748 12.5089 2.31604 12.1666 2.31604 11.7454C2.31619 11.3242 2.65758 10.9828 3.07874 10.9827Z\" fill=\"currentColor\"/> <path d=\"M13.3336 7.47485C13.6234 7.47503 13.858 7.7104 13.858 8.00024C13.8579 8.28995 13.6233 8.52546 13.3336 8.52563H6.66663C6.37678 8.52563 6.1414 8.29005 6.14124 8.00024C6.14124 7.71029 6.37668 7.47485 6.66663 7.47485H13.3336Z\" fill=\"currentColor\"/> <path d=\"M3.07874 6.98267C3.50002 6.98267 3.84226 7.32411 3.84241 7.74536C3.84241 8.16674 3.50011 8.50903 3.07874 8.50903C2.65748 8.50888 2.31604 8.16665 2.31604 7.74536C2.31619 7.3242 2.65758 6.98281 3.07874 6.98267Z\" fill=\"currentColor\"/> <path d=\"M13.3336 3.47485C13.6234 3.47503 13.858 3.7104 13.858 4.00024C13.8579 4.28995 13.6233 4.52546 13.3336 4.52563H6.66663C6.37678 4.52563 6.1414 4.29005 6.14124 4.00024C6.14124 3.71029 6.37668 3.47485 6.66663 3.47485H13.3336Z\" fill=\"currentColor\"/> <path d=\"M3.07874 2.98267C3.50002 2.98267 3.84226 3.32411 3.84241 3.74536C3.84241 4.16674 3.50011 4.50903 3.07874 4.50903C2.65748 4.50888 2.31604 4.16665 2.31604 3.74536C2.31619 3.3242 2.65758 2.98281 3.07874 2.98267Z\" fill=\"currentColor\"/></svg>";
|
|
@@ -8,40 +16,41 @@ const folderGlyph = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20
|
|
|
8
16
|
const marker = "data-minecodex-unread";
|
|
9
17
|
const style = document.createElement("style");
|
|
10
18
|
style.textContent = `
|
|
11
|
-
[data-minecodex-task-title] { pointer-events:auto; -webkit-app-region:no-drag; cursor:pointer; border-radius:var(--radius-lg); min-height:32px; padding-inline:6px; }
|
|
12
|
-
[data-minecodex-task-title]:hover { background:var(--color-background-primary-ghost-hover); }
|
|
13
|
-
[data-minecodex-task-title]:focus-visible { outline:2px solid var(--color-ring); outline-offset:-2px; }
|
|
14
|
-
[data-minecodex-task-badge] { display:inline-flex; flex-shrink:0; align-items:center; gap:4px; color:var(--color-text-secondary); font-family:var(--font-sans-default); font-size:var(--text-xs); font-variant-numeric:tabular-nums; position:relative; }
|
|
15
|
-
[data-minecodex-task-badge][data-notification]::after { content:""; position:absolute; width:6px; height:6px; right:-2px; top:-1px; border-radius:50%; background:var(--blue-400,#3A83F7); box-shadow:0 0 0 1.5px var(--color-token-dropdown-background); }
|
|
19
|
+
[data-minecodex-task-title] { pointer-events:auto; -webkit-app-region:no-drag; cursor:pointer; border-radius:var(--mcx-radius-lg); min-height:32px; padding-inline:6px; }
|
|
20
|
+
[data-minecodex-task-title]:hover { background:var(--mcx-color-background-primary-ghost-hover); }
|
|
21
|
+
[data-minecodex-task-title]:focus-visible { outline:2px solid var(--mcx-color-ring); outline-offset:-2px; }
|
|
22
|
+
[data-minecodex-task-badge] { display:inline-flex; flex-shrink:0; align-items:center; gap:4px; color:var(--mcx-color-text-secondary); font-family:var(--mcx-font-sans-default); font-size:var(--mcx-text-xs); font-variant-numeric:tabular-nums; position:relative; }
|
|
23
|
+
[data-minecodex-task-badge][data-notification]::after { content:""; position:absolute; width:6px; height:6px; right:-2px; top:-1px; border-radius:50%; background:var(--blue-400,#3A83F7); box-shadow:0 0 0 1.5px var(--mcx-color-token-dropdown-background); }
|
|
16
24
|
[data-minecodex-task-badge][data-notification=red]::after { background:#FF3B30; }
|
|
17
25
|
[data-minecodex-sidebar-original] { display:none !important; }
|
|
18
26
|
[data-minecodex-unread=trigger] { -webkit-app-region:no-drag; }
|
|
19
|
-
[${marker}=panel] { position:fixed;
|
|
27
|
+
[${marker}=panel] { position:fixed; box-sizing:border-box; width:min(320px,calc(100vw - 16px)); max-height:min(440px,calc(100vh - 64px)); display:flex; flex-direction:column; overflow:hidden; padding:6px 0; border-radius:16px; color:var(--mcx-color-text-primary); font-family:var(--mcx-font-sans-default); font-size:var(--mcx-text-sm); }
|
|
20
28
|
[${marker}=panel][hidden] { display:none; }
|
|
21
|
-
[${marker}=panel] .mcx-thread-list { min-height:0; overflow-y:auto; overscroll-behavior:contain; padding:0 6px; scrollbar-width:thin; scrollbar-color:var(--color-border) transparent; }
|
|
29
|
+
[${marker}=panel] .mcx-thread-list { min-height:0; overflow-y:auto; overscroll-behavior:contain; padding:0 6px; scrollbar-width:thin; scrollbar-color:var(--mcx-color-border) transparent; }
|
|
22
30
|
[${marker}=panel] .mcx-thread-list:hover, [${marker}=panel] .mcx-thread-list:active { scrollbar-color:var(--color-border-strong) transparent; }
|
|
23
|
-
[${marker}=panel] h2 { flex-shrink:0; margin:8px 16px; font-size:var(--text-sm); font-weight:500; color:var(--color-text-tertiary); }
|
|
24
|
-
[${marker}=panel] [data-unread-row] { display:flex; flex-direction:column; gap:4px; width:100%; padding:10px 12px; box-sizing:border-box; border:0; border-radius:10px; background:transparent; text-align:start; color:var(--color-text-primary); font:inherit; cursor:pointer; }
|
|
31
|
+
[${marker}=panel] h2 { flex-shrink:0; margin:8px 16px; font-size:var(--mcx-text-sm); font-weight:500; color:var(--mcx-color-text-tertiary); }
|
|
32
|
+
[${marker}=panel] [data-unread-row] { display:flex; flex-direction:column; gap:4px; width:100%; padding:10px 12px; box-sizing:border-box; border:0; border-radius:10px; background:transparent; text-align:start; color:var(--mcx-color-text-primary); font:inherit; cursor:pointer; }
|
|
25
33
|
[${marker}=panel] [data-unread-row][aria-current=page] .mcx-thread-title::before { content:""; display:inline-block; width:5px; height:5px; margin-inline-end:6px; border-radius:50%; background:currentColor; vertical-align:middle; }
|
|
26
|
-
[${marker}=panel] [data-unread-row]:hover { background:var(--color-background-primary-ghost-hover); }
|
|
27
|
-
[${marker}=panel] [data-unread-row]:focus-visible { outline:2px solid var(--color-ring); outline-offset:-2px; }
|
|
34
|
+
[${marker}=panel] [data-unread-row]:hover { background:var(--mcx-color-background-primary-ghost-hover); }
|
|
35
|
+
[${marker}=panel] [data-unread-row]:focus-visible { outline:2px solid var(--mcx-color-ring); outline-offset:-2px; }
|
|
28
36
|
[${marker}=panel] .mcx-thread-header { display:flex; align-items:center; gap:8px; width:100%; min-width:0; }
|
|
29
37
|
[${marker}=panel] .mcx-thread-title { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; font-size:13px; line-height:18px; font-weight:500; }
|
|
30
|
-
[${marker}=panel] .mcx-device { display:flex; flex-shrink:0; color:var(--color-text-tertiary); }
|
|
31
|
-
[${marker}=panel] .mcx-loading { margin-inline-start:auto; flex-shrink:0; display:flex; align-items:center; color:var(--color-text-tertiary); }
|
|
32
|
-
[${marker}=panel] time { margin-inline-start:auto; flex-shrink:0; font-size:var(--text-xs); color:var(--color-text-tertiary); }
|
|
33
|
-
[${marker}=panel] .mcx-summary { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; line-height:16px; color:var(--color-text-tertiary); }
|
|
34
|
-
[${marker}=panel] .mcx-project { display:flex; align-items:center; gap:6px; padding:0; min-width:0; font-size:11px; line-height:16px; color:var(--color-text-tertiary); font-weight:400; }
|
|
38
|
+
[${marker}=panel] .mcx-device { display:flex; flex-shrink:0; color:var(--mcx-color-text-tertiary); }
|
|
39
|
+
[${marker}=panel] .mcx-loading { margin-inline-start:auto; flex-shrink:0; display:flex; align-items:center; color:var(--mcx-color-text-tertiary); }
|
|
40
|
+
[${marker}=panel] time { margin-inline-start:auto; flex-shrink:0; font-size:var(--mcx-text-xs); color:var(--mcx-color-text-tertiary); }
|
|
41
|
+
[${marker}=panel] .mcx-summary { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; line-height:16px; color:var(--mcx-color-text-tertiary); }
|
|
42
|
+
[${marker}=panel] .mcx-project { display:flex; align-items:center; gap:6px; padding:0; min-width:0; font-size:11px; line-height:16px; color:var(--mcx-color-text-tertiary); font-weight:400; }
|
|
35
43
|
[${marker}=panel] .mcx-project > span:last-child { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
36
44
|
[${marker}=panel] .mcx-project svg, [${marker}=panel] .mcx-project-icon { width:12px; height:12px; flex:none; }
|
|
37
45
|
[${marker}=panel] .mcx-project-icon > * { width:12px; height:12px; }
|
|
38
|
-
[${marker}=panel] .mcx-attention { margin-inline-start:auto; flex:none; max-width:150px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; border-radius:999px; padding:2px 10px 2px 8px; font-size:12px; line-height:16px; color:var(--color-chart-blue); background:color-mix(in srgb,var(--color-chart-blue) 15%,transparent); }
|
|
46
|
+
[${marker}=panel] .mcx-attention { margin-inline-start:auto; flex:none; max-width:150px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; border-radius:999px; padding:2px 10px 2px 8px; font-size:12px; line-height:16px; color:var(--mcx-color-chart-blue); background:color-mix(in srgb,var(--mcx-color-chart-blue) 15%,transparent); }
|
|
39
47
|
[${marker}=panel] .mcx-attention[data-attention="approval"] { color:var(--color-chart-green); background:color-mix(in srgb,var(--color-chart-green) 20%,transparent); }
|
|
40
|
-
[${marker}=panel] .mcx-empty { margin:20px 12px; color:var(--color-text-tertiary); }
|
|
48
|
+
[${marker}=panel] .mcx-empty { margin:20px 12px; color:var(--mcx-color-text-tertiary); }
|
|
41
49
|
`;
|
|
42
50
|
document.head.append(style);
|
|
43
51
|
const panel = document.createElement("div");
|
|
44
52
|
panel.setAttribute(marker, "panel");
|
|
53
|
+
config.ui.surface(panel, "popover");
|
|
45
54
|
panel.id = "minecodex-unread-panel";
|
|
46
55
|
panel.setAttribute("role", "dialog");
|
|
47
56
|
panel.hidden = true;
|
|
@@ -71,31 +80,10 @@ let restoreActivity = false, signature = "", queued = false, disposed = false;
|
|
|
71
80
|
function activityButton() {
|
|
72
81
|
return [...document.querySelectorAll('#app-shell-sidebar button[aria-pressed]')].find(e => /activity|活动|动态/.test(e.getAttribute("aria-label") || ""));
|
|
73
82
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
77
|
-
if (typeof fiber.memoizedProps?.isUnread === "boolean") return fiber.memoizedProps.isUnread;
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
function isWorking(row) {
|
|
82
|
-
let fiber = row[Object.keys(row).find(key => key.startsWith("__reactFiber"))];
|
|
83
|
-
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
84
|
-
const status = fiber.memoizedProps?.statusState;
|
|
85
|
-
if (status?.type) return status.type === "loading";
|
|
86
|
-
}
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
83
|
+
|
|
84
|
+
|
|
89
85
|
// 缓存数据与原生导航动作,不保留会被 React 卸载的会话 DOM。
|
|
90
|
-
|
|
91
|
-
let fiber = row[Object.keys(row).find(key => key.startsWith("__reactFiber"))];
|
|
92
|
-
let activate = null;
|
|
93
|
-
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
94
|
-
const props = fiber.memoizedProps;
|
|
95
|
-
if (props?.statusState && typeof props.onClick === "function") { activate = props.onClick; break; }
|
|
96
|
-
}
|
|
97
|
-
return { ...threadDetails(row), key:row.dataset.appActionSidebarThreadId, unread:isUnread(row), activate, spinner:row.querySelector?.('[class~="motion-safe:animate-spin"]')?.outerHTML ?? "" };
|
|
98
|
-
}
|
|
86
|
+
|
|
99
87
|
function notificationColor(records) {
|
|
100
88
|
return records.some(item => item.attention) ? "red" : records.some(item => item.unread) ? "blue" : null;
|
|
101
89
|
}
|
|
@@ -107,7 +95,7 @@ function selectThreads(records, recent, now = Date.now(), currentKey = null) {
|
|
|
107
95
|
}
|
|
108
96
|
function captureCurrentRow() {
|
|
109
97
|
const selected = document.querySelector('#app-shell-sidebar [data-app-action-sidebar-thread-selected="true"]');
|
|
110
|
-
const id =
|
|
98
|
+
const id = config.codex.currentThreadId();
|
|
111
99
|
selectedKey = selected?.dataset.appActionSidebarThreadId
|
|
112
100
|
?? [...recentRows.keys()].find(key => id && key.endsWith(":" + id)) ?? null;
|
|
113
101
|
if (selected) recentRows.set(selectedKey, threadRecord(selected));
|
|
@@ -116,52 +104,9 @@ function captureCurrentRow() {
|
|
|
116
104
|
openedAt.delete(key); recentRows.delete(key); messageUpdates.delete(key);
|
|
117
105
|
}
|
|
118
106
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const group = fiber.memoizedProps?.secondaryContent?.props?.projectGroup;
|
|
123
|
-
if (group?.projectId) return { id:group.projectId, name:group.label };
|
|
124
|
-
}
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
function attentionState(row) {
|
|
128
|
-
let fiber = row[Object.keys(row).find(key => key.startsWith("__reactFiber"))];
|
|
129
|
-
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
130
|
-
const props = fiber.memoizedProps;
|
|
131
|
-
// 复用原生状态契约,不从摘要文案推断待确认状态。
|
|
132
|
-
if (props?.chips?.some(chip => chip.id === "awaiting-approval")) return "approval";
|
|
133
|
-
if (props?.statusPill?.label?.props?.id === "codex.localTaskRow.needsInput") return "input";
|
|
134
|
-
}
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
function threadDetails(row) {
|
|
138
|
-
let fiber = row[Object.keys(row).find(key => key.startsWith("__reactFiber"))];
|
|
139
|
-
let summary, hostConfig;
|
|
140
|
-
for (let depth = 0; fiber && depth < 35; depth++, fiber = fiber.return) {
|
|
141
|
-
summary ||= fiber.memoizedProps?.threadSummary;
|
|
142
|
-
hostConfig ||= fiber.memoizedProps?.hoverCardHostConfig;
|
|
143
|
-
}
|
|
144
|
-
const hostId = summary?.hostId || row.dataset.appActionSidebarThreadHostId || "local";
|
|
145
|
-
const key = row.dataset.appActionSidebarThreadId;
|
|
146
|
-
const working = isWorking(row);
|
|
147
|
-
const message = row.querySelector("[data-thread-secondary-title]")?.textContent || "";
|
|
148
|
-
const nativeTime = summary?.recencyAt ?? summary?.updatedAt ?? null;
|
|
149
|
-
const previous = messageUpdates.get(key);
|
|
150
|
-
// 原生流式摘要发生变化时推进时间,不因重绘或打开菜单重置消息时间。
|
|
151
|
-
if (working && (!previous || previous.message !== message || !previous.working)) {
|
|
152
|
-
messageUpdates.set(key, {message, working, time:Math.max(nativeTime || 0, Date.now())});
|
|
153
|
-
} else if (previous) previous.working = working;
|
|
154
|
-
return {
|
|
155
|
-
title:row.dataset.appActionSidebarThreadTitle || row.querySelector("[data-thread-title]")?.textContent || "",
|
|
156
|
-
summary:row.querySelector("[data-thread-secondary-title]")?.textContent || "",
|
|
157
|
-
// 与原生 Thread hover card 相同,优先使用任务 recency 时间。
|
|
158
|
-
timestamp:Math.max(nativeTime || 0, messageUpdates.get(key)?.time || 0) || null,
|
|
159
|
-
hostId, hostName:hostConfig?.display_name || hostId,
|
|
160
|
-
project:projectForRow(row),
|
|
161
|
-
working:isWorking(row),
|
|
162
|
-
attention:attentionState(row),
|
|
163
|
-
};
|
|
164
|
-
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
165
110
|
function relativeTime(timestamp, now = Date.now()) {
|
|
166
111
|
if (!Number.isFinite(timestamp)) return "";
|
|
167
112
|
const seconds = Math.max(0, Math.floor((now - timestamp) / 1000));
|
|
@@ -242,7 +187,7 @@ function renderRows(details = activityRecords()) {
|
|
|
242
187
|
device.className = "mcx-device";
|
|
243
188
|
const remote = item.hostId !== "local";
|
|
244
189
|
device.innerHTML = deviceGlyphs[remote ? "remote" : "local"];
|
|
245
|
-
device.
|
|
190
|
+
device.setAttribute("data-minecodex-tooltip", text("Remote", "远程"));
|
|
246
191
|
device.setAttribute("aria-label", device.title);
|
|
247
192
|
header.append(title);
|
|
248
193
|
if (remote) header.append(device);
|
|
@@ -265,7 +210,7 @@ function renderRows(details = activityRecords()) {
|
|
|
265
210
|
const time = document.createElement("time");
|
|
266
211
|
time.dateTime = new Date(item.timestamp).toISOString();
|
|
267
212
|
time.textContent = elapsed;
|
|
268
|
-
time.
|
|
213
|
+
time.setAttribute("data-minecodex-tooltip", new Date(item.timestamp).toLocaleString());
|
|
269
214
|
header.append(time);
|
|
270
215
|
}
|
|
271
216
|
card.append(header);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Usage Meter
|
|
2
2
|
|
|
3
|
+
通用视觉、材质、按钮、Tooltip 与原生 sidebar 层级遵循根 [DESIGN.md](../../DESIGN.md)。本文件只记录功能特有组件、布局、数据语义与通用组件选型。
|
|
4
|
+
|
|
3
5
|
## 用户任务与范围
|
|
4
6
|
|
|
5
|
-
在原生导航栏
|
|
7
|
+
在原生导航栏 Avatar 上方查看当前账号剩余额度,点击直达 Settings → Usage。
|
|
6
8
|
仅向 `nav[data-app-navigation-rail="true"]` 注入一个控件,不注册通用侧栏
|
|
7
9
|
菜单项,不向 Settings 的分类列表插入内容。
|
|
8
10
|
|
|
@@ -24,22 +26,20 @@ primary/secondary 顺序或套餐字符串。剩余百分比为 clamp(100-used_p
|
|
|
24
26
|
|
|
25
27
|
| 角色 | 权威 token / 值 | 用途 |
|
|
26
28
|
| --- | --- | --- |
|
|
27
|
-
| 点击区域 | 克隆
|
|
29
|
+
| 点击区域 | 克隆 Avatar 的原生按钮 class 和布局样式属性;目标 36×36px | 与导航项对齐 |
|
|
28
30
|
| 环图 | 24×24px;viewBox 32;圆心 16;周半径 43/3、五小时半径 10.5;描边 10/3 viewBox units(渲染 2.5px) | 自定义数据图,不是替代导航图标 |
|
|
29
|
-
| 周环 |
|
|
31
|
+
| 周环 | `--color-text-tertiary`(原生中性灰) | 单圈或外圈 |
|
|
30
32
|
| 五小时环 | `--color-text-secondary`(中性灰) | 双圈内圈 |
|
|
31
33
|
| 轨道 | 对应环色 18% alpha | 剩余额度的底轨 |
|
|
32
|
-
| 中央数字 |
|
|
33
|
-
| Tooltip | `--color-background-tooltip` / `--color-text-tooltip` / `--color-border-tooltip` / `--shadow-tooltip` | 与原生 Help Tooltip 一致 |
|
|
34
|
-
| Tooltip 几何 | `--radius-2xl`;5px 12px padding;`--text-sm` / 18px;两列 gap 16px | label 左、数字右对齐 |
|
|
35
|
-
| Tooltip 字体 | `--tooltip-compact-font-weight` / `--tracking-tooltip` | 与原生紧凑提示一致 |
|
|
34
|
+
| 中央数字 | 继承环的中性灰;`--font-sans-default`;`--text-xs` × 2/3(8px),单双圈与三位数均一致;500、tabular-nums、line-height:1 | 优先五小时 |
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
环从顶部顺时针填充。额度 Tooltip 的特殊内容是两列对齐,列间距16px;其通用材质和文字样式遵循根规范。Tooltip 在按钮右侧 2px,纵向居中并约束在窗口内;
|
|
38
37
|
鼠标悬停延迟 350ms,键盘聚焦立即显示,Escape 隐藏。单圈一行 Weekly Limit;
|
|
39
38
|
双圈依次为 5h Limit、Weekly Limit,数值带 %。没有额外标题或说明。
|
|
40
39
|
|
|
41
|
-
原生
|
|
40
|
+
原生 Avatar 点击区域目标为 36px,头像可见直径为 24px;额度外环含描边的可见直径同为 24px,
|
|
42
41
|
数字与环均以按钮几何中心对齐;中性灰取自原生 navbar,不使用彩色强调。
|
|
42
|
+
注入点为头像整行在最近原生纵向容器中的前一项,复用该容器的 8px 间距;不向头像内部横向行插入按钮,不改动原生容器布局。
|
|
43
43
|
单双圈使用同一实现,双圈验收图采用模拟值,不更改账号缓存。减少动画偏好不添加进度过渡。
|
|
44
44
|
|
|
45
45
|
## 跳转与生命周期
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"label": { "en": "Usage Meter", "zh-CN": "额度环" },
|
|
6
6
|
"description": {
|
|
7
|
-
"en": "Show remaining weekly and 5-hour limits
|
|
8
|
-
"zh-CN": "在
|
|
7
|
+
"en": "Show remaining weekly and 5-hour limits above Avatar and open Usage settings.",
|
|
8
|
+
"zh-CN": "在 Avatar 上方显示周与五小时剩余额度,点击进入 Usage 设置。"
|
|
9
9
|
},
|
|
10
|
-
"entry": { "kind": "page-script", "script": "src/usage-meter.mjs" },
|
|
10
|
+
"entry": { "kind": "page-script", "script": "src/usage-meter.mjs", "sourceFactory": "src/injection-source.mjs" },
|
|
11
11
|
"icon": { "viewBox": "0 0 24 24", "markup": "<circle cx=\"12\" cy=\"12\" r=\"9\"></circle><circle cx=\"12\" cy=\"12\" r=\"5\"></circle>" },
|
|
12
12
|
"placement": { "order": 60 }
|
|
13
13
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function createUsageAdapter(signal, codex, invalidate) {
|
|
2
|
+
let client=null,unsubscribe=null;
|
|
3
|
+
function remainingWindows(data) {
|
|
4
|
+
const windows = [data?.rate_limit?.primary_window, data?.rate_limit?.secondary_window];
|
|
5
|
+
const remaining = seconds => {
|
|
6
|
+
const window = windows.find(window => window?.limit_window_seconds === seconds);
|
|
7
|
+
return typeof window?.used_percent === "number" && Number.isFinite(window.used_percent)
|
|
8
|
+
? Math.round(Math.max(0, Math.min(100, 100 - window.used_percent))) : null;
|
|
9
|
+
};
|
|
10
|
+
const weekly = remaining(604800), fiveHour = remaining(18000);
|
|
11
|
+
return weekly == null ? null : { weekly, fiveHour, primary: fiveHour ?? weekly };
|
|
12
|
+
}
|
|
13
|
+
function nativeContext(rail) {
|
|
14
|
+
const result = { auth:null,client:null,router:null };
|
|
15
|
+
codex.props(rail, props => {
|
|
16
|
+
for (const value of [props.auth,props.value]) if (!result.auth && value && typeof value === "object" && "authMethod" in value && "userId" in value && "accountId" in value) result.auth=value;
|
|
17
|
+
for (const value of [props.queryClient,props.client,props.value]) if (!result.client && typeof value?.getQueryCache === "function" && typeof value?.getQueryData === "function") result.client=value;
|
|
18
|
+
const router=props.router ?? props.value?.router;
|
|
19
|
+
if (!result.router && typeof router?.navigate === "function") result.router=router;
|
|
20
|
+
return Boolean(result.auth && result.client && result.router);
|
|
21
|
+
});
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
function currentWindows(context) {
|
|
25
|
+
const { auth, client } = context;
|
|
26
|
+
if (auth?.isLoading || !auth?.userId || !auth?.accountId || !["chatgpt", "chatgptAuthTokens"].includes(auth.authMethod)) return null;
|
|
27
|
+
const data = client?.getQueryData(["rate-limit-status"]);
|
|
28
|
+
// 账号与缓存必须同时一致;旧账号的最后一次响应不能留在导航栏。
|
|
29
|
+
if (!data || data.user_id !== auth.userId || data.account_id !== auth.accountId) return null;
|
|
30
|
+
return remainingWindows(data);
|
|
31
|
+
}
|
|
32
|
+
function findAvatar(rail) {
|
|
33
|
+
const buttons = [...(rail?.querySelectorAll("button") ?? [])];
|
|
34
|
+
return buttons.find(button => /\b(account|profile|avatar|user)\b|账户|帐户|个人资料|头像/i.test(button.getAttribute("aria-label") ?? ""))
|
|
35
|
+
?? buttons.find(button => button.matches('[data-sidebar-destination="builtin:account"]'))
|
|
36
|
+
?? buttons.findLast(button => button.querySelector('img[alt], img[src], [data-avatar]'))
|
|
37
|
+
?? null;
|
|
38
|
+
}
|
|
39
|
+
function avatarAnchor(avatar, rail) {
|
|
40
|
+
// 头像内部是横向行;在最近的原生纵向容器中插入独立一项。
|
|
41
|
+
let anchor = avatar;
|
|
42
|
+
while (anchor.parentElement !== rail && !anchor.parentElement.classList.contains("flex-col")) {
|
|
43
|
+
anchor = anchor.parentElement;
|
|
44
|
+
}
|
|
45
|
+
return anchor;
|
|
46
|
+
}
|
|
47
|
+
function read() {
|
|
48
|
+
const rail=codex.rail(), context=rail ? nativeContext(rail) : {};
|
|
49
|
+
if (client !== context.client) {
|
|
50
|
+
unsubscribe?.(); client=context.client;
|
|
51
|
+
unsubscribe=client?.getQueryCache().subscribe(event=>{ if (event.query?.queryKey?.[0] === "rate-limit-status") invalidate(); });
|
|
52
|
+
}
|
|
53
|
+
const windows=currentWindows(context), avatar=findAvatar(rail);
|
|
54
|
+
const available=Boolean(avatar && context.router && windows);
|
|
55
|
+
codex.report("usage-meter",available,available ? null : "account-quota-or-anchor-unavailable");
|
|
56
|
+
return { rail,avatar,windows,available };
|
|
57
|
+
}
|
|
58
|
+
function open(rail) { const {router}=nativeContext(rail); return router?.navigate("/settings/usage?tab=overview",{state:router.state?.location?.state}); }
|
|
59
|
+
signal.addEventListener("abort",()=>unsubscribe?.(),{once:true});
|
|
60
|
+
return { read,open,anchor:avatarAnchor };
|
|
61
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createUsageAdapter } from "./adapters/codex/renderer.mjs";
|
|
3
|
+
export async function createPageScriptSource() {
|
|
4
|
+
const source=await readFile(new URL("./usage-meter.mjs",import.meta.url),"utf8");
|
|
5
|
+
return `const createUsageAdapter=${createUsageAdapter.toString()};\n${source}`;
|
|
6
|
+
}
|
|
@@ -3,60 +3,26 @@ const MARKER = "data-minecodex-usage-meter";
|
|
|
3
3
|
const TIP_ID = "minecodex-usage-tooltip";
|
|
4
4
|
const STYLE_ID = "minecodex-usage-style";
|
|
5
5
|
let record = null;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
|
|
7
|
+
|
|
8
8
|
let queued = false;
|
|
9
9
|
let hoverTimer = null;
|
|
10
10
|
|
|
11
|
-
function remainingWindows(data) {
|
|
12
|
-
const windows = [data?.rate_limit?.primary_window, data?.rate_limit?.secondary_window];
|
|
13
|
-
const remaining = seconds => {
|
|
14
|
-
const window = windows.find(window => window?.limit_window_seconds === seconds);
|
|
15
|
-
return typeof window?.used_percent === "number" && Number.isFinite(window.used_percent)
|
|
16
|
-
? Math.round(Math.max(0, Math.min(100, 100 - window.used_percent))) : null;
|
|
17
|
-
};
|
|
18
|
-
const weekly = remaining(604800), fiveHour = remaining(18000);
|
|
19
|
-
return weekly == null ? null : { weekly, fiveHour, primary: fiveHour ?? weekly };
|
|
20
|
-
}
|
|
21
11
|
|
|
22
|
-
function nativeContext(rail) {
|
|
23
|
-
const key = Object.keys(rail).find(key => key.startsWith("__reactFiber$"));
|
|
24
|
-
let fiber = key ? rail[key] : null;
|
|
25
|
-
const result = { auth: null, client: null, router: null };
|
|
26
|
-
for (let depth = 0; fiber && depth < 200; depth++, fiber = fiber.return) {
|
|
27
|
-
const props = fiber.memoizedProps;
|
|
28
|
-
if (!props) continue;
|
|
29
|
-
for (const value of [props.auth, props.value]) {
|
|
30
|
-
if (!result.auth && value && typeof value === "object" && "authMethod" in value && "userId" in value && "accountId" in value) result.auth = value;
|
|
31
|
-
}
|
|
32
|
-
for (const value of [props.queryClient, props.client, props.value]) {
|
|
33
|
-
if (!result.client && typeof value?.getQueryCache === "function" && typeof value?.getQueryData === "function") result.client = value;
|
|
34
|
-
}
|
|
35
|
-
const router = props.router ?? props.value?.router;
|
|
36
|
-
if (!result.router && typeof router?.navigate === "function") result.router = router;
|
|
37
|
-
if (result.auth && result.client && result.router) break;
|
|
38
|
-
}
|
|
39
|
-
return result;
|
|
40
|
-
}
|
|
41
12
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const data = client?.getQueryData(["rate-limit-status"]);
|
|
46
|
-
// 账号与缓存必须同时一致;旧账号的最后一次响应不能留在导航栏。
|
|
47
|
-
if (!data || data.user_id !== auth.userId || data.account_id !== auth.accountId) return null;
|
|
48
|
-
return remainingWindows(data);
|
|
49
|
-
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
50
16
|
|
|
51
17
|
const style = document.createElement("style");
|
|
52
18
|
style.id = STYLE_ID;
|
|
53
19
|
style.textContent = `
|
|
54
|
-
[${MARKER}] { position:relative; display:flex; align-items:center; justify-content:center; flex:none; -webkit-app-region:no-drag; }
|
|
20
|
+
[${MARKER}] { position:relative; display:flex; align-items:center; justify-content:center; flex:none; color:var(--mcx-color-text-tertiary); -webkit-app-region:no-drag; }
|
|
55
21
|
[${MARKER}] .mcx-usage-ring { display:block; width:24px; height:24px; overflow:visible; }
|
|
56
|
-
[${MARKER}] .mcx-usage-number { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:inherit; font-family:var(--font-sans-default); font-size:calc(var(--text-xs) * 2 / 3); line-height:1; font-weight:500; letter-spacing:0; font-variant-numeric:tabular-nums; pointer-events:none; }
|
|
22
|
+
[${MARKER}] .mcx-usage-number { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:inherit; font-family:var(--mcx-font-sans-default); font-size:calc(var(--mcx-text-xs) * 2 / 3); line-height:1; font-weight:var(--font-weight-medium,500); letter-spacing:0; font-variant-numeric:tabular-nums; pointer-events:none; }
|
|
57
23
|
[${MARKER}] [data-window="weekly"] { color:inherit; }
|
|
58
|
-
[${MARKER}] [data-window="fiveHour"] { color:var(--color-text-secondary); }
|
|
59
|
-
#${TIP_ID} {
|
|
24
|
+
[${MARKER}] [data-window="fiveHour"] { color:var(--mcx-color-text-secondary); }
|
|
25
|
+
#${TIP_ID} { width:max-content; max-width:calc(100vw - 16px); }
|
|
60
26
|
#${TIP_ID}[hidden] { display:none; }
|
|
61
27
|
#${TIP_ID} dl { display:grid; grid-template-columns:auto auto; gap:2px 16px; margin:0; }
|
|
62
28
|
#${TIP_ID} dt { text-align:left; }
|
|
@@ -132,21 +98,25 @@ function removeControl() {
|
|
|
132
98
|
record = null;
|
|
133
99
|
}
|
|
134
100
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
function mount(avatar, rail) {
|
|
106
|
+
// 克隆 Avatar 热区的外观,不携带原生账号菜单行为。
|
|
107
|
+
const button = avatar.cloneNode(false);
|
|
138
108
|
for (const attribute of [...button.attributes]) {
|
|
139
|
-
if (attribute.name.startsWith("aria-") ||
|
|
109
|
+
if (attribute.name.startsWith("aria-") || ["id", "title", "data-state", "data-sidebar-destination", "data-testid"].includes(attribute.name)) button.removeAttribute(attribute.name);
|
|
140
110
|
}
|
|
141
111
|
button.setAttribute(MARKER, "");
|
|
142
112
|
const tooltip = document.createElement("div");
|
|
143
113
|
tooltip.id = TIP_ID;
|
|
114
|
+
config.ui.tooltipSurface(tooltip);
|
|
144
115
|
tooltip.setAttribute("role", "tooltip");
|
|
145
116
|
tooltip.hidden = true;
|
|
146
|
-
|
|
147
|
-
anchor.after(button);
|
|
117
|
+
nativeUsage.anchor(avatar, rail).before(button);
|
|
148
118
|
document.body.append(tooltip);
|
|
149
|
-
record = { button, tooltip, rail,
|
|
119
|
+
record = { button, tooltip, rail, avatar, signature: null };
|
|
150
120
|
button.addEventListener("pointerenter", () => { hoverTimer = setTimeout(showTooltip, 350); });
|
|
151
121
|
button.addEventListener("pointerleave", hideTooltip);
|
|
152
122
|
button.addEventListener("focus", showTooltip);
|
|
@@ -154,27 +124,17 @@ function mount(help, rail) {
|
|
|
154
124
|
button.addEventListener("keydown", event => { if (event.key === "Escape") { event.stopPropagation(); hideTooltip(); } });
|
|
155
125
|
button.addEventListener("click", () => {
|
|
156
126
|
hideTooltip();
|
|
157
|
-
|
|
158
|
-
router?.navigate("/settings/usage?tab=overview", { state: router.state?.location?.state });
|
|
127
|
+
nativeUsage.open(rail);
|
|
159
128
|
});
|
|
160
129
|
}
|
|
161
130
|
|
|
162
131
|
function sync() {
|
|
163
132
|
queued = false;
|
|
164
133
|
if (signal.aborted) return;
|
|
165
|
-
const rail
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
unsubscribe?.();
|
|
170
|
-
client = context.client;
|
|
171
|
-
unsubscribe = client?.getQueryCache().subscribe(event => {
|
|
172
|
-
if (event.query?.queryKey?.[0] === "rate-limit-status") schedule();
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
const windows = currentWindows(context);
|
|
176
|
-
if (!help || !context.router || !windows) { removeControl(); return; }
|
|
177
|
-
if (record?.help !== help || !record?.button.isConnected) { removeControl(); mount(help, rail); }
|
|
134
|
+
const { rail,avatar,windows,available } = nativeUsage.read();
|
|
135
|
+
if (!available) { removeControl(); return; }
|
|
136
|
+
if (record?.avatar !== avatar || !record?.button.isConnected) { removeControl(); mount(avatar, rail); }
|
|
137
|
+
else if (record.button.nextElementSibling !== nativeUsage.anchor(avatar, rail)) nativeUsage.anchor(avatar, rail).before(record.button);
|
|
178
138
|
const signature = JSON.stringify(windows);
|
|
179
139
|
if (record.signature !== signature) { record.signature = signature; draw(windows); }
|
|
180
140
|
}
|
|
@@ -183,13 +143,14 @@ function schedule() {
|
|
|
183
143
|
queued = true;
|
|
184
144
|
queueMicrotask(sync);
|
|
185
145
|
}
|
|
146
|
+
const nativeUsage = createUsageAdapter(signal, config.codex, schedule);
|
|
186
147
|
const observer = new MutationObserver(schedule);
|
|
187
148
|
observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ["aria-label"] });
|
|
188
149
|
window.addEventListener("resize", positionTooltip);
|
|
189
150
|
window.addEventListener("focus", schedule);
|
|
190
151
|
const cleanup = () => {
|
|
191
152
|
observer.disconnect();
|
|
192
|
-
|
|
153
|
+
|
|
193
154
|
window.removeEventListener("resize", positionTooltip);
|
|
194
155
|
window.removeEventListener("focus", schedule);
|
|
195
156
|
removeControl();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minecodex",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Lightweight, local-first plugins for the Codex desktop app.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,12 +18,14 @@
|
|
|
18
18
|
"packages/cli/src",
|
|
19
19
|
"packages/cli/native/MineCodex.app",
|
|
20
20
|
"packages/runtime-host/src",
|
|
21
|
+
"features/model-slider/src",
|
|
21
22
|
"features/model-slider/assets",
|
|
22
23
|
"features/model-slider/codex-feature.json",
|
|
23
24
|
"features/model-slider/THIRD_PARTY_NOTICES.md",
|
|
24
25
|
"features/model-slider/vendor",
|
|
25
26
|
"features/notes/src",
|
|
26
27
|
"features/notes/assets",
|
|
28
|
+
"features/notes/vendor/phosphor-LICENSE",
|
|
27
29
|
"features/notes/codex-feature.json",
|
|
28
30
|
"features/notes/DESIGN.md",
|
|
29
31
|
"features/notes/README.md",
|
|
@@ -53,21 +55,32 @@
|
|
|
53
55
|
"features/sidebar-focus/src",
|
|
54
56
|
"features/sidebar-focus/codex-feature.json",
|
|
55
57
|
"features/sidebar-focus/DESIGN.md",
|
|
56
|
-
"features/sidebar-focus/vendor/lucide-LICENSE"
|
|
58
|
+
"features/sidebar-focus/vendor/lucide-LICENSE",
|
|
59
|
+
"features/sites/src",
|
|
60
|
+
"features/sites/codex-feature.json",
|
|
61
|
+
"features/sites/DESIGN.md",
|
|
62
|
+
"features/sites/README.md",
|
|
63
|
+
"features/sites/vendor/lucide-LICENSE",
|
|
64
|
+
"packages/ui/src",
|
|
65
|
+
"packages/codex-adapter/src",
|
|
66
|
+
"packages/codex-adapter/package.json",
|
|
67
|
+
"packages/ui/package.json",
|
|
68
|
+
"DESIGN.md"
|
|
57
69
|
],
|
|
58
70
|
"engines": {
|
|
59
71
|
"node": ">=22.5.0"
|
|
60
72
|
},
|
|
61
73
|
"scripts": {
|
|
62
74
|
"start": "node packages/cli/bin/mcx.mjs serve",
|
|
63
|
-
"test": "node --test packages/cli/test/*.test.mjs packages/runtime-host/test/*.test.mjs features/model-slider/test/*.test.mjs features/notes/test/*.test.mjs features/images/test/*.test.mjs features/hide-upsell-banner/test/*.test.mjs features/session-status/test/*.test.mjs features/usage-meter/test/*.test.mjs apps/dashboard/test/*.test.mjs features/unread-inbox/test/*.test.mjs features/sidebar-focus/test/*.test.mjs",
|
|
75
|
+
"test": "node --test packages/cli/test/*.test.mjs packages/runtime-host/test/*.test.mjs features/model-slider/test/*.test.mjs features/notes/test/*.test.mjs features/images/test/*.test.mjs features/hide-upsell-banner/test/*.test.mjs features/session-status/test/*.test.mjs features/usage-meter/test/*.test.mjs apps/dashboard/test/*.test.mjs features/unread-inbox/test/*.test.mjs features/sidebar-focus/test/*.test.mjs features/sites/test/*.test.mjs packages/ui/test/*.test.mjs packages/codex-adapter/test/*.test.mjs",
|
|
64
76
|
"test:cli": "node --test packages/cli/test/*.test.mjs",
|
|
65
77
|
"test:runtime": "node --test packages/runtime-host/test/*.test.mjs",
|
|
66
78
|
"check": "node scripts/check-syntax.mjs",
|
|
67
79
|
"build:service": "scripts/build-service-app.zsh",
|
|
68
80
|
"pack:check": "npm pack --dry-run",
|
|
69
81
|
"pack:audit": "node scripts/audit-package.mjs",
|
|
70
|
-
"prepublishOnly": "npm test && npm run check && npm run pack:audit"
|
|
82
|
+
"prepublishOnly": "npm test && npm run check && npm run pack:audit",
|
|
83
|
+
"test:ui": "node --test packages/ui/test/*.test.mjs"
|
|
71
84
|
},
|
|
72
85
|
"repository": {
|
|
73
86
|
"type": "git",
|