dsh-claude-style 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.en.md +2 -9
- package/README.md +2 -15
- package/docs/STYLE.md +81 -32
- package/docs/architecture.md +6 -7
- package/lib/client.js +881 -106
- package/lib/index.js +41 -3
- package/lib/model-descriptions.json +46 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* - src/overrides/permissions.js
|
|
18
18
|
* - src/overrides/model-brand.js
|
|
19
19
|
* - src/overrides/model-picker.js
|
|
20
|
+
* - src/overrides/hero-menu.js
|
|
21
|
+
* - src/overrides/quick-providers.js
|
|
20
22
|
* - src/overrides/account-footer.js
|
|
21
23
|
* - src/overrides/ban-screen.js
|
|
22
24
|
* - src/overrides/theme-flip.js
|
|
@@ -36,6 +38,7 @@
|
|
|
36
38
|
* - src/styles/components/account-footer.css
|
|
37
39
|
* - src/styles/components/ban-screen.css
|
|
38
40
|
* - src/styles/components/model-picker.css
|
|
41
|
+
* - src/styles/components/hero-menu.css
|
|
39
42
|
* - src/styles/components/footer-takeover.css
|
|
40
43
|
* - src/styles/components/third-party.css
|
|
41
44
|
* - src/styles/components/settings.css
|
|
@@ -220,6 +223,14 @@ window.__ModuleLoader__.load({
|
|
|
220
223
|
var DEFAULT_BAN_LOCALE = BAN_LOCALE_EN
|
|
221
224
|
/** Present while the composer restyle applies to the page currently shown. */
|
|
222
225
|
var COMPOSER_ATTR = 'data-dsh-claude-composer-active'
|
|
226
|
+
/**
|
|
227
|
+
* Stamped on the host's shared menu card while it is the hero row's picker
|
|
228
|
+
* (the workspace chip or the agent-preset seat opened it). The host portals
|
|
229
|
+
* that card to <body> with no marker of its own, so the stylesheet cannot
|
|
230
|
+
* tell it from the host's other menus; src/overrides/hero-menu.js stamps it
|
|
231
|
+
* and components/hero-menu.css switches on this attribute.
|
|
232
|
+
*/
|
|
233
|
+
var HERO_MENU_ATTR = 'data-dsh-claude-hero-menu'
|
|
223
234
|
/**
|
|
224
235
|
* Present while the browser window does NOT hold focus.
|
|
225
236
|
*
|
|
@@ -231,6 +242,16 @@ window.__ModuleLoader__.load({
|
|
|
231
242
|
var WINDOW_BLUR_ATTR = 'data-dsh-window-blur'
|
|
232
243
|
/** Composer surfaces the restyle may cover, in settings order. */
|
|
233
244
|
var COMPOSER_SCOPES = ['off', 'hero', 'conversation', 'all']
|
|
245
|
+
/**
|
|
246
|
+
* How eagerly the skin's popovers open on hover: `off` is click-only,
|
|
247
|
+
* `account` auto-opens the sidebar account popover alone, and `all` adds the
|
|
248
|
+
* permission, model, session-stats and the host's two hero-row pickers.
|
|
249
|
+
*/
|
|
250
|
+
var AUTO_POPOVER_OFF = 'off'
|
|
251
|
+
var AUTO_POPOVER_ACCOUNT = 'account'
|
|
252
|
+
var AUTO_POPOVER_ALL = 'all'
|
|
253
|
+
var AUTO_POPOVER_SCOPES = [AUTO_POPOVER_OFF, AUTO_POPOVER_ACCOUNT, AUTO_POPOVER_ALL]
|
|
254
|
+
var DEFAULT_AUTO_POPOVER = AUTO_POPOVER_ALL
|
|
234
255
|
/** Route the browser half reads and writes preferences through (lib/index.js). */
|
|
235
256
|
var PREFS_ROUTE = '/dsh-claude-style/prefs'
|
|
236
257
|
/** Route that resolves the host OS user once; never polled. */
|
|
@@ -1888,21 +1909,22 @@ window.__ModuleLoader__.load({
|
|
|
1888
1909
|
" content below, roomy type. */",
|
|
1889
1910
|
"body[data-dsh-claude-style][data-dsh-claude-composer-active] .dsh-claude-stats-popover {",
|
|
1890
1911
|
" position: fixed !important;",
|
|
1891
|
-
" z-index:
|
|
1892
|
-
" min-width:
|
|
1912
|
+
" z-index: 99999 !important;",
|
|
1913
|
+
" min-width: 248px !important;",
|
|
1893
1914
|
" max-width: min(380px, calc(100vw - 16px)) !important;",
|
|
1894
1915
|
" box-sizing: border-box !important;",
|
|
1895
|
-
" padding:
|
|
1916
|
+
" padding: 6px !important;",
|
|
1896
1917
|
" background: var(--dsw-alias-bg-overlay, #ffffff) !important;",
|
|
1897
1918
|
" border: 1px solid var(--dsw-alias-border-l1, #e8e6dc) !important;",
|
|
1898
1919
|
" border-radius: 12px !important;",
|
|
1899
1920
|
" box-shadow: 0 8px 30px rgba(20, 20, 19, 0.12), 0 2px 8px rgba(20, 20, 19, 0.06) !important;",
|
|
1900
1921
|
" display: flex !important;",
|
|
1901
1922
|
" flex-direction: column !important;",
|
|
1902
|
-
" gap:
|
|
1923
|
+
" gap: 6px !important;",
|
|
1903
1924
|
" opacity: 0 !important;",
|
|
1904
1925
|
" pointer-events: none !important;",
|
|
1905
1926
|
" transform: translateY(4px) scale(0.98) !important;",
|
|
1927
|
+
" transform-origin: bottom left !important;",
|
|
1906
1928
|
" transition: opacity 0.15s ease, transform 0.15s ease !important;",
|
|
1907
1929
|
" font-family: var(--dsw-font-family) !important;",
|
|
1908
1930
|
"}",
|
|
@@ -1940,8 +1962,9 @@ window.__ModuleLoader__.load({
|
|
|
1940
1962
|
" display: flex !important;",
|
|
1941
1963
|
" flex-direction: column !important;",
|
|
1942
1964
|
" gap: 2px !important;",
|
|
1943
|
-
"
|
|
1944
|
-
"
|
|
1965
|
+
" min-height: 32px !important;",
|
|
1966
|
+
" padding: 2px 7px !important;",
|
|
1967
|
+
" border-radius: 6px !important;",
|
|
1945
1968
|
"}",
|
|
1946
1969
|
"body[data-dsh-claude-style][data-dsh-claude-composer-active] .dsh-claude-stats-popover-item:hover {",
|
|
1947
1970
|
" background: var(--dsh-claude-hover-bg, rgba(0, 0, 0, 0.08)) !important;",
|
|
@@ -2403,7 +2426,7 @@ window.__ModuleLoader__.load({
|
|
|
2403
2426
|
"}",
|
|
2404
2427
|
"body[data-dsh-claude-style] .dsh-claude-perm-popover {",
|
|
2405
2428
|
" position: fixed !important;",
|
|
2406
|
-
" min-width:
|
|
2429
|
+
" min-width: 248px !important;",
|
|
2407
2430
|
" background: var(--dsw-alias-bg-overlay, #ffffff) !important;",
|
|
2408
2431
|
" border: 1px solid var(--dsw-alias-border-l1, #e8e6dc) !important;",
|
|
2409
2432
|
" border-radius: 12px !important;",
|
|
@@ -2527,10 +2550,10 @@ window.__ModuleLoader__.load({
|
|
|
2527
2550
|
" box-shadow: 0 8px 30px rgba(20, 20, 19, 0.12), 0 2px 8px rgba(20, 20, 19, 0.06) !important;",
|
|
2528
2551
|
" padding: 6px !important;",
|
|
2529
2552
|
" box-sizing: border-box !important;",
|
|
2530
|
-
" z-index:
|
|
2553
|
+
" z-index: 99999 !important;",
|
|
2531
2554
|
" display: flex !important;",
|
|
2532
2555
|
" flex-direction: column !important;",
|
|
2533
|
-
" gap:
|
|
2556
|
+
" gap: 6px !important;",
|
|
2534
2557
|
" opacity: 0 !important;",
|
|
2535
2558
|
" pointer-events: none !important;",
|
|
2536
2559
|
" transform: translateY(6px) scale(0.98) !important;",
|
|
@@ -2563,8 +2586,9 @@ window.__ModuleLoader__.load({
|
|
|
2563
2586
|
" strip is the inner row, so the hover plate covers the name and not the divider",
|
|
2564
2587
|
" that follows it. */",
|
|
2565
2588
|
"body[data-dsh-claude-style] .dsh-claude-account-popover-row {",
|
|
2566
|
-
"
|
|
2567
|
-
"
|
|
2589
|
+
" min-height: 32px !important;",
|
|
2590
|
+
" padding: 2px 7px !important;",
|
|
2591
|
+
" border-radius: 6px !important;",
|
|
2568
2592
|
" cursor: pointer !important;",
|
|
2569
2593
|
" transition: background-color 0.12s ease !important;",
|
|
2570
2594
|
"}",
|
|
@@ -3050,7 +3074,11 @@ window.__ModuleLoader__.load({
|
|
|
3050
3074
|
" position: fixed !important;",
|
|
3051
3075
|
" min-width: 248px !important;",
|
|
3052
3076
|
" max-width: min(360px, calc(100vw - 16px)) !important;",
|
|
3053
|
-
"
|
|
3077
|
+
" /* The first level is sized by its content like the second: the cap is only",
|
|
3078
|
+
" the viewport, so a short list does not leave a tall empty card. The level-1",
|
|
3079
|
+
" list scrolls inside the body instead (see the scoped rule below); this",
|
|
3080
|
+
" overflow-y stays for the sub popover, whose whole card scrolls. */",
|
|
3081
|
+
" max-height: calc(100vh - 80px) !important;",
|
|
3054
3082
|
" overflow-y: auto !important;",
|
|
3055
3083
|
" background: var(--dsw-alias-bg-overlay, #ffffff) !important;",
|
|
3056
3084
|
" border: 1px solid var(--dsw-alias-border-l1, #e8e6dc) !important;",
|
|
@@ -3084,17 +3112,35 @@ window.__ModuleLoader__.load({
|
|
|
3084
3112
|
" gap: 3px !important;",
|
|
3085
3113
|
" min-width: 0 !important;",
|
|
3086
3114
|
"}",
|
|
3115
|
+
"/* Level 1 splits into a scrollable list and a pinned footer: the model rows",
|
|
3116
|
+
" scroll under the divider / effort / more-models rows, which never leave the",
|
|
3117
|
+
" viewport edge of the card. Only the first level does this — the sub popover",
|
|
3118
|
+
" keeps scrolling as one card, with its provider labels pinned (below). */",
|
|
3119
|
+
"body[data-dsh-claude-style] .dsh-claude-model-popover:not(.dsh-claude-model-popover-sub) {",
|
|
3120
|
+
" overflow: hidden !important;",
|
|
3121
|
+
"}",
|
|
3122
|
+
"body[data-dsh-claude-style] .dsh-claude-model-popover:not(.dsh-claude-model-popover-sub) .dsh-claude-model-popover-body {",
|
|
3123
|
+
" flex: 1 1 auto !important;",
|
|
3124
|
+
" min-height: 0 !important;",
|
|
3125
|
+
" overflow-y: auto !important;",
|
|
3126
|
+
"}",
|
|
3127
|
+
"body[data-dsh-claude-style] .dsh-claude-model-footer {",
|
|
3128
|
+
" flex: none !important;",
|
|
3129
|
+
" display: flex !important;",
|
|
3130
|
+
" flex-direction: column !important;",
|
|
3131
|
+
" gap: 2px !important;",
|
|
3132
|
+
" min-width: 0 !important;",
|
|
3133
|
+
"}",
|
|
3087
3134
|
"body[data-dsh-claude-style] .dsh-claude-model-status {",
|
|
3088
3135
|
" color: var(--dsw-alias-label-tertiary, #8f8d84) !important;",
|
|
3089
3136
|
" font-size: 12px !important;",
|
|
3090
3137
|
" line-height: 20px !important;",
|
|
3091
3138
|
" padding: 4px 8px !important;",
|
|
3092
3139
|
"}",
|
|
3093
|
-
"/* The sub popover is taller than the first level
|
|
3094
|
-
"
|
|
3095
|
-
"
|
|
3140
|
+
"/* The sub popover is taller than the first level and sized by its content: the",
|
|
3141
|
+
" cap is only the viewport, so a short provider list does not leave a tall empty",
|
|
3142
|
+
" card. Provider labels stay pinned to the top while the list scrolls. */",
|
|
3096
3143
|
"body[data-dsh-claude-style] .dsh-claude-model-popover-sub {",
|
|
3097
|
-
" max-height: min(540px, calc(100vh - 80px)) !important;",
|
|
3098
3144
|
" padding-top: 0 !important;",
|
|
3099
3145
|
"}",
|
|
3100
3146
|
"",
|
|
@@ -3102,7 +3148,9 @@ window.__ModuleLoader__.load({
|
|
|
3102
3148
|
" position: relative !important;",
|
|
3103
3149
|
" display: flex !important;",
|
|
3104
3150
|
" flex-direction: column !important;",
|
|
3105
|
-
"
|
|
3151
|
+
" /* Level 2 rows sit close together: the 3px the body uses between sections",
|
|
3152
|
+
" read as a gap between models of the same provider. */",
|
|
3153
|
+
" gap: 1px !important;",
|
|
3106
3154
|
" min-width: 0 !important;",
|
|
3107
3155
|
"}",
|
|
3108
3156
|
"",
|
|
@@ -3243,12 +3291,42 @@ window.__ModuleLoader__.load({
|
|
|
3243
3291
|
" align-items: center !important;",
|
|
3244
3292
|
" color: var(--dsw-alias-brand-primary, #d97757) !important;",
|
|
3245
3293
|
"}",
|
|
3246
|
-
"
|
|
3294
|
+
"/* The picker's two hairlines share one declaration: the provider header's rule",
|
|
3295
|
+
" and the divider that closes the model list. Same colour, same thickness, same",
|
|
3296
|
+
" insets — the only difference left is that the header's line starts after the",
|
|
3297
|
+
" provider's name, which is the point of that row. */",
|
|
3298
|
+
"body[data-dsh-claude-style] .dsh-claude-model-divider,",
|
|
3299
|
+
"body[data-dsh-claude-style] .dsh-claude-model-rule::after {",
|
|
3247
3300
|
" height: 1px !important;",
|
|
3248
3301
|
" background: var(--dsw-alias-border-l1, #e8e6dc) !important;",
|
|
3302
|
+
"}",
|
|
3303
|
+
"body[data-dsh-claude-style] .dsh-claude-model-divider {",
|
|
3304
|
+
" margin: 2px 4px !important;",
|
|
3305
|
+
" flex: none !important;",
|
|
3306
|
+
"}",
|
|
3307
|
+
"/* The provider header: the name first, then the rule running to the right edge.",
|
|
3308
|
+
" The line is what is left of that row, not a separate full-width rule above it",
|
|
3309
|
+
" — one rule per provider boundary, and nothing more. An empty label leaves the",
|
|
3310
|
+
" bare rule, which is how a provider that needs no naming (the official source)",
|
|
3311
|
+
" is separated. */",
|
|
3312
|
+
"body[data-dsh-claude-style] .dsh-claude-model-rule {",
|
|
3313
|
+
" display: flex !important;",
|
|
3314
|
+
" align-items: center !important;",
|
|
3315
|
+
" gap: 6px !important;",
|
|
3249
3316
|
" margin: 2px 4px !important;",
|
|
3250
3317
|
" flex: none !important;",
|
|
3251
3318
|
"}",
|
|
3319
|
+
"body[data-dsh-claude-style] .dsh-claude-model-rule::after {",
|
|
3320
|
+
" content: \"\" !important;",
|
|
3321
|
+
" flex: 1 !important;",
|
|
3322
|
+
"}",
|
|
3323
|
+
"body[data-dsh-claude-style] .dsh-claude-model-rule-name {",
|
|
3324
|
+
" flex: none !important;",
|
|
3325
|
+
" font-size: 11px !important;",
|
|
3326
|
+
" line-height: 14px !important;",
|
|
3327
|
+
" color: var(--dsw-alias-label-caption, #a6a094) !important;",
|
|
3328
|
+
" letter-spacing: 0.02em !important;",
|
|
3329
|
+
"}",
|
|
3252
3330
|
"/* Level-2 row: label + current value + chevron; hover opens its own level. */",
|
|
3253
3331
|
"body[data-dsh-claude-style] .dsh-claude-model-cell {",
|
|
3254
3332
|
" display: flex !important;",
|
|
@@ -3318,9 +3396,8 @@ window.__ModuleLoader__.load({
|
|
|
3318
3396
|
"}",
|
|
3319
3397
|
"body[data-dsh-claude-style] .dsh-claude-popover-item {",
|
|
3320
3398
|
" width: 100% !important;",
|
|
3321
|
-
" height: 32px !important;",
|
|
3322
3399
|
" min-height: 32px !important;",
|
|
3323
|
-
" padding:
|
|
3400
|
+
" padding: 2px 7px !important;",
|
|
3324
3401
|
" border-radius: 6px !important;",
|
|
3325
3402
|
" border: none !important;",
|
|
3326
3403
|
" background: transparent !important;",
|
|
@@ -3378,12 +3455,156 @@ window.__ModuleLoader__.load({
|
|
|
3378
3455
|
" background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.08)) !important;",
|
|
3379
3456
|
" color: var(--dsw-alias-label-secondary, #787672) !important;",
|
|
3380
3457
|
"}",
|
|
3458
|
+
"/* A provider the catalog no longer offers: its stored row stays checked but is",
|
|
3459
|
+
" muted, so it reads as \"this used to be picked\" and can be unchecked away. */",
|
|
3460
|
+
"body[data-dsh-claude-style] .dsh-claude-popover-item.dsh-claude-model-stale .dsh-claude-popover-item-text,",
|
|
3461
|
+
"body[data-dsh-claude-style] .dsh-claude-popover-item.dsh-claude-model-stale .dsh-claude-popover-item-badge {",
|
|
3462
|
+
" color: var(--dsw-alias-label-tertiary, #8f8d84) !important;",
|
|
3463
|
+
"}",
|
|
3464
|
+
"body[data-dsh-claude-style] .dsh-claude-popover-item.dsh-claude-model-stale .dsh-claude-popover-item-badge {",
|
|
3465
|
+
" background: transparent !important;",
|
|
3466
|
+
"}",
|
|
3381
3467
|
"",
|
|
3382
3468
|
"/* Ensure popover items only display inside the popover */",
|
|
3383
3469
|
"body[data-dsh-claude-style] [class*=\"footArea\"] > .dsh-claude-popover-item {",
|
|
3384
3470
|
" display: none !important;",
|
|
3385
3471
|
"}",
|
|
3386
3472
|
"",
|
|
3473
|
+
"/* @composer-gate */",
|
|
3474
|
+
"/* ---------- Host menu primitive: the hero row's pickers ---------- */",
|
|
3475
|
+
"/* The workspace (directory) chip and the agent-preset seat open the host's",
|
|
3476
|
+
" shared Menu primitive, which portals its card to <body>. The card carries no",
|
|
3477
|
+
" marker of its own, so src/overrides/hero-menu.js stamps the one that belongs",
|
|
3478
|
+
" to this row with HERO_MENU_ATTR and these rules reach it alone — the host's",
|
|
3479
|
+
" other menus keep their own design. The metrics are the skin's popover",
|
|
3480
|
+
" language, the same card / rows / hover plate / accent check as",
|
|
3481
|
+
" components/model-picker.css and components/permissions.css. */",
|
|
3482
|
+
"",
|
|
3483
|
+
"/* Two class-name families meet here. The host's client-ui packages hash as",
|
|
3484
|
+
" `<hash>_<local>`, so their fragments read `_itemName` / `_itemDesc`; the",
|
|
3485
|
+
" shared menu ships inside the web shell and hashes as `_<local>_<hash>_<n>`,",
|
|
3486
|
+
" so its fragments read `_itemWrap_` / `_itemLabel_`. Both are the longest",
|
|
3487
|
+
" stable piece of the class — never the bare local name. */",
|
|
3488
|
+
"",
|
|
3489
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] {",
|
|
3490
|
+
" min-width: 248px !important;",
|
|
3491
|
+
" max-width: min(360px, calc(100vw - 16px)) !important;",
|
|
3492
|
+
" max-height: min(440px, calc(100vh - 96px)) !important;",
|
|
3493
|
+
" padding: 6px !important;",
|
|
3494
|
+
" gap: 6px !important;",
|
|
3495
|
+
" border: 1px solid var(--dsw-alias-border-l1, #e8e6dc) !important;",
|
|
3496
|
+
" border-radius: 12px !important;",
|
|
3497
|
+
" background: var(--dsw-alias-bg-overlay, #ffffff) !important;",
|
|
3498
|
+
" box-shadow: 0 8px 30px rgba(20, 20, 19, 0.12), 0 2px 8px rgba(20, 20, 19, 0.06) !important;",
|
|
3499
|
+
" z-index: 99999 !important;",
|
|
3500
|
+
" font-family: var(--dsw-font-family) !important;",
|
|
3501
|
+
" /* The host mounts the card instead of toggling a `data-open` attribute, so",
|
|
3502
|
+
" the skin's open transition becomes a one-shot animation with the same",
|
|
3503
|
+
" values. Its origin is the edge nearest the trigger: the card sits beside it. */",
|
|
3504
|
+
" transform-origin: bottom left !important;",
|
|
3505
|
+
" animation: dsh-claude-hero-menu-in 0.15s ease !important;",
|
|
3506
|
+
"}",
|
|
3507
|
+
"@keyframes dsh-claude-hero-menu-in {",
|
|
3508
|
+
" from {",
|
|
3509
|
+
" opacity: 0;",
|
|
3510
|
+
" transform: translateY(4px) scale(0.98);",
|
|
3511
|
+
" }",
|
|
3512
|
+
" to {",
|
|
3513
|
+
" opacity: 1;",
|
|
3514
|
+
" transform: none;",
|
|
3515
|
+
" }",
|
|
3516
|
+
"}",
|
|
3517
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [data-dsh-claude-hero-menu] {",
|
|
3518
|
+
" background: #1e1e1d !important;",
|
|
3519
|
+
" border-color: #2e2c29 !important;",
|
|
3520
|
+
" box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3) !important;",
|
|
3521
|
+
"}",
|
|
3522
|
+
"",
|
|
3523
|
+
"/* One row height, one radius, one hover plate for both pickers. */",
|
|
3524
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_itemWrap_\"] > [role=\"menuitem\"] {",
|
|
3525
|
+
" min-height: 32px !important;",
|
|
3526
|
+
" padding: 2px 7px !important;",
|
|
3527
|
+
" gap: 8px !important;",
|
|
3528
|
+
" border-radius: 6px !important;",
|
|
3529
|
+
" font-size: 13px !important;",
|
|
3530
|
+
" line-height: 20px !important;",
|
|
3531
|
+
" color: var(--dsw-alias-label-primary, #141413) !important;",
|
|
3532
|
+
" transition: background-color 0.12s ease !important;",
|
|
3533
|
+
"}",
|
|
3534
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_itemWrap_\"] > [role=\"menuitem\"]:hover:not(:disabled) {",
|
|
3535
|
+
" background: var(--dsh-claude-hover-bg, rgba(0, 0, 0, 0.08)) !important;",
|
|
3536
|
+
"}",
|
|
3537
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_itemWrap_\"] > [role=\"menuitem\"]:disabled {",
|
|
3538
|
+
" opacity: 0.4 !important;",
|
|
3539
|
+
" cursor: not-allowed !important;",
|
|
3540
|
+
"}",
|
|
3541
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_itemIcon_\"] {",
|
|
3542
|
+
" width: 16px !important;",
|
|
3543
|
+
" height: 16px !important;",
|
|
3544
|
+
" color: var(--dsw-alias-label-secondary, #787672) !important;",
|
|
3545
|
+
"}",
|
|
3546
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_itemIcon_\"] svg {",
|
|
3547
|
+
" width: 16px !important;",
|
|
3548
|
+
" height: 16px !important;",
|
|
3549
|
+
"}",
|
|
3550
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_itemLabel_\"] {",
|
|
3551
|
+
" font-size: 13px !important;",
|
|
3552
|
+
" line-height: 20px !important;",
|
|
3553
|
+
"}",
|
|
3554
|
+
"/* The current row is marked by the accent, as in the model picker. */",
|
|
3555
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_check_\"] {",
|
|
3556
|
+
" flex: none !important;",
|
|
3557
|
+
" color: var(--dsw-alias-brand-primary, #d97757) !important;",
|
|
3558
|
+
"}",
|
|
3559
|
+
"",
|
|
3560
|
+
"/* The preset rows are two lines (name + description) and the workspace rows",
|
|
3561
|
+
" one with an icon, so the height above is a floor rather than a cap. */",
|
|
3562
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_itemName\"] {",
|
|
3563
|
+
" font-size: 13px !important;",
|
|
3564
|
+
" font-weight: 500 !important;",
|
|
3565
|
+
" line-height: 16px !important;",
|
|
3566
|
+
"}",
|
|
3567
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_itemDesc\"] {",
|
|
3568
|
+
" font-size: 11px !important;",
|
|
3569
|
+
" line-height: 14px !important;",
|
|
3570
|
+
" color: var(--dsw-alias-label-tertiary, #8f8d84) !important;",
|
|
3571
|
+
"}",
|
|
3572
|
+
"",
|
|
3573
|
+
"/* Heading and separator rows. */",
|
|
3574
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] > [class*=\"_viewport_\"] > [role=\"presentation\"] {",
|
|
3575
|
+
" padding: 6px 7px 2px !important;",
|
|
3576
|
+
" font-size: 11px !important;",
|
|
3577
|
+
" font-weight: 600 !important;",
|
|
3578
|
+
" line-height: 16px !important;",
|
|
3579
|
+
" letter-spacing: 0.04em !important;",
|
|
3580
|
+
" text-transform: uppercase !important;",
|
|
3581
|
+
" color: var(--dsw-alias-label-tertiary, #8f8d84) !important;",
|
|
3582
|
+
"}",
|
|
3583
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [role=\"separator\"] {",
|
|
3584
|
+
" height: 1px !important;",
|
|
3585
|
+
" margin: 2px 4px !important;",
|
|
3586
|
+
" background: var(--dsw-alias-border-l1, #e8e6dc) !important;",
|
|
3587
|
+
"}",
|
|
3588
|
+
"",
|
|
3589
|
+
"/* The pinned footer (\"添加工作区…\") keeps the host's spacing with the skin's",
|
|
3590
|
+
" hairline. */",
|
|
3591
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] > [class*=\"_footer_\"] {",
|
|
3592
|
+
" margin-top: 4px !important;",
|
|
3593
|
+
" padding-top: 4px !important;",
|
|
3594
|
+
" border-top: 1px solid var(--dsw-alias-border-l1, #e8e6dc) !important;",
|
|
3595
|
+
"}",
|
|
3596
|
+
"",
|
|
3597
|
+
"/* The card's padding is the skin's 6px rather than the host's 4px, so a side",
|
|
3598
|
+
" card moves with it: 6px pad + 6px gap. */",
|
|
3599
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_submenu_\"] {",
|
|
3600
|
+
" bottom: -6px !important;",
|
|
3601
|
+
" left: calc(100% + 12px) !important;",
|
|
3602
|
+
"}",
|
|
3603
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-hero-menu] [class*=\"_submenu_\"]::before {",
|
|
3604
|
+
" left: -12px !important;",
|
|
3605
|
+
" width: 12px !important;",
|
|
3606
|
+
"}",
|
|
3607
|
+
"",
|
|
3387
3608
|
"/* ---------- Footer takeover ----------",
|
|
3388
3609
|
" Everything in this block modifies the HOST's own footer (it hides entries and",
|
|
3389
3610
|
" zeroes the containers that host them) and exists only because the skin",
|
|
@@ -3715,6 +3936,32 @@ window.__ModuleLoader__.load({
|
|
|
3715
3936
|
" color: var(--dsw-alias-label-caption);",
|
|
3716
3937
|
"}",
|
|
3717
3938
|
"",
|
|
3939
|
+
"/* The quick-provider trigger belongs to the same control family as the username",
|
|
3940
|
+
" input: one bordered plate that opens a picker. Its card is the shared popover",
|
|
3941
|
+
" card (components/permissions.css), so only the trigger is styled here. */",
|
|
3942
|
+
"body[data-dsh-claude-style] .dsh-claude-settings-picker {",
|
|
3943
|
+
" flex: none;",
|
|
3944
|
+
" min-width: 140px;",
|
|
3945
|
+
" max-width: 45%;",
|
|
3946
|
+
" box-sizing: border-box;",
|
|
3947
|
+
" border: 1px solid var(--dsw-alias-border-l1);",
|
|
3948
|
+
" border-radius: 8px;",
|
|
3949
|
+
" background: var(--dsw-alias-bg-layer-1);",
|
|
3950
|
+
" color: var(--dsw-alias-label-primary);",
|
|
3951
|
+
" padding: 6px 10px;",
|
|
3952
|
+
" font: inherit;",
|
|
3953
|
+
" font-size: 13px;",
|
|
3954
|
+
" line-height: 18px;",
|
|
3955
|
+
" text-align: left;",
|
|
3956
|
+
" cursor: pointer;",
|
|
3957
|
+
"}",
|
|
3958
|
+
"body[data-dsh-claude-style] .dsh-claude-settings-picker:hover {",
|
|
3959
|
+
" border-color: var(--dsw-alias-border-l2);",
|
|
3960
|
+
"}",
|
|
3961
|
+
"body[data-dsh-claude-style] .dsh-claude-settings-picker[aria-expanded=\"true\"] {",
|
|
3962
|
+
" border-color: var(--dsw-alias-brand-primary, #d97757);",
|
|
3963
|
+
"}",
|
|
3964
|
+
"",
|
|
3718
3965
|
"/* ============================================================================",
|
|
3719
3966
|
" 主题翻转:瞬时抑制过渡 (Theme Flip: Transition Suppression)",
|
|
3720
3967
|
" ============================================================================ */",
|
|
@@ -3968,8 +4215,10 @@ window.__ModuleLoader__.load({
|
|
|
3968
4215
|
var prefs = {
|
|
3969
4216
|
brand: DEFAULT_BRAND,
|
|
3970
4217
|
collapseFooter: true,
|
|
3971
|
-
autoPopover:
|
|
4218
|
+
autoPopover: DEFAULT_AUTO_POPOVER,
|
|
3972
4219
|
composerScope: 'all',
|
|
4220
|
+
modelPicker: true,
|
|
4221
|
+
quickProviders: [],
|
|
3973
4222
|
username: '',
|
|
3974
4223
|
banLocale: fallbackBanLocale || DEFAULT_BAN_LOCALE,
|
|
3975
4224
|
}
|
|
@@ -4066,14 +4315,46 @@ window.__ModuleLoader__.load({
|
|
|
4066
4315
|
} catch (error) { /* no fetch: defaults stay */ }
|
|
4067
4316
|
}
|
|
4068
4317
|
|
|
4318
|
+
/**
|
|
4319
|
+
* Clamp the hover-open preference. It used to be a boolean, and a value
|
|
4320
|
+
* stored in that shape still has to land on a scope: `true` meant every
|
|
4321
|
+
* popover, `false` meant click-only.
|
|
4322
|
+
*/
|
|
4323
|
+
function normalizeAutoPopover(value) {
|
|
4324
|
+
if (value === true) return AUTO_POPOVER_ALL
|
|
4325
|
+
if (value === false) return AUTO_POPOVER_OFF
|
|
4326
|
+
return AUTO_POPOVER_SCOPES.indexOf(value) === -1 ? DEFAULT_AUTO_POPOVER : value
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
/**
|
|
4330
|
+
* The provider ids the picker's first level carries. Ids rather than names:
|
|
4331
|
+
* a provider can be renamed by the catalog at any time, and the stored
|
|
4332
|
+
* selection has to survive that. Order is the caller's, duplicates dropped.
|
|
4333
|
+
* The official service is the picker's default, not a choice, so a stored
|
|
4334
|
+
* id for it is dropped: the first level shows it whenever nothing else is
|
|
4335
|
+
* picked, which is what "default" means.
|
|
4336
|
+
*/
|
|
4337
|
+
function normalizeQuickProviders(value) {
|
|
4338
|
+
if (!Array.isArray(value)) return []
|
|
4339
|
+
var out = []
|
|
4340
|
+
for (var i = 0; i < value.length; i++) {
|
|
4341
|
+
var id = value[i]
|
|
4342
|
+
if (typeof id !== 'string' || id === '' || id === MODEL_OFFICIAL_GROUP || out.indexOf(id) !== -1) continue
|
|
4343
|
+
out.push(id)
|
|
4344
|
+
}
|
|
4345
|
+
return out
|
|
4346
|
+
}
|
|
4347
|
+
|
|
4069
4348
|
/** Clamp one host value into the preference shape (the host already did this). */
|
|
4070
4349
|
function normalizePrefs(value) {
|
|
4071
4350
|
var section = value && typeof value === 'object' ? value : {}
|
|
4072
4351
|
return {
|
|
4073
4352
|
brand: section.brand === BRAND_ANTHROPIC || section.brand === BRAND_OFF ? section.brand : BRAND_CLAUDE,
|
|
4074
4353
|
collapseFooter: section.collapseFooter !== false,
|
|
4075
|
-
autoPopover: section.autoPopover
|
|
4354
|
+
autoPopover: normalizeAutoPopover(section.autoPopover),
|
|
4076
4355
|
composerScope: COMPOSER_SCOPES.indexOf(section.composerScope) === -1 ? 'all' : section.composerScope,
|
|
4356
|
+
modelPicker: section.modelPicker !== false,
|
|
4357
|
+
quickProviders: normalizeQuickProviders(section.quickProviders),
|
|
4077
4358
|
username: (typeof section.username === 'string' ? section.username.trim().slice(0, USERNAME_MAX) : '') || fallbackUsername,
|
|
4078
4359
|
banLocale: resolveBanLocale(section.banLocale),
|
|
4079
4360
|
}
|
|
@@ -4151,19 +4432,6 @@ window.__ModuleLoader__.load({
|
|
|
4151
4432
|
})
|
|
4152
4433
|
}
|
|
4153
4434
|
|
|
4154
|
-
/**
|
|
4155
|
-
* The brand to show. `off` leaves the brand area entirely to the host, so
|
|
4156
|
-
* the stylesheet matches neither brand variant for it.
|
|
4157
|
-
*
|
|
4158
|
-
* @param brand - the stored choice.
|
|
4159
|
-
* @returns the brand actually applied.
|
|
4160
|
-
*/
|
|
4161
|
-
function applyBrand(brand) {
|
|
4162
|
-
var next = brand === BRAND_ANTHROPIC || brand === BRAND_OFF ? brand : BRAND_CLAUDE
|
|
4163
|
-
document.body.setAttribute(BRAND_ATTR, next)
|
|
4164
|
-
return next
|
|
4165
|
-
}
|
|
4166
|
-
|
|
4167
4435
|
/**
|
|
4168
4436
|
* Model & settings localized copy.
|
|
4169
4437
|
*
|
|
@@ -4326,9 +4594,13 @@ window.__ModuleLoader__.load({
|
|
|
4326
4594
|
* the picker copy, so the page follows the shell language too — and the
|
|
4327
4595
|
* English constants stay as the fallback for a failed fetch.
|
|
4328
4596
|
*/
|
|
4329
|
-
function settingsCopy(key, fallback) {
|
|
4597
|
+
function settingsCopy(key, fallback, params) {
|
|
4330
4598
|
var text = modelCopy === null || !modelCopy.settings ? '' : localized(modelCopy.settings[key])
|
|
4331
|
-
|
|
4599
|
+
if (!text) text = fallback
|
|
4600
|
+
if (!params) return text
|
|
4601
|
+
return text.replace(/\{(\w+)\}/g, function (match, name) {
|
|
4602
|
+
return Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match
|
|
4603
|
+
})
|
|
4332
4604
|
}
|
|
4333
4605
|
|
|
4334
4606
|
/**
|
|
@@ -4730,10 +5002,10 @@ window.__ModuleLoader__.load({
|
|
|
4730
5002
|
permHoverIntent = createHoverIntent(openPerm, closePermMenu, 150)
|
|
4731
5003
|
|
|
4732
5004
|
btn.addEventListener('mouseenter', function () {
|
|
4733
|
-
if (readPrefs().autoPopover) openPerm()
|
|
5005
|
+
if (readPrefs().autoPopover === AUTO_POPOVER_ALL) openPerm()
|
|
4734
5006
|
})
|
|
4735
5007
|
btn.addEventListener('mouseleave', function () {
|
|
4736
|
-
if (readPrefs().autoPopover) permHoverIntent.scheduleClose()
|
|
5008
|
+
if (readPrefs().autoPopover === AUTO_POPOVER_ALL) permHoverIntent.scheduleClose()
|
|
4737
5009
|
})
|
|
4738
5010
|
popover.addEventListener('mouseenter', function () {
|
|
4739
5011
|
permHoverIntent.cancel()
|
|
@@ -5240,16 +5512,18 @@ window.__ModuleLoader__.load({
|
|
|
5240
5512
|
function bindStatsHover(root) {
|
|
5241
5513
|
if (root.__dshStatsHoverBound) return
|
|
5242
5514
|
root.__dshStatsHoverBound = true
|
|
5243
|
-
// Gated like the account, model and permission popovers
|
|
5244
|
-
//
|
|
5245
|
-
//
|
|
5246
|
-
//
|
|
5247
|
-
//
|
|
5515
|
+
// Gated like the account, model and permission popovers — and on the
|
|
5516
|
+
// strictest scope: the stats card is not a picker, so it only auto-opens
|
|
5517
|
+
// under "All". The setting promises "hover opens these; off makes them
|
|
5518
|
+
// click-to-open", and the stats popover was the one that opened on hover
|
|
5519
|
+
// no matter what — so it ignored the switch. With the setting off, a
|
|
5520
|
+
// click still reaches the host's own stats dialog, which is exactly what
|
|
5521
|
+
// that button is for.
|
|
5248
5522
|
root.addEventListener('mouseenter', function () {
|
|
5249
|
-
if (readPrefs().autoPopover) showStatsPopover(root)
|
|
5523
|
+
if (readPrefs().autoPopover === AUTO_POPOVER_ALL) showStatsPopover(root)
|
|
5250
5524
|
})
|
|
5251
5525
|
root.addEventListener('mouseleave', function () {
|
|
5252
|
-
if (readPrefs().autoPopover) scheduleHideStatsPopover()
|
|
5526
|
+
if (readPrefs().autoPopover === AUTO_POPOVER_ALL) scheduleHideStatsPopover()
|
|
5253
5527
|
})
|
|
5254
5528
|
}
|
|
5255
5529
|
|
|
@@ -5296,7 +5570,8 @@ window.__ModuleLoader__.load({
|
|
|
5296
5570
|
syncSegments()
|
|
5297
5571
|
syncChatTabComposer()
|
|
5298
5572
|
},
|
|
5299
|
-
closeMenu: closePermMenu
|
|
5573
|
+
closeMenu: closePermMenu,
|
|
5574
|
+
closeStats: hideStatsPopover
|
|
5300
5575
|
}
|
|
5301
5576
|
|
|
5302
5577
|
return function () {
|
|
@@ -5326,15 +5601,6 @@ window.__ModuleLoader__.load({
|
|
|
5326
5601
|
}
|
|
5327
5602
|
}
|
|
5328
5603
|
|
|
5329
|
-
// 从 model-picker.js 抽出(体量停止线:该文件已达 737/750 行)。碎片共享一个
|
|
5330
|
-
// 工厂作用域,所以这里能直接看到 context/model-copy.js 的 modelCopy 与构建生成
|
|
5331
|
-
// 的 COMBINE_SVGS / COMBINE_WORDS,
|
|
5332
|
-
// 不需要 import;函数声明会提升,所以本文件在 build.mjs 的 FRAGMENTS 里的位置
|
|
5333
|
-
// 不影响调用。
|
|
5334
|
-
//
|
|
5335
|
-
// modelEl 是行构建器共用的元素工厂,随它的消费者一起搬过来(原先与这些函数同
|
|
5336
|
-
// 在 installModelPicker 内)。
|
|
5337
|
-
|
|
5338
5604
|
function modelEl(tag, cls, text) {
|
|
5339
5605
|
var el = document.createElement(tag)
|
|
5340
5606
|
if (cls) el.className = cls
|
|
@@ -5353,11 +5619,10 @@ window.__ModuleLoader__.load({
|
|
|
5353
5619
|
* model it did not make (an OpenCode row wearing OpenCode's mark beside
|
|
5354
5620
|
* "LongCat-2.0"), which reads as a wrong answer rather than a missing one.
|
|
5355
5621
|
*
|
|
5356
|
-
* @param groupId - provider route id (unused; kept for call-site symmetry).
|
|
5357
5622
|
* @param modelId - catalog model id.
|
|
5358
5623
|
* @returns the vendored lockup's id, or null when no rule claims it.
|
|
5359
5624
|
*/
|
|
5360
|
-
function modelBrand(
|
|
5625
|
+
function modelBrand(modelId) {
|
|
5361
5626
|
if (modelCopy !== null) {
|
|
5362
5627
|
var id = String(modelId === void 0 || modelId === null ? '' : modelId).toLowerCase()
|
|
5363
5628
|
for (var i = 0; i < modelCopy.brandRules.length; i++) {
|
|
@@ -5438,6 +5703,7 @@ window.__ModuleLoader__.load({
|
|
|
5438
5703
|
var modelPop = null
|
|
5439
5704
|
var modelSubPop = null
|
|
5440
5705
|
var modelBody = null
|
|
5706
|
+
var modelFooter = null
|
|
5441
5707
|
var modelSubBody = null
|
|
5442
5708
|
var modelHoverIntent = createHoverIntent(openModelPopover, closeModelPopovers, 180)
|
|
5443
5709
|
var modelDir = null
|
|
@@ -5447,6 +5713,8 @@ window.__ModuleLoader__.load({
|
|
|
5447
5713
|
var modelSubKind = null
|
|
5448
5714
|
var modelBodySig = ''
|
|
5449
5715
|
var modelSubSig = ''
|
|
5716
|
+
/** Settings-page listeners waiting on the provider list. */
|
|
5717
|
+
var providerListeners = []
|
|
5450
5718
|
|
|
5451
5719
|
/** Exact entry: `provider/model`, bare id, folded id, then the alias table. */
|
|
5452
5720
|
function exactModelCopy(groupId, modelId) {
|
|
@@ -5597,7 +5865,7 @@ window.__ModuleLoader__.load({
|
|
|
5597
5865
|
var store = modelDir.store
|
|
5598
5866
|
if (store && typeof store.subscribe === 'function') {
|
|
5599
5867
|
try {
|
|
5600
|
-
modelSub = store.subscribe(function () { if (ui.schedule) ui.schedule() })
|
|
5868
|
+
modelSub = store.subscribe(function () { notifyProviders(); if (ui.schedule) ui.schedule() })
|
|
5601
5869
|
} catch (error) {
|
|
5602
5870
|
modelSub = null
|
|
5603
5871
|
}
|
|
@@ -5611,6 +5879,37 @@ window.__ModuleLoader__.load({
|
|
|
5611
5879
|
try { return modelDir.store.getSnapshot() } catch (error) { return null }
|
|
5612
5880
|
}
|
|
5613
5881
|
|
|
5882
|
+
/**
|
|
5883
|
+
* The catalog's providers, in catalog order, each with its model count.
|
|
5884
|
+
*
|
|
5885
|
+
* The settings page's quick-provider picker is the other consumer, and it
|
|
5886
|
+
* may be opened before the picker itself ever was — so this resolves the
|
|
5887
|
+
* directory and starts the shared catalog load rather than requiring a
|
|
5888
|
+
* first popover open.
|
|
5889
|
+
*/
|
|
5890
|
+
function modelProviders() {
|
|
5891
|
+
modelDirectory()
|
|
5892
|
+
warmModelCatalog()
|
|
5893
|
+
var snap = modelSnapshot()
|
|
5894
|
+
var groups = (snap && snap.groups) || []
|
|
5895
|
+
var out = []
|
|
5896
|
+
for (var i = 0; i < groups.length; i++) {
|
|
5897
|
+
if (groups[i].models.length === 0) continue
|
|
5898
|
+
out.push({ id: groups[i].id, name: groups[i].name || groups[i].id, count: groups[i].models.length })
|
|
5899
|
+
}
|
|
5900
|
+
return out
|
|
5901
|
+
}
|
|
5902
|
+
|
|
5903
|
+
/** Tell the settings picker the provider list moved (catalog arrived, changed). */
|
|
5904
|
+
function notifyProviders() {
|
|
5905
|
+
if (providerListeners.length === 0) return
|
|
5906
|
+
var list = modelProviders()
|
|
5907
|
+
var listeners = providerListeners.slice()
|
|
5908
|
+
for (var i = 0; i < listeners.length; i++) {
|
|
5909
|
+
try { listeners[i](list) } catch (error) { /* one listener must not block the rest */ }
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
|
|
5614
5913
|
/**
|
|
5615
5914
|
* Start the host's model catalog load as soon as a session resolves, rather
|
|
5616
5915
|
* than waiting for the first popover open.
|
|
@@ -5692,13 +5991,32 @@ window.__ModuleLoader__.load({
|
|
|
5692
5991
|
var MODEL_CHECK_SVG = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8.5l3.2 3.2L13 5"/></svg>'
|
|
5693
5992
|
var MODEL_CHEVRON_SVG = '<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4l4 4-4 4"/></svg>'
|
|
5694
5993
|
|
|
5994
|
+
/** Catalog order is whatever the provider happened to send; id order is scannable. */
|
|
5995
|
+
function byModelId(a, b) {
|
|
5996
|
+
var left = String(a.id)
|
|
5997
|
+
var right = String(b.id)
|
|
5998
|
+
return left < right ? -1 : left > right ? 1 : 0
|
|
5999
|
+
}
|
|
6000
|
+
|
|
6001
|
+
/**
|
|
6002
|
+
* The rule that separates one provider's models from the next. The provider
|
|
6003
|
+
* name rides the rule itself rather than trailing the model in parentheses:
|
|
6004
|
+
* one quiet line above the group says who serves it, and the model names
|
|
6005
|
+
* stay clean.
|
|
6006
|
+
*/
|
|
6007
|
+
function buildProviderRule(name) {
|
|
6008
|
+
var rule = modelEl('div', 'dsh-claude-model-rule')
|
|
6009
|
+
if (name) rule.appendChild(modelEl('span', 'dsh-claude-model-rule-name', name))
|
|
6010
|
+
return rule
|
|
6011
|
+
}
|
|
6012
|
+
|
|
5695
6013
|
/** One selectable model row: brand mark, name, optional description line and a check when current. */
|
|
5696
6014
|
function buildModelOption(group, model, selected, withDescription) {
|
|
5697
6015
|
var item = modelEl('button', 'dsh-claude-model-option')
|
|
5698
6016
|
item.type = 'button'
|
|
5699
6017
|
item.setAttribute('role', 'menuitemradio')
|
|
5700
6018
|
item.setAttribute('aria-checked', selected ? 'true' : 'false')
|
|
5701
|
-
var brand = modelBrand(
|
|
6019
|
+
var brand = modelBrand(model.id)
|
|
5702
6020
|
// The brand id is the row's styling hook — it is what gives a vendor's rows
|
|
5703
6021
|
// their own typography (see .dsh-claude-model-name in
|
|
5704
6022
|
// styles/components/model-picker.css). The vendor's mark is no longer drawn
|
|
@@ -5739,7 +6057,7 @@ window.__ModuleLoader__.load({
|
|
|
5739
6057
|
cell.appendChild(chevron)
|
|
5740
6058
|
cell.addEventListener('mouseenter', (function (k) {
|
|
5741
6059
|
return function () {
|
|
5742
|
-
if (readPrefs().autoPopover) openModelSub(k)
|
|
6060
|
+
if (readPrefs().autoPopover === AUTO_POPOVER_ALL) openModelSub(k)
|
|
5743
6061
|
}
|
|
5744
6062
|
})(kind))
|
|
5745
6063
|
cell.addEventListener('click', (function (k) {
|
|
@@ -5785,60 +6103,81 @@ window.__ModuleLoader__.load({
|
|
|
5785
6103
|
var groups = (snap && snap.groups) || []
|
|
5786
6104
|
var current = modelCurrent(snap)
|
|
5787
6105
|
var effort = modelEffort(snap)
|
|
5788
|
-
var sig = [status, activeLocale(), current ? current.group.id + '/' + current.model.id : '', effort ? String(effort.effective) : ''].join('|')
|
|
6106
|
+
var sig = [status, activeLocale(), current ? current.group.id + '/' + current.model.id : '', effort ? String(effort.effective) : '', readPrefs().quickProviders.join(',')].join('|')
|
|
5789
6107
|
for (var g = 0; g < groups.length; g++) sig += ';' + groups[g].id + ':' + groups[g].models.length
|
|
5790
6108
|
if (sig === modelBodySig) return
|
|
5791
6109
|
modelBodySig = sig
|
|
5792
6110
|
while (modelBody.firstChild) modelBody.removeChild(modelBody.firstChild)
|
|
6111
|
+
while (modelFooter && modelFooter.firstChild) modelFooter.removeChild(modelFooter.firstChild)
|
|
5793
6112
|
|
|
5794
6113
|
if (status === 'idle' || status === 'loading' || status === 'selecting') {
|
|
5795
6114
|
modelBody.appendChild(modelEl('div', 'dsh-claude-model-status', copyLabel('loading', MODEL_LOADING_LABEL)))
|
|
5796
6115
|
} else {
|
|
5797
|
-
|
|
6116
|
+
// Level 1 always leads with the official service: it is the default
|
|
6117
|
+
// provider, not a choice in the settings, so it stays first no matter
|
|
6118
|
+
// what else is picked. The quick providers the settings page picked
|
|
6119
|
+
// follow it, each under its own rule. Only when the catalog has no
|
|
6120
|
+
// (non-empty) official service at all does the list fall back to the
|
|
6121
|
+
// picked providers, and with none picked to every provider.
|
|
6122
|
+
var sections = []
|
|
6123
|
+
var chosen = readPrefs().quickProviders
|
|
6124
|
+
for (var g0 = 0; g0 < groups.length; g0++) {
|
|
6125
|
+
if (groups[g0].id === MODEL_OFFICIAL_GROUP && groups[g0].models.length > 0) {
|
|
6126
|
+
sections.push(groups[g0])
|
|
6127
|
+
break
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
5798
6130
|
for (var g2 = 0; g2 < groups.length; g2++) {
|
|
5799
|
-
if (groups[g2].id ===
|
|
6131
|
+
if (chosen.indexOf(groups[g2].id) === -1 || groups[g2].id === MODEL_OFFICIAL_GROUP || groups[g2].models.length === 0) continue
|
|
6132
|
+
sections.push(groups[g2])
|
|
5800
6133
|
}
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
} else {
|
|
5805
|
-
for (var g3 = 0; g3 < groups.length; g3++) {
|
|
5806
|
-
for (var m2 = 0; m2 < groups[g3].models.length; m2++) rows.push({ group: groups[g3], model: groups[g3].models[m2] })
|
|
6134
|
+
if (sections.length === 0) {
|
|
6135
|
+
for (var g4 = 0; g4 < groups.length; g4++) {
|
|
6136
|
+
if (groups[g4].models.length > 0) sections.push(groups[g4])
|
|
5807
6137
|
}
|
|
5808
6138
|
}
|
|
5809
|
-
if (
|
|
6139
|
+
if (sections.length === 0) {
|
|
5810
6140
|
modelBody.appendChild(modelEl('div', 'dsh-claude-model-status', copyLabel('empty', MODEL_EMPTY_LABEL)))
|
|
5811
6141
|
} else {
|
|
5812
|
-
for (var
|
|
5813
|
-
var
|
|
5814
|
-
|
|
6142
|
+
for (var s = 0; s < sections.length; s++) {
|
|
6143
|
+
var section = sections[s]
|
|
6144
|
+
// The official source needs no naming, and the first section needs
|
|
6145
|
+
// no rule: a bare line above the list would be one line too many.
|
|
6146
|
+
var sectionLabel = section.id === MODEL_OFFICIAL_GROUP ? '' : (section.name || section.id)
|
|
6147
|
+
if (sectionLabel !== '' || s > 0) modelBody.appendChild(buildProviderRule(sectionLabel))
|
|
6148
|
+
var sectionModels = section.models.slice().sort(byModelId)
|
|
6149
|
+
for (var m = 0; m < sectionModels.length; m++) {
|
|
6150
|
+
var selected = current !== null && current.group.id === section.id && current.model.id === sectionModels[m].id
|
|
6151
|
+
modelBody.appendChild(buildModelOption(section, sectionModels[m], selected, true))
|
|
6152
|
+
}
|
|
5815
6153
|
}
|
|
5816
6154
|
}
|
|
5817
|
-
|
|
5818
|
-
//
|
|
5819
|
-
//
|
|
5820
|
-
//
|
|
5821
|
-
//
|
|
5822
|
-
//
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
6155
|
+
// The current seat is surfaced under the list when none of the sections
|
|
6156
|
+
// above already carries it, so the row the seat is read from is always
|
|
6157
|
+
// on screen. Its provider rides a rule of its own rather than trailing
|
|
6158
|
+
// the model in parentheses — the same idiom the sections use. It stays
|
|
6159
|
+
// above the divider: the divider closes the model list, so anything
|
|
6160
|
+
// that belongs to the list has to sit on its side of it.
|
|
6161
|
+
var currentListed = false
|
|
6162
|
+
for (var c = 0; c < sections.length; c++) {
|
|
6163
|
+
if (current !== null && sections[c].id === current.group.id) currentListed = true
|
|
6164
|
+
}
|
|
6165
|
+
if (current !== null && !currentListed) {
|
|
6166
|
+
// The divider above already draws a line, so a provider that needs no
|
|
6167
|
+
// naming (the official source) adds nothing here.
|
|
6168
|
+
var currentRuleName = current.group.id === MODEL_OFFICIAL_GROUP ? '' : (current.group.name || current.group.id)
|
|
6169
|
+
if (currentRuleName !== '') modelBody.appendChild(buildProviderRule(currentRuleName))
|
|
5827
6170
|
var currentRow = modelEl('button', 'dsh-claude-model-option')
|
|
5828
6171
|
currentRow.type = 'button'
|
|
5829
6172
|
currentRow.setAttribute('role', 'menuitemradio')
|
|
5830
6173
|
currentRow.setAttribute('aria-checked', 'true')
|
|
5831
|
-
var currentBrand = modelBrand(current.
|
|
6174
|
+
var currentBrand = modelBrand(current.model.id)
|
|
5832
6175
|
var currentName = current.model.name || current.model.id
|
|
5833
6176
|
// The brand id is the row's styling hook here too, so this row wears
|
|
5834
6177
|
// the same vendor lockup and face as the list entry it stands for.
|
|
5835
6178
|
if (currentBrand) currentRow.setAttribute('data-brand', currentBrand)
|
|
5836
6179
|
var currentCopy = modelEl('span', 'dsh-claude-model-copy')
|
|
5837
6180
|
var currentLabel = buildModelLabel(currentName, currentBrand)
|
|
5838
|
-
// The provider's own display name; the route id only stands in when
|
|
5839
|
-
// the catalog gives the group no name.
|
|
5840
|
-
var currentProvider = current.group.name || current.group.id
|
|
5841
|
-
if (currentProvider) currentLabel.appendChild(document.createTextNode(' (' + currentProvider + ')'))
|
|
5842
6181
|
currentCopy.appendChild(currentLabel)
|
|
5843
6182
|
var currentDesc = modelDescription(current.group.id, current.model)
|
|
5844
6183
|
if (currentDesc) currentCopy.appendChild(modelEl('span', 'dsh-claude-model-desc', currentDesc))
|
|
@@ -5852,8 +6191,14 @@ window.__ModuleLoader__.load({
|
|
|
5852
6191
|
})
|
|
5853
6192
|
modelBody.appendChild(currentRow)
|
|
5854
6193
|
}
|
|
5855
|
-
|
|
5856
|
-
|
|
6194
|
+
// The divider closes the model list and the two drill rows follow it;
|
|
6195
|
+
// all three live in the footer, OUTSIDE the scroll area — the list
|
|
6196
|
+
// above scrolls under them while the controls stay reachable.
|
|
6197
|
+
if (modelFooter) {
|
|
6198
|
+
modelFooter.appendChild(modelEl('div', 'dsh-claude-model-divider'))
|
|
6199
|
+
if (effort) modelFooter.appendChild(buildModelCell(copyLabel('effortLabel', MODEL_EFFORT_LABEL), effort.label, 'effort'))
|
|
6200
|
+
modelFooter.appendChild(buildModelCell(copyLabel('moreLabel', MODEL_MORE_LABEL), '', 'more'))
|
|
6201
|
+
}
|
|
5857
6202
|
}
|
|
5858
6203
|
}
|
|
5859
6204
|
|
|
@@ -5918,11 +6263,7 @@ window.__ModuleLoader__.load({
|
|
|
5918
6263
|
// A provider's models read in id order, so the list is scannable and stays
|
|
5919
6264
|
// put between visits; the catalog's own order is whatever the provider
|
|
5920
6265
|
// happened to send. Sorted on a copy — the snapshot belongs to the store.
|
|
5921
|
-
var groupModels = group.models.slice().sort(
|
|
5922
|
-
var left = String(a.id)
|
|
5923
|
-
var right = String(b.id)
|
|
5924
|
-
return left < right ? -1 : left > right ? 1 : 0
|
|
5925
|
-
})
|
|
6266
|
+
var groupModels = group.models.slice().sort(byModelId)
|
|
5926
6267
|
for (var m = 0; m < groupModels.length; m++) {
|
|
5927
6268
|
var selected = current !== null && current.group.id === group.id && current.model.id === groupModels[m].id
|
|
5928
6269
|
groupSection.appendChild(buildModelOption(group, groupModels[m], selected, false))
|
|
@@ -5958,6 +6299,9 @@ window.__ModuleLoader__.load({
|
|
|
5958
6299
|
modelBody = document.createElement('div')
|
|
5959
6300
|
modelBody.className = 'dsh-claude-model-popover-body'
|
|
5960
6301
|
modelPop.appendChild(modelBody)
|
|
6302
|
+
modelFooter = document.createElement('div')
|
|
6303
|
+
modelFooter.className = 'dsh-claude-model-footer'
|
|
6304
|
+
modelPop.appendChild(modelFooter)
|
|
5961
6305
|
modelPop.addEventListener('mouseenter', cancelCloseModel)
|
|
5962
6306
|
modelPop.addEventListener('mouseleave', scheduleCloseModel)
|
|
5963
6307
|
document.body.appendChild(modelPop)
|
|
@@ -5978,7 +6322,10 @@ window.__ModuleLoader__.load({
|
|
|
5978
6322
|
|
|
5979
6323
|
/** Build/refresh the trigger, its label and the popover rows. */
|
|
5980
6324
|
function syncModelControl() {
|
|
5981
|
-
|
|
6325
|
+
// Two ways to be off: the composer restyle does not apply to this page, or
|
|
6326
|
+
// the picker preference is off. Both hand the host's own model seat and
|
|
6327
|
+
// menu back, so both run the same sweep.
|
|
6328
|
+
if (!ui.copy.isComposerActive() || !readPrefs().modelPicker) {
|
|
5982
6329
|
var allHosts = document.querySelectorAll('[data-dsh-claude-model-host]')
|
|
5983
6330
|
for (var h = 0; h < allHosts.length; h++) {
|
|
5984
6331
|
allHosts[h].removeAttribute('data-dsh-claude-model-host')
|
|
@@ -5995,6 +6342,7 @@ window.__ModuleLoader__.load({
|
|
|
5995
6342
|
modelPop = null
|
|
5996
6343
|
modelSubPop = null
|
|
5997
6344
|
modelBody = null
|
|
6345
|
+
modelFooter = null
|
|
5998
6346
|
modelSubBody = null
|
|
5999
6347
|
modelSubKind = null
|
|
6000
6348
|
modelBodySig = ''
|
|
@@ -6014,6 +6362,18 @@ window.__ModuleLoader__.load({
|
|
|
6014
6362
|
var slot = document.querySelector('[data-slot="conversation.input.model"]')
|
|
6015
6363
|
if (slot === null) return
|
|
6016
6364
|
// Hide the host's own seat (React owns the node; re-mark on swap).
|
|
6365
|
+
// Idempotent against a torn-down-less reload, like the account footer:
|
|
6366
|
+
// client HMR drops the old fiber's disposals, so a previous generation's
|
|
6367
|
+
// trigger and popovers are still in the DOM while this fresh scope starts
|
|
6368
|
+
// from null. Sweep the strays, or the seat renders twice.
|
|
6369
|
+
var strayBtns = slot.querySelectorAll('.dsh-claude-model-btn')
|
|
6370
|
+
for (var sb = 0; sb < strayBtns.length; sb++) {
|
|
6371
|
+
if (strayBtns[sb] !== modelBtn) strayBtns[sb].parentElement.removeChild(strayBtns[sb])
|
|
6372
|
+
}
|
|
6373
|
+
var strayPops = document.querySelectorAll('body > .dsh-claude-model-popover')
|
|
6374
|
+
for (var sp = 0; sp < strayPops.length; sp++) {
|
|
6375
|
+
if (strayPops[sp] !== modelPop && strayPops[sp] !== modelSubPop) strayPops[sp].parentElement.removeChild(strayPops[sp])
|
|
6376
|
+
}
|
|
6017
6377
|
var hostRoot = slot.firstElementChild
|
|
6018
6378
|
if (hostRoot !== null && !hostRoot.hasAttribute('data-dsh-claude-model-host')) {
|
|
6019
6379
|
hostRoot.setAttribute('data-dsh-claude-model-host', '')
|
|
@@ -6025,13 +6385,13 @@ window.__ModuleLoader__.load({
|
|
|
6025
6385
|
modelBtn.className = 'dsh-claude-model-btn'
|
|
6026
6386
|
modelBtn.setAttribute('aria-haspopup', 'menu')
|
|
6027
6387
|
modelBtn.innerHTML = '<span class="dsh-claude-model-btn-label"></span>'
|
|
6028
|
-
// Same contract as the account trigger: hover
|
|
6029
|
-
//
|
|
6388
|
+
// Same contract as the account trigger: hover under the "All" scope,
|
|
6389
|
+
// click-only otherwise.
|
|
6030
6390
|
modelBtn.addEventListener('mouseenter', function () {
|
|
6031
|
-
if (readPrefs().autoPopover) openModelPopover()
|
|
6391
|
+
if (readPrefs().autoPopover === AUTO_POPOVER_ALL) openModelPopover()
|
|
6032
6392
|
})
|
|
6033
6393
|
modelBtn.addEventListener('mouseleave', function () {
|
|
6034
|
-
if (readPrefs().autoPopover) scheduleCloseModel()
|
|
6394
|
+
if (readPrefs().autoPopover === AUTO_POPOVER_ALL) scheduleCloseModel()
|
|
6035
6395
|
})
|
|
6036
6396
|
modelBtn.addEventListener('click', function (e) {
|
|
6037
6397
|
e.stopPropagation()
|
|
@@ -6083,6 +6443,14 @@ window.__ModuleLoader__.load({
|
|
|
6083
6443
|
(modelSubPop !== null && modelSubPop.contains(target))
|
|
6084
6444
|
},
|
|
6085
6445
|
reposition: positionModelPopovers,
|
|
6446
|
+
providers: modelProviders,
|
|
6447
|
+
onProviders: function (listener) {
|
|
6448
|
+
providerListeners.push(listener)
|
|
6449
|
+
return function () {
|
|
6450
|
+
var at = providerListeners.indexOf(listener)
|
|
6451
|
+
if (at !== -1) providerListeners.splice(at, 1)
|
|
6452
|
+
}
|
|
6453
|
+
},
|
|
6086
6454
|
invalidateCopy: function () {
|
|
6087
6455
|
modelBodySig = ''
|
|
6088
6456
|
modelSubSig = ''
|
|
@@ -6097,6 +6465,7 @@ window.__ModuleLoader__.load({
|
|
|
6097
6465
|
modelPop = null
|
|
6098
6466
|
modelSubPop = null
|
|
6099
6467
|
modelBody = null
|
|
6468
|
+
modelFooter = null
|
|
6100
6469
|
modelSubBody = null
|
|
6101
6470
|
modelSubKind = null
|
|
6102
6471
|
modelBodySig = ''
|
|
@@ -6107,6 +6476,331 @@ window.__ModuleLoader__.load({
|
|
|
6107
6476
|
return ui.model.teardown
|
|
6108
6477
|
}
|
|
6109
6478
|
|
|
6479
|
+
/**
|
|
6480
|
+
* The hero row's two pickers — the workspace (directory) chip and the
|
|
6481
|
+
* agent-preset seat — both open the host's shared menu primitive, which
|
|
6482
|
+
* portals its card to <body>. That card carries no marker of its own, so a
|
|
6483
|
+
* stylesheet cannot tell it apart from the host's other menus (sidebar row
|
|
6484
|
+
* menus, the settings permission row, submenus) — and it has to: this skin
|
|
6485
|
+
* redraws these two and leaves the rest alone.
|
|
6486
|
+
*
|
|
6487
|
+
* So the browser half stamps the open card rather than guessing in CSS.
|
|
6488
|
+
* While either trigger reports `aria-expanded="true"`, the one host menu
|
|
6489
|
+
* sitting in <body> is that card; HERO_MENU_ATTR is what
|
|
6490
|
+
* components/hero-menu.css switches on.
|
|
6491
|
+
*
|
|
6492
|
+
* The scheduler's attributeFilter does not watch `aria-expanded`, but the
|
|
6493
|
+
* card is inserted into <body> as it opens — a childList mutation the pass
|
|
6494
|
+
* already observes — so the stamp lands on the frame the menu appears. Two
|
|
6495
|
+
* candidates mean an unrelated menu is open as well: the pass then leaves
|
|
6496
|
+
* every card alone rather than stamping the wrong one.
|
|
6497
|
+
*
|
|
6498
|
+
* The host drops that card below its trigger, which is exactly where the
|
|
6499
|
+
* composer sits: it would land on the input area and on the controls in it.
|
|
6500
|
+
* This row is *above* the composer, so the card belongs beside the trigger
|
|
6501
|
+
* instead — bottom-aligned with it and growing upward into the empty hero
|
|
6502
|
+
* space. The host re-places the card on every scroll and resize, so this
|
|
6503
|
+
* position is written with `important` (which beats its inline write) and
|
|
6504
|
+
* re-applied from the scheduler's scroll/resize hook.
|
|
6505
|
+
*
|
|
6506
|
+
* Hover is the host's own click handler driven from here: the "Open popovers
|
|
6507
|
+
* on hover" preference's `all` scope covers these two, and the host offers no
|
|
6508
|
+
* hover of its own. The listeners are delegated from the document because
|
|
6509
|
+
* React replaces the triggers, and the close waits out the gap between the
|
|
6510
|
+
* trigger and the card so crossing it does not shut the menu.
|
|
6511
|
+
*/
|
|
6512
|
+
function installHeroMenu(ctx, ui) {
|
|
6513
|
+
/** Air between the trigger and the card, and the viewport's own margin. */
|
|
6514
|
+
var GAP = 6
|
|
6515
|
+
var MARGIN = 12
|
|
6516
|
+
/** Long enough to cross the gap above, short enough to still read as hover. */
|
|
6517
|
+
var CLOSE_DELAY = 160
|
|
6518
|
+
/** The two triggers, and the card once it is stamped. */
|
|
6519
|
+
var TRIGGER_SELECTOR = '[class*="heroWorkspaceRow"] [aria-haspopup="menu"]'
|
|
6520
|
+
var CARD_SELECTOR = '[' + HERO_MENU_ATTR + ']'
|
|
6521
|
+
var stamped = null
|
|
6522
|
+
var stampedTrigger = null
|
|
6523
|
+
var closeTimer = null
|
|
6524
|
+
var openedByHover = false
|
|
6525
|
+
|
|
6526
|
+
function cancelHoverClose() {
|
|
6527
|
+
if (closeTimer === null) return
|
|
6528
|
+
clearTimeout(closeTimer)
|
|
6529
|
+
closeTimer = null
|
|
6530
|
+
}
|
|
6531
|
+
|
|
6532
|
+
/** Close what hover opened; a click-opened menu is left alone. */
|
|
6533
|
+
function closeFromHover() {
|
|
6534
|
+
closeTimer = null
|
|
6535
|
+
if (!openedByHover) return
|
|
6536
|
+
var trigger = openTrigger()
|
|
6537
|
+
if (trigger !== null) trigger.click()
|
|
6538
|
+
openedByHover = false
|
|
6539
|
+
}
|
|
6540
|
+
|
|
6541
|
+
function scheduleHoverClose() {
|
|
6542
|
+
cancelHoverClose()
|
|
6543
|
+
closeTimer = setTimeout(closeFromHover, CLOSE_DELAY)
|
|
6544
|
+
}
|
|
6545
|
+
|
|
6546
|
+
/** The `all` scope only, and only while the composer restyle is in play. */
|
|
6547
|
+
function hoverEnabled() {
|
|
6548
|
+
return readPrefs().autoPopover === AUTO_POPOVER_ALL &&
|
|
6549
|
+
ui.copy !== undefined && ui.copy.isComposerActive()
|
|
6550
|
+
}
|
|
6551
|
+
|
|
6552
|
+
function closestWithin(target, selector) {
|
|
6553
|
+
if (target === null || target === undefined || typeof target.closest !== 'function') return null
|
|
6554
|
+
return target.closest(selector)
|
|
6555
|
+
}
|
|
6556
|
+
|
|
6557
|
+
function onHeroPointerOver(e) {
|
|
6558
|
+
if (!hoverEnabled()) return
|
|
6559
|
+
var target = e.target
|
|
6560
|
+
if (closestWithin(target, CARD_SELECTOR) !== null) {
|
|
6561
|
+
cancelHoverClose()
|
|
6562
|
+
return
|
|
6563
|
+
}
|
|
6564
|
+
var trigger = closestWithin(target, TRIGGER_SELECTOR)
|
|
6565
|
+
if (trigger === null) return
|
|
6566
|
+
cancelHoverClose()
|
|
6567
|
+
if (trigger.getAttribute('aria-expanded') !== 'true') {
|
|
6568
|
+
trigger.click()
|
|
6569
|
+
openedByHover = true
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
|
|
6573
|
+
function onHeroPointerOut(e) {
|
|
6574
|
+
if (!hoverEnabled()) return
|
|
6575
|
+
var target = e.target
|
|
6576
|
+
if (closestWithin(target, CARD_SELECTOR) === null && closestWithin(target, TRIGGER_SELECTOR) === null) return
|
|
6577
|
+
// Moving onto the other half — the card, or the trigger — is not a leave.
|
|
6578
|
+
var next = e.relatedTarget
|
|
6579
|
+
if (closestWithin(next, CARD_SELECTOR) !== null || closestWithin(next, TRIGGER_SELECTOR) !== null) return
|
|
6580
|
+
scheduleHoverClose()
|
|
6581
|
+
}
|
|
6582
|
+
|
|
6583
|
+
document.addEventListener('mouseover', onHeroPointerOver, true)
|
|
6584
|
+
document.addEventListener('mouseout', onHeroPointerOut, true)
|
|
6585
|
+
|
|
6586
|
+
function clearStamp() {
|
|
6587
|
+
if (stamped === null) return
|
|
6588
|
+
stamped.removeAttribute(HERO_MENU_ATTR)
|
|
6589
|
+
stamped = null
|
|
6590
|
+
stampedTrigger = null
|
|
6591
|
+
}
|
|
6592
|
+
|
|
6593
|
+
/** The trigger whose picker is open, or null. */
|
|
6594
|
+
function openTrigger() {
|
|
6595
|
+
return document.querySelector('[class*="heroWorkspaceRow"] [aria-haspopup="menu"][aria-expanded="true"]') ||
|
|
6596
|
+
document.querySelector('[class*="cardWorkspaceTrigger"] [aria-expanded="true"]')
|
|
6597
|
+
}
|
|
6598
|
+
|
|
6599
|
+
/**
|
|
6600
|
+
* Park the card beside its trigger: to its right, bottom-aligned so it
|
|
6601
|
+
* grows upward, flipped to the left when the viewport is tight, and clamped
|
|
6602
|
+
* to the viewport either way. A card that has not been laid out yet is left
|
|
6603
|
+
* for the next pass rather than pinned to a zero-sized guess.
|
|
6604
|
+
*/
|
|
6605
|
+
function placeCard(trigger, card) {
|
|
6606
|
+
var rect = trigger.getBoundingClientRect()
|
|
6607
|
+
var width = card.offsetWidth
|
|
6608
|
+
var height = card.offsetHeight
|
|
6609
|
+
if (width === 0 || height === 0) return
|
|
6610
|
+
var left = rect.right + GAP
|
|
6611
|
+
if (left + width > window.innerWidth - MARGIN) left = rect.left - GAP - width
|
|
6612
|
+
left = Math.max(MARGIN, Math.min(left, window.innerWidth - width - MARGIN))
|
|
6613
|
+
var top = rect.bottom - height
|
|
6614
|
+
top = Math.max(MARGIN, Math.min(top, window.innerHeight - height - MARGIN))
|
|
6615
|
+
card.style.setProperty('left', Math.round(left) + 'px', 'important')
|
|
6616
|
+
card.style.setProperty('top', Math.round(top) + 'px', 'important')
|
|
6617
|
+
}
|
|
6618
|
+
|
|
6619
|
+
/** Re-place an open card after a scroll or a resize moved its anchor. */
|
|
6620
|
+
function repositionHeroMenu() {
|
|
6621
|
+
if (stamped === null || stampedTrigger === null) return
|
|
6622
|
+
placeCard(stampedTrigger, stamped)
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6625
|
+
function syncHeroMenu() {
|
|
6626
|
+
var trigger = openTrigger()
|
|
6627
|
+
if (trigger === null) {
|
|
6628
|
+
// The menu is shut — by a click, by Escape or by an outside press — so
|
|
6629
|
+
// whatever hover opened it no longer owns it.
|
|
6630
|
+
openedByHover = false
|
|
6631
|
+
cancelHoverClose()
|
|
6632
|
+
clearStamp()
|
|
6633
|
+
return
|
|
6634
|
+
}
|
|
6635
|
+
// The skin's own popovers carry their own classes, so excluding them
|
|
6636
|
+
// leaves the host's menus only.
|
|
6637
|
+
var cards = document.querySelectorAll('body > [role="menu"]:not([class*="dsh-claude"])')
|
|
6638
|
+
if (cards.length !== 1) {
|
|
6639
|
+
clearStamp()
|
|
6640
|
+
return
|
|
6641
|
+
}
|
|
6642
|
+
if (stamped !== cards[0]) {
|
|
6643
|
+
clearStamp()
|
|
6644
|
+
stamped = cards[0]
|
|
6645
|
+
stampedTrigger = trigger
|
|
6646
|
+
stamped.setAttribute(HERO_MENU_ATTR, '')
|
|
6647
|
+
}
|
|
6648
|
+
placeCard(trigger, stamped)
|
|
6649
|
+
}
|
|
6650
|
+
|
|
6651
|
+
ui.heroMenu = { sync: syncHeroMenu, reposition: repositionHeroMenu }
|
|
6652
|
+
return function () {
|
|
6653
|
+
cancelHoverClose()
|
|
6654
|
+
openedByHover = false
|
|
6655
|
+
clearStamp()
|
|
6656
|
+
document.removeEventListener('mouseover', onHeroPointerOver, true)
|
|
6657
|
+
document.removeEventListener('mouseout', onHeroPointerOut, true)
|
|
6658
|
+
delete ui.heroMenu
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
|
|
6662
|
+
/**
|
|
6663
|
+
* The settings page's quick-provider picker — the providers whose models the
|
|
6664
|
+
* model picker's first level carries.
|
|
6665
|
+
*
|
|
6666
|
+
* It is the permission popover's design in multi-select form: the same card
|
|
6667
|
+
* and the same rows (`.dsh-claude-perm-popover` is that shared card, the name
|
|
6668
|
+
* is historical), with the accent check marking the chosen providers. It is a
|
|
6669
|
+
* settings control rather than a takeover, so it opens on click only —
|
|
6670
|
+
* hovering a settings row should not unfold a menu — and it stays open while
|
|
6671
|
+
* rows are toggled.
|
|
6672
|
+
*
|
|
6673
|
+
* The card is appended to <body> rather than rendered inside the settings row:
|
|
6674
|
+
* a dialog clips and may transform its own subtree, so a `position: fixed`
|
|
6675
|
+
* card in there would be positioned against the dialog, not the viewport.
|
|
6676
|
+
*/
|
|
6677
|
+
function installQuickProviders(ctx, ui) {
|
|
6678
|
+
var CHECK_SVG = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8.5l3.2 3.2L13 5"/></svg>'
|
|
6679
|
+
var card = null
|
|
6680
|
+
var cardBody = null
|
|
6681
|
+
var anchor = null
|
|
6682
|
+
var onWrite = null
|
|
6683
|
+
var providers = []
|
|
6684
|
+
var unsubscribe = null
|
|
6685
|
+
|
|
6686
|
+
function close() {
|
|
6687
|
+
if (card !== null && card.parentElement) card.parentElement.removeChild(card)
|
|
6688
|
+
if (anchor !== null) anchor.setAttribute('aria-expanded', 'false')
|
|
6689
|
+
card = null
|
|
6690
|
+
cardBody = null
|
|
6691
|
+
anchor = null
|
|
6692
|
+
onWrite = null
|
|
6693
|
+
document.removeEventListener('pointerdown', onOutsidePointerDown, true)
|
|
6694
|
+
document.removeEventListener('keydown', onKeyDown, true)
|
|
6695
|
+
}
|
|
6696
|
+
|
|
6697
|
+
function onOutsidePointerDown(e) {
|
|
6698
|
+
if (card === null) return
|
|
6699
|
+
var target = e.target
|
|
6700
|
+
if (card.contains(target)) return
|
|
6701
|
+
if (anchor !== null && anchor.contains(target)) return
|
|
6702
|
+
close()
|
|
6703
|
+
}
|
|
6704
|
+
|
|
6705
|
+
function onKeyDown(e) {
|
|
6706
|
+
if (e.key === 'Escape') close()
|
|
6707
|
+
}
|
|
6708
|
+
|
|
6709
|
+
/** One provider: its name, how many models it offers, and a check when chosen. */
|
|
6710
|
+
function buildRow(provider, chosen, stale) {
|
|
6711
|
+
var row = modelEl('button', 'dsh-claude-popover-item')
|
|
6712
|
+
if (stale) row.classList.add('dsh-claude-model-stale')
|
|
6713
|
+
row.type = 'button'
|
|
6714
|
+
row.setAttribute('role', 'menuitemcheckbox')
|
|
6715
|
+
row.setAttribute('aria-checked', chosen ? 'true' : 'false')
|
|
6716
|
+
row.appendChild(modelEl('span', 'dsh-claude-popover-item-text', provider.name))
|
|
6717
|
+
row.appendChild(modelEl('span', 'dsh-claude-popover-item-badge', stale ? settingsCopy('quickRemoved', 'Removed') : String(provider.count)))
|
|
6718
|
+
var check = modelEl('span', 'dsh-claude-model-check')
|
|
6719
|
+
check.innerHTML = chosen ? CHECK_SVG : ''
|
|
6720
|
+
row.appendChild(check)
|
|
6721
|
+
row.addEventListener('click', function (e) {
|
|
6722
|
+
e.stopPropagation()
|
|
6723
|
+
var next = readPrefs().quickProviders.slice()
|
|
6724
|
+
var at = next.indexOf(provider.id)
|
|
6725
|
+
if (at === -1) next.push(provider.id)
|
|
6726
|
+
else next.splice(at, 1)
|
|
6727
|
+
if (typeof onWrite === 'function') onWrite(next)
|
|
6728
|
+
renderBody()
|
|
6729
|
+
})
|
|
6730
|
+
return row
|
|
6731
|
+
}
|
|
6732
|
+
|
|
6733
|
+
function renderBody() {
|
|
6734
|
+
if (cardBody === null) return
|
|
6735
|
+
while (cardBody.firstChild) cardBody.removeChild(cardBody.firstChild)
|
|
6736
|
+
if (providers.length === 0) {
|
|
6737
|
+
cardBody.appendChild(modelEl('div', 'dsh-claude-model-status', settingsCopy('quickLoading', 'Loading providers…')))
|
|
6738
|
+
return
|
|
6739
|
+
}
|
|
6740
|
+
var chosen = readPrefs().quickProviders
|
|
6741
|
+
// The official service always leads the first level, so it is not a
|
|
6742
|
+
// choice here: it is never listed, and a stored id for it is pruned on
|
|
6743
|
+
// load. A provider that vanished from the catalog still appears, marked
|
|
6744
|
+
// removed, so it can be unchecked out of the stored list instead of
|
|
6745
|
+
// lingering invisibly.
|
|
6746
|
+
var known = {}
|
|
6747
|
+
for (var i = 0; i < providers.length; i++) {
|
|
6748
|
+
if (providers[i].id === MODEL_OFFICIAL_GROUP) continue
|
|
6749
|
+
known[providers[i].id] = true
|
|
6750
|
+
cardBody.appendChild(buildRow(providers[i], chosen.indexOf(providers[i].id) !== -1, false))
|
|
6751
|
+
}
|
|
6752
|
+
for (var s = 0; s < chosen.length; s++) {
|
|
6753
|
+
if (chosen[s] === MODEL_OFFICIAL_GROUP || known[chosen[s]]) continue
|
|
6754
|
+
cardBody.appendChild(buildRow({ id: chosen[s], name: chosen[s], count: 0 }, true, true))
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
|
|
6758
|
+
function open(trigger, write) {
|
|
6759
|
+
anchor = trigger
|
|
6760
|
+
anchor.setAttribute('aria-expanded', 'true')
|
|
6761
|
+
onWrite = write
|
|
6762
|
+
providers = ui.model && typeof ui.model.providers === 'function' ? ui.model.providers() : []
|
|
6763
|
+
card = modelEl('div', 'dsh-claude-perm-popover')
|
|
6764
|
+
card.setAttribute('role', 'menu')
|
|
6765
|
+
card.setAttribute('data-open', 'true')
|
|
6766
|
+
cardBody = modelEl('div', 'dsh-claude-model-popover-body')
|
|
6767
|
+
card.appendChild(cardBody)
|
|
6768
|
+
renderBody()
|
|
6769
|
+
document.body.appendChild(card)
|
|
6770
|
+
positionAnchoredPopover(trigger, card, { side: 'above', gap: 6, important: true })
|
|
6771
|
+
document.addEventListener('pointerdown', onOutsidePointerDown, true)
|
|
6772
|
+
document.addEventListener('keydown', onKeyDown, true)
|
|
6773
|
+
}
|
|
6774
|
+
|
|
6775
|
+
function toggle(trigger, write) {
|
|
6776
|
+
if (card === null) open(trigger, write)
|
|
6777
|
+
else close()
|
|
6778
|
+
}
|
|
6779
|
+
|
|
6780
|
+
// The catalog arrives after the settings page may already be open, so an
|
|
6781
|
+
// open card follows it rather than showing an empty list until reopened.
|
|
6782
|
+
if (ui.model && typeof ui.model.onProviders === 'function') {
|
|
6783
|
+
unsubscribe = ui.model.onProviders(function (list) {
|
|
6784
|
+
providers = list
|
|
6785
|
+
if (card !== null) renderBody()
|
|
6786
|
+
})
|
|
6787
|
+
}
|
|
6788
|
+
|
|
6789
|
+
ui.quickProviders = {
|
|
6790
|
+
toggle: toggle,
|
|
6791
|
+
close: close,
|
|
6792
|
+
isOpen: function () { return card !== null },
|
|
6793
|
+
}
|
|
6794
|
+
return function () {
|
|
6795
|
+
close()
|
|
6796
|
+
if (unsubscribe !== null) {
|
|
6797
|
+
try { unsubscribe() } catch (error) { /* already disposed */ }
|
|
6798
|
+
unsubscribe = null
|
|
6799
|
+
}
|
|
6800
|
+
delete ui.quickProviders
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
|
|
6110
6804
|
function installAccountFooter(ctx, ui) {
|
|
6111
6805
|
var accountBtn = null
|
|
6112
6806
|
var accountPopover = null
|
|
@@ -6696,6 +7390,20 @@ window.__ModuleLoader__.load({
|
|
|
6696
7390
|
}
|
|
6697
7391
|
var username = getUsername(ctx)
|
|
6698
7392
|
|
|
7393
|
+
// Idempotent against a torn-down-less reload: client HMR drops the old
|
|
7394
|
+
// fiber's disposals instead of running them, so a previous generation's
|
|
7395
|
+
// button and popover are still in the DOM and this fresh scope knows
|
|
7396
|
+
// nothing of them. Sweep the strays before deciding whether to build —
|
|
7397
|
+
// otherwise the row renders twice (and the orphan keeps a stale name).
|
|
7398
|
+
var strayBtns = footArea.querySelectorAll('.dsh-claude-account-btn')
|
|
7399
|
+
for (var sb = 0; sb < strayBtns.length; sb++) {
|
|
7400
|
+
if (strayBtns[sb] !== accountBtn) strayBtns[sb].parentElement.removeChild(strayBtns[sb])
|
|
7401
|
+
}
|
|
7402
|
+
var strayPops = document.querySelectorAll('body > .dsh-claude-account-popover')
|
|
7403
|
+
for (var sp = 0; sp < strayPops.length; sp++) {
|
|
7404
|
+
if (strayPops[sp] !== accountPopover) strayPops[sp].parentElement.removeChild(strayPops[sp])
|
|
7405
|
+
}
|
|
7406
|
+
|
|
6699
7407
|
if (accountBtn === null || !footArea.contains(accountBtn)) {
|
|
6700
7408
|
if (accountBtn && accountBtn.parentElement) accountBtn.parentElement.removeChild(accountBtn)
|
|
6701
7409
|
accountBtn = document.createElement('div')
|
|
@@ -6714,12 +7422,13 @@ window.__ModuleLoader__.load({
|
|
|
6714
7422
|
// Hover is the default way in; the "Open popovers on hover"
|
|
6715
7423
|
// preference turns it off, leaving the click handler below as the only
|
|
6716
7424
|
// way in (and the only way out, so a click-opened popover does not
|
|
6717
|
-
// vanish when the pointer leaves).
|
|
7425
|
+
// vanish when the pointer leaves). The account row is the one popover
|
|
7426
|
+
// the `account` scope keeps on hover.
|
|
6718
7427
|
accountBtn.addEventListener('mouseenter', function () {
|
|
6719
|
-
if (readPrefs().autoPopover) openPopover()
|
|
7428
|
+
if (readPrefs().autoPopover !== AUTO_POPOVER_OFF) openPopover()
|
|
6720
7429
|
})
|
|
6721
7430
|
accountBtn.addEventListener('mouseleave', function () {
|
|
6722
|
-
if (readPrefs().autoPopover && !popoverOpenedByClick) scheduleClosePopover()
|
|
7431
|
+
if (readPrefs().autoPopover !== AUTO_POPOVER_OFF && !popoverOpenedByClick) scheduleClosePopover()
|
|
6723
7432
|
})
|
|
6724
7433
|
accountBtn.addEventListener('click', function (e) {
|
|
6725
7434
|
e.stopPropagation()
|
|
@@ -7339,6 +8048,23 @@ window.__ModuleLoader__.load({
|
|
|
7339
8048
|
}
|
|
7340
8049
|
}
|
|
7341
8050
|
|
|
8051
|
+
// Focus moving into the composer means the user is about to type: every
|
|
8052
|
+
// popover the skin keeps open around the card is in the way there, so all
|
|
8053
|
+
// of them close. `focusin` bubbles (unlike focus), so one listener covers
|
|
8054
|
+
// the card and everything inside it; the observer's attributeFilter does
|
|
8055
|
+
// not watch focus events, so this cannot feed itself another pass.
|
|
8056
|
+
function onComposerFocusIn(e) {
|
|
8057
|
+
var target = e.target
|
|
8058
|
+
if (!target || typeof target.closest !== 'function') return
|
|
8059
|
+
if (target.closest('[data-composer-card]') === null) return
|
|
8060
|
+
if (ui.model) ui.model.close()
|
|
8061
|
+
if (ui.permissions) ui.permissions.closeMenu()
|
|
8062
|
+
if (ui.permissions && ui.permissions.closeStats) ui.permissions.closeStats()
|
|
8063
|
+
if (ui.footer) ui.footer.close()
|
|
8064
|
+
if (ui.heroMenu) ui.heroMenu.close()
|
|
8065
|
+
if (ui.quickProviders) ui.quickProviders.close()
|
|
8066
|
+
}
|
|
8067
|
+
|
|
7342
8068
|
function onComposerInput(e) {
|
|
7343
8069
|
var target = e.target
|
|
7344
8070
|
if (!target) return
|
|
@@ -7352,12 +8078,14 @@ window.__ModuleLoader__.load({
|
|
|
7352
8078
|
document.addEventListener('keydown', onGlobalKeyDown, true)
|
|
7353
8079
|
document.addEventListener('input', onComposerInput, true)
|
|
7354
8080
|
document.addEventListener('compositionend', onComposerInput, true)
|
|
8081
|
+
document.addEventListener('focusin', onComposerFocusIn, true)
|
|
7355
8082
|
|
|
7356
8083
|
// Both fixed popovers are anchored to their trigger; scroll of the page
|
|
7357
8084
|
// (not the conversation's own auto-stick) and resizes move the anchor, so
|
|
7358
8085
|
// whichever is open must re-resolve it.
|
|
7359
8086
|
function onFixedPopoverViewportChange() {
|
|
7360
8087
|
if (ui.model) ui.model.reposition()
|
|
8088
|
+
if (ui.heroMenu) ui.heroMenu.reposition()
|
|
7361
8089
|
if (ui.footer && ui.footer.isOpen()) ui.footer.reposition()
|
|
7362
8090
|
}
|
|
7363
8091
|
window.addEventListener('resize', onFixedPopoverViewportChange)
|
|
@@ -7429,6 +8157,7 @@ window.__ModuleLoader__.load({
|
|
|
7429
8157
|
if (ui.copy) ui.copy.sync()
|
|
7430
8158
|
if (ui.permissions) ui.permissions.sync()
|
|
7431
8159
|
if (ui.model) ui.model.sync()
|
|
8160
|
+
if (ui.heroMenu) ui.heroMenu.sync()
|
|
7432
8161
|
if (ui.footer) ui.footer.sync()
|
|
7433
8162
|
if (ui.settings) ui.settings.sync()
|
|
7434
8163
|
// Covers the rail toggle (and any reflow) while the popover is open:
|
|
@@ -7497,6 +8226,7 @@ window.__ModuleLoader__.load({
|
|
|
7497
8226
|
document.removeEventListener('keydown', onGlobalKeyDown, true)
|
|
7498
8227
|
document.removeEventListener('input', onComposerInput, true)
|
|
7499
8228
|
document.removeEventListener('compositionend', onComposerInput, true)
|
|
8229
|
+
document.removeEventListener('focusin', onComposerFocusIn, true)
|
|
7500
8230
|
// Safety-net DOM sweep. Feature teardowns run after this and tolerate
|
|
7501
8231
|
// nodes already being detached.
|
|
7502
8232
|
var leftoverItems = document.querySelectorAll('.dsh-claude-popover-item, .dsh-claude-popover-embed, .dsh-claude-account-popover, .dsh-claude-account-btn, .dsh-claude-perm-container, .dsh-claude-perm-popover, .dsh-claude-segments[data-composer-segments], .dsh-claude-model-btn, .dsh-claude-model-popover, .dsh-claude-ban, [data-dsh-synthetic-placeholder]')
|
|
@@ -7530,6 +8260,13 @@ window.__ModuleLoader__.load({
|
|
|
7530
8260
|
* `.dsh-claude-segment` classes — the same control the composer's permission
|
|
7531
8261
|
* picker uses — so the two read as one design instead of two lookalikes.
|
|
7532
8262
|
*/
|
|
8263
|
+
/**
|
|
8264
|
+
* The quick-provider popover (src/overrides/quick-providers.js). The settings
|
|
8265
|
+
* row is React-rendered while that card is imperative, so the row reaches the
|
|
8266
|
+
* popover through this handle.
|
|
8267
|
+
*/
|
|
8268
|
+
var quickProviderApi = null
|
|
8269
|
+
|
|
7533
8270
|
function ClaudeStyleSettingsSection() {
|
|
7534
8271
|
var state = React.useState(readPrefs())
|
|
7535
8272
|
var prefs = state[0]
|
|
@@ -7541,6 +8278,7 @@ window.__ModuleLoader__.load({
|
|
|
7541
8278
|
var username = usernameState[0]
|
|
7542
8279
|
var setUsername = usernameState[1]
|
|
7543
8280
|
var usernameTimer = React.useRef(null)
|
|
8281
|
+
var quickTrigger = React.useRef(null)
|
|
7544
8282
|
|
|
7545
8283
|
// The skin's own apply-side writes land here too (a reload, a conflict
|
|
7546
8284
|
// re-read), so the page never drifts from what the document says.
|
|
@@ -7669,6 +8407,17 @@ window.__ModuleLoader__.load({
|
|
|
7669
8407
|
{ value: BAN_LOCALE_ZH, label: settingsCopy('banLocaleZh', '中文') },
|
|
7670
8408
|
{ value: BAN_LOCALE_EN, label: settingsCopy('banLocaleEn', 'English') },
|
|
7671
8409
|
]
|
|
8410
|
+
var autoPopoverOptions = [
|
|
8411
|
+
{ value: AUTO_POPOVER_OFF, label: settingsCopy('autoPopoverOff', 'Off') },
|
|
8412
|
+
{ value: AUTO_POPOVER_ACCOUNT, label: settingsCopy('autoPopoverAccount', 'Account only') },
|
|
8413
|
+
{ value: AUTO_POPOVER_ALL, label: settingsCopy('autoPopoverAll', 'All') },
|
|
8414
|
+
]
|
|
8415
|
+
|
|
8416
|
+
/** What the quick-provider trigger reads: how many, or nothing chosen. */
|
|
8417
|
+
var quickSummary = function (chosen) {
|
|
8418
|
+
if (chosen.length === 0) return settingsCopy('quickNone', 'None')
|
|
8419
|
+
return settingsCopy('quickCount', '{count} providers', { count: chosen.length })
|
|
8420
|
+
}
|
|
7672
8421
|
|
|
7673
8422
|
var rows = [
|
|
7674
8423
|
row(
|
|
@@ -7715,8 +8464,8 @@ window.__ModuleLoader__.load({
|
|
|
7715
8464
|
row(
|
|
7716
8465
|
'autoPopover',
|
|
7717
8466
|
settingsCopy('autoPopoverTitle', 'Open popovers on hover'),
|
|
7718
|
-
settingsCopy('autoPopoverDesc', '
|
|
7719
|
-
|
|
8467
|
+
settingsCopy('autoPopoverDesc', 'Which popovers hover opens. Off leaves them all click-to-open.'),
|
|
8468
|
+
segment(autoPopoverOptions, prefs.autoPopover, function (value) { write({ autoPopover: value }) }),
|
|
7720
8469
|
),
|
|
7721
8470
|
row(
|
|
7722
8471
|
'composerScope',
|
|
@@ -7724,6 +8473,28 @@ window.__ModuleLoader__.load({
|
|
|
7724
8473
|
settingsCopy('composerDesc', 'Which input area the skin restyles: the new-conversation page, the conversation, or both.'),
|
|
7725
8474
|
segment(scopeOptions, prefs.composerScope, function (value) { write({ composerScope: value }) }),
|
|
7726
8475
|
),
|
|
8476
|
+
row(
|
|
8477
|
+
'modelPicker',
|
|
8478
|
+
settingsCopy('pickerTitle', 'Redraw the model picker'),
|
|
8479
|
+
settingsCopy('pickerDesc', 'Replace the composer\'s model seat with the two-level Claude-style menu. Off hands the model menu back to the host; the rest of the composer restyle is unaffected.'),
|
|
8480
|
+
toggle(prefs.modelPicker, function (value) { write({ modelPicker: value }) }),
|
|
8481
|
+
),
|
|
8482
|
+
row(
|
|
8483
|
+
'quickProviders',
|
|
8484
|
+
settingsCopy('quickTitle', 'Quick providers'),
|
|
8485
|
+
settingsCopy('quickDesc', 'Providers whose models the picker\'s first level lists, one rule between providers. Nothing picked keeps the default: the official service.'),
|
|
8486
|
+
React.createElement('button', {
|
|
8487
|
+
type: 'button',
|
|
8488
|
+
ref: quickTrigger,
|
|
8489
|
+
className: 'dsh-claude-settings-picker',
|
|
8490
|
+
'aria-haspopup': 'menu',
|
|
8491
|
+
'aria-expanded': 'false',
|
|
8492
|
+
onClick: function () {
|
|
8493
|
+
if (quickProviderApi === null || quickTrigger.current === null) return
|
|
8494
|
+
quickProviderApi.toggle(quickTrigger.current, function (next) { write({ quickProviders: next }) })
|
|
8495
|
+
},
|
|
8496
|
+
}, quickSummary(prefs.quickProviders)),
|
|
8497
|
+
),
|
|
7727
8498
|
row(
|
|
7728
8499
|
'banLocale',
|
|
7729
8500
|
settingsCopy('banLocaleTitle', 'Account-hold easter egg language'),
|
|
@@ -7784,6 +8555,7 @@ window.__ModuleLoader__.load({
|
|
|
7784
8555
|
ui.settings = {
|
|
7785
8556
|
sync: syncSettingsNav,
|
|
7786
8557
|
}
|
|
8558
|
+
quickProviderApi = ui.quickProviders || null
|
|
7787
8559
|
}
|
|
7788
8560
|
if (typeof ctx.inject !== 'function') return function () {}
|
|
7789
8561
|
var fiber = ctx.inject(['slots'], function (scope) {
|
|
@@ -7809,6 +8581,7 @@ window.__ModuleLoader__.load({
|
|
|
7809
8581
|
if (ui && ui.settings) {
|
|
7810
8582
|
delete ui.settings
|
|
7811
8583
|
}
|
|
8584
|
+
quickProviderApi = null
|
|
7812
8585
|
try {
|
|
7813
8586
|
if (fiber && typeof fiber.dispose === 'function') fiber.dispose()
|
|
7814
8587
|
} catch (error) {
|
|
@@ -7844,6 +8617,8 @@ window.__ModuleLoader__.load({
|
|
|
7844
8617
|
teardowns.push(installCopy(ctx, ui))
|
|
7845
8618
|
teardowns.push(installPermissions(ctx, ui))
|
|
7846
8619
|
teardowns.push(installModelPicker(ctx, ui))
|
|
8620
|
+
teardowns.push(installHeroMenu(ctx, ui)) // hero 行的目录/预设弹层:打标记给样式表用
|
|
8621
|
+
teardowns.push(installQuickProviders(ctx, ui)) // 设置页的「快捷供应商」多选弹层
|
|
7847
8622
|
teardowns.push(installAccountFooter(ctx, ui))
|
|
7848
8623
|
teardowns.push(installBanScreen(ctx, ui)) // 账户横条的封号彩蛋(账户弹层把点击交给 ui.ban)
|
|
7849
8624
|
teardowns.push(installThemeFlip()) // 主题翻转瞬间抑制过渡,修掉「先色后样」
|