dsh-hypatia-auto-memory 0.1.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/LICENSE +21 -0
- package/README.md +454 -0
- package/cordis.patch.yml +20 -0
- package/lib/client.js +1081 -0
- package/lib/client.js.map +1 -0
- package/package.json +90 -0
- package/skills/hypatia/SKILL.md +585 -0
- package/skills/hypatia-dream/SKILL.md +246 -0
- package/skills/hypatia-dream/evals/evals.json +92 -0
- package/skills/hypatia-memory/SKILL.md +175 -0
- package/src/auto-approve.js +133 -0
- package/src/cascade.js +190 -0
- package/src/client/SettingsCard.module.css +420 -0
- package/src/client/SettingsCard.tsx +700 -0
- package/src/client/card-form.ts +450 -0
- package/src/client/consolidation-models.ts +69 -0
- package/src/client/css-modules.d.ts +4 -0
- package/src/client/index.tsx +106 -0
- package/src/client/locales.ts +111 -0
- package/src/client/shelves.ts +88 -0
- package/src/client/slot-contract.ts +17 -0
- package/src/collector.js +596 -0
- package/src/config.js +225 -0
- package/src/consolidator.js +592 -0
- package/src/content-policy.js +301 -0
- package/src/housekeeping.js +197 -0
- package/src/hypatia-cli.js +294 -0
- package/src/hypatia-client.js +72 -0
- package/src/hypatia-mcp.js +477 -0
- package/src/index.js +418 -0
- package/src/persisted-session.js +99 -0
- package/src/progress.js +56 -0
- package/src/queue.js +466 -0
- package/src/recall.js +148 -0
- package/src/shelf.js +180 -0
- package/src/skills.js +166 -0
- package/src/state.js +87 -0
- package/src/status.js +53 -0
- package/src/writer.js +320 -0
package/src/cascade.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchical summary cascade — the log₁₆(n) archive the protocol calls for.
|
|
3
|
+
*
|
|
4
|
+
* A span summary (`summary 1`) condenses messages. Sixteen unarchived `summary 1`
|
|
5
|
+
* entries condense into one `summary 2`, sixteen of those into a `summary 3`,
|
|
6
|
+
* and so on. `docs/memory-nolinear.md` argues the point: the alternative — one
|
|
7
|
+
* model call reading ten thousand words and emitting two hundred — loses
|
|
8
|
+
* information in a way nobody can bound. Each tier here compresses only material
|
|
9
|
+
* that a previous tier already distilled, so loss accumulates at the edges of
|
|
10
|
+
* each step instead of in a single collapse. Nothing is deleted; the lower tiers
|
|
11
|
+
* simply leave the hot path, and a reader drills down through `summary` edges
|
|
12
|
+
* when they need the detail.
|
|
13
|
+
*
|
|
14
|
+
* Membership comes from hypatia's own `$not-summaried`, which anti-joins on
|
|
15
|
+
* `statement.tail` for `relation = 'summary'` and returns `created_at ASC` — so
|
|
16
|
+
* "the oldest sixteen entries at this tier that nothing has archived yet" is one
|
|
17
|
+
* query, and the FIFO batch order the protocol specifies falls out of it.
|
|
18
|
+
*
|
|
19
|
+
* @module dsh-hypatia-auto-memory/cascade
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createHash } from 'node:crypto'
|
|
23
|
+
|
|
24
|
+
import { PLUGIN_NAME } from './consolidator.js'
|
|
25
|
+
|
|
26
|
+
/** Tag marking the tier an entry belongs to. */
|
|
27
|
+
export function levelTag(level) {
|
|
28
|
+
return `summary ${level}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Name of a tier-N archive entry.
|
|
33
|
+
*
|
|
34
|
+
* Derived from its members so a replay of the same batch reproduces the same
|
|
35
|
+
* name. It keeps the `sum` prefix every downstream exclusion list already knows
|
|
36
|
+
* (`hypatia-dream`'s among them). The protocol asks for descriptive names; a
|
|
37
|
+
* model-authored title cannot be used as the key because the model may word it
|
|
38
|
+
* differently on a retry and silently fork the entry — so the title goes in the
|
|
39
|
+
* body's first heading instead, and the key stays derivable.
|
|
40
|
+
*/
|
|
41
|
+
export function archiveName(level, members) {
|
|
42
|
+
const digest = createHash('sha256').update([...members].sort().join('\n')).digest('hex').slice(0, 12)
|
|
43
|
+
return `sum${level}-${digest}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** JSE selecting the oldest unarchived entries of one tier within a project. */
|
|
47
|
+
export function notSummarisedQuery(tag, project, limit) {
|
|
48
|
+
// The OBJECT form is the only one that carries `limit`: appending a metadata
|
|
49
|
+
// object to the array form is rejected as "unexpected node in condition
|
|
50
|
+
// context" (asserted in test/integration/cli-contract.test.mjs).
|
|
51
|
+
return JSON.stringify({
|
|
52
|
+
'$not-summaried': [tag, ['$contains', 'scopes', project]],
|
|
53
|
+
limit,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Directive for one archive step. */
|
|
58
|
+
function archiveInstruction(level, count) {
|
|
59
|
+
return [
|
|
60
|
+
`You are the memory archivist of an AI coding assistant, building tier ${level} of a`,
|
|
61
|
+
'hierarchical archive. Below are',
|
|
62
|
+
`${count} summaries from tier ${level - 1}, oldest first.`,
|
|
63
|
+
'',
|
|
64
|
+
'Output EXACTLY one JSON object — no prose, no markdown fences:',
|
|
65
|
+
'{"title": "<short descriptive noun phrase naming what this stretch of work was about>",',
|
|
66
|
+
' "summary": "<markdown: 5-10 terse bullets>"}',
|
|
67
|
+
'',
|
|
68
|
+
'Compress the narrative, not the conclusions. Keep decisions and their reasons, exact',
|
|
69
|
+
'identifiers, and anything still unresolved. Drop chronology and restatement. You are',
|
|
70
|
+
'reading material that was already distilled once, so do not distil it to vagueness —',
|
|
71
|
+
'a bullet that could describe any project is worse than no bullet.',
|
|
72
|
+
'Write in the same language the summaries use.',
|
|
73
|
+
].join('\n')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {{
|
|
78
|
+
* cli: ReturnType<import('./hypatia-client.js').createHypatiaClient>,
|
|
79
|
+
* llm: any,
|
|
80
|
+
* selectRoute: (routes: any) => any,
|
|
81
|
+
* getConfig: () => any,
|
|
82
|
+
* status: import('./status.js').StatusLog,
|
|
83
|
+
* }} deps
|
|
84
|
+
*/
|
|
85
|
+
export function createCascade({ cli, llm, selectRoute, getConfig, status }) {
|
|
86
|
+
/**
|
|
87
|
+
* Ask the model to archive one batch.
|
|
88
|
+
* @returns {Promise<{title: string, summary: string} | undefined>}
|
|
89
|
+
*/
|
|
90
|
+
async function archiveBatch(level, rows, route, timeoutMs) {
|
|
91
|
+
const body = rows.map((row, i) => {
|
|
92
|
+
const data = typeof row?.content?.data === 'string' ? row.content.data : ''
|
|
93
|
+
return `### ${i + 1}. ${row.name}\n${data}`
|
|
94
|
+
}).join('\n\n')
|
|
95
|
+
|
|
96
|
+
const controller = new AbortController()
|
|
97
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
98
|
+
try {
|
|
99
|
+
const { createUserMessage, BlockAssembler } = await import('@deepseek-ai/dsh-llm')
|
|
100
|
+
const assembler = new BlockAssembler()
|
|
101
|
+
for await (const chunk of llm.stream({
|
|
102
|
+
provider: route.provider,
|
|
103
|
+
model: route.model,
|
|
104
|
+
system: 'You produce strict JSON only. You never add prose around it.',
|
|
105
|
+
messages: [createUserMessage({
|
|
106
|
+
content: [{ type: 'text', text: `${archiveInstruction(level, rows.length)}\n\n${body}` }],
|
|
107
|
+
source: { kind: 'plugin', plugin: PLUGIN_NAME },
|
|
108
|
+
})],
|
|
109
|
+
maxTokens: getConfig().consolidation.maxOutputTokens,
|
|
110
|
+
purpose: 'memory-cascade',
|
|
111
|
+
signal: controller.signal,
|
|
112
|
+
})) {
|
|
113
|
+
assembler.push(chunk)
|
|
114
|
+
}
|
|
115
|
+
if (assembler.finish.kind !== 'stop') return undefined
|
|
116
|
+
const text = assembler.blocks().filter((b) => b.type === 'text').map((b) => b.text).join('\n').trim()
|
|
117
|
+
const fence = /^```(?:json)?\s*([\s\S]*?)```\s*$/.exec(text)
|
|
118
|
+
const parsed = JSON.parse(fence ? fence[1].trim() : text)
|
|
119
|
+
const summary = typeof parsed?.summary === 'string' ? parsed.summary.trim() : ''
|
|
120
|
+
if (summary === '') return undefined
|
|
121
|
+
const title = typeof parsed?.title === 'string' && parsed.title.trim() !== ''
|
|
122
|
+
? parsed.title.trim()
|
|
123
|
+
: `Archive tier ${level}`
|
|
124
|
+
return { title, summary }
|
|
125
|
+
} finally {
|
|
126
|
+
clearTimeout(timer)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Run the cascade for one project, from tier 2 upward, until a tier has fewer
|
|
132
|
+
* than `batchSize` unarchived entries.
|
|
133
|
+
*
|
|
134
|
+
* @param {{project: string}} task
|
|
135
|
+
*/
|
|
136
|
+
async function execute(task) {
|
|
137
|
+
const config = getConfig()
|
|
138
|
+
const consolidation = config.consolidation
|
|
139
|
+
if (consolidation.cascade?.enabled === false) return
|
|
140
|
+
const batchSize = consolidation.cascade?.batchSize ?? 16
|
|
141
|
+
const route = selectRoute(consolidation.models)
|
|
142
|
+
if (route === undefined) return
|
|
143
|
+
|
|
144
|
+
// A ceiling on tiers, not an expected depth: reaching tier 8 at 16:1 would
|
|
145
|
+
// mean ~4 billion source entries. It exists so a malformed shelf cannot spin.
|
|
146
|
+
for (let level = 2; level <= 8; level += 1) {
|
|
147
|
+
const sourceTag = levelTag(level - 1)
|
|
148
|
+
let rows
|
|
149
|
+
try {
|
|
150
|
+
rows = await cli.query(notSummarisedQuery(sourceTag, task.project, batchSize))
|
|
151
|
+
} catch (error) {
|
|
152
|
+
status.warn(`cascade query failed at tier ${level}: ${String(error)}`)
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
if (rows.length < batchSize) return
|
|
156
|
+
|
|
157
|
+
const members = rows.map((row) => row?.name).filter((name) => typeof name === 'string')
|
|
158
|
+
if (members.length < batchSize) return
|
|
159
|
+
const name = archiveName(level, members)
|
|
160
|
+
|
|
161
|
+
const existing = await cli.knowledgeGet(name)
|
|
162
|
+
if (existing.found === false) {
|
|
163
|
+
const archived = await archiveBatch(level, rows, route, consolidation.timeoutMs)
|
|
164
|
+
if (archived === undefined) {
|
|
165
|
+
status.warn(`cascade archive produced nothing usable at tier ${level}`)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
await cli.knowledgeCreate(name, {
|
|
169
|
+
data: `# ${archived.title}\n\n${archived.summary}`,
|
|
170
|
+
tags: ['summary', levelTag(level)],
|
|
171
|
+
scopes: [task.project],
|
|
172
|
+
})
|
|
173
|
+
status.count('written')
|
|
174
|
+
}
|
|
175
|
+
// Edges are asserted whether or not the entry is new, so a crash between
|
|
176
|
+
// the two leaves something a replay can finish. Note the consequence when
|
|
177
|
+
// it does crash mid-batch: the members that DID get linked drop out of the
|
|
178
|
+
// next `$not-summaried` result, so the retry archives a different batch
|
|
179
|
+
// under a different name and the first entry keeps only its partial set.
|
|
180
|
+
// Nothing is lost — every member still ends up archived exactly once — but
|
|
181
|
+
// the grouping boundary is not the one the first attempt intended.
|
|
182
|
+
for (const member of members) {
|
|
183
|
+
await cli.statementCreate(name, 'summary', member, { scopes: [task.project] })
|
|
184
|
+
}
|
|
185
|
+
status.info(`cascade: tier ${level} archived ${members.length} entries as ${name}`)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { execute, archiveBatch }
|
|
190
|
+
}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/* Bundle-local implementation of the DSH plugin-settings card treatment. */
|
|
2
|
+
|
|
3
|
+
.card {
|
|
4
|
+
list-style: none;
|
|
5
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
6
|
+
border-radius: 16px;
|
|
7
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
8
|
+
transition: border-color .16s, background .16s;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.card:hover {
|
|
12
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.cardOpen {
|
|
16
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
17
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.header {
|
|
21
|
+
display: flex;
|
|
22
|
+
width: 100%;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: 12px;
|
|
25
|
+
appearance: none;
|
|
26
|
+
border: 0;
|
|
27
|
+
border-radius: 12px;
|
|
28
|
+
background: none;
|
|
29
|
+
color: inherit;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
font: inherit;
|
|
32
|
+
padding: 14px 16px;
|
|
33
|
+
text-align: left;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.header:focus-visible {
|
|
37
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
38
|
+
outline-offset: -2px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.headText {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex: 1;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: 4px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.name {
|
|
50
|
+
color: var(--dsw-alias-label-primary);
|
|
51
|
+
font-size: 15px;
|
|
52
|
+
font-weight: 600;
|
|
53
|
+
line-height: 1.4;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.description {
|
|
57
|
+
color: var(--dsw-alias-label-tertiary);
|
|
58
|
+
font-size: 13px;
|
|
59
|
+
line-height: 1.5;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.pending {
|
|
63
|
+
flex: none;
|
|
64
|
+
border-radius: 999px;
|
|
65
|
+
corner-shape: round;
|
|
66
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
67
|
+
color: var(--dsw-alias-label-secondary);
|
|
68
|
+
font-size: 11px;
|
|
69
|
+
font-weight: 500;
|
|
70
|
+
line-height: 17px;
|
|
71
|
+
padding: 1px 8px;
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.chevron {
|
|
76
|
+
flex: none;
|
|
77
|
+
color: var(--dsw-alias-label-tertiary);
|
|
78
|
+
transition: transform .16s;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.chevronOpen {
|
|
82
|
+
transform: rotate(180deg);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.body {
|
|
86
|
+
margin: 0 16px;
|
|
87
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
88
|
+
padding-bottom: 8px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.status,
|
|
92
|
+
.readOnly,
|
|
93
|
+
.hint,
|
|
94
|
+
.advancedHint,
|
|
95
|
+
.error {
|
|
96
|
+
margin: 0;
|
|
97
|
+
font-size: 12px;
|
|
98
|
+
line-height: 1.5;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.status,
|
|
102
|
+
.readOnly,
|
|
103
|
+
.hint,
|
|
104
|
+
.advancedHint {
|
|
105
|
+
color: var(--dsw-alias-label-tertiary);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.status {
|
|
109
|
+
list-style: none;
|
|
110
|
+
padding: 12px 16px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.readOnly {
|
|
114
|
+
padding-top: 12px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.field {
|
|
118
|
+
display: grid;
|
|
119
|
+
gap: 6px;
|
|
120
|
+
padding: 12px 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.body > .field + .group,
|
|
124
|
+
.body > .group + .field {
|
|
125
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.group {
|
|
129
|
+
padding: 12px 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.groupTitle {
|
|
133
|
+
margin: 0;
|
|
134
|
+
color: var(--dsw-alias-label-primary);
|
|
135
|
+
font-size: 13px;
|
|
136
|
+
font-weight: 500;
|
|
137
|
+
line-height: 1.5;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.limitsTitle {
|
|
141
|
+
margin: 16px 0 0;
|
|
142
|
+
padding-top: 12px;
|
|
143
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
144
|
+
color: var(--dsw-alias-label-primary);
|
|
145
|
+
font-size: 13px;
|
|
146
|
+
font-weight: 500;
|
|
147
|
+
line-height: 1.5;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.limitsTitle + .hint {
|
|
151
|
+
margin-top: 4px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.group > .field:first-of-type {
|
|
155
|
+
padding-top: 12px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.group > .field + .field,
|
|
159
|
+
.pairedFields {
|
|
160
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.fieldHead {
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
gap: 8px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.label {
|
|
170
|
+
flex: 1;
|
|
171
|
+
min-width: 0;
|
|
172
|
+
color: var(--dsw-alias-label-primary);
|
|
173
|
+
font-size: 13px;
|
|
174
|
+
font-weight: 500;
|
|
175
|
+
line-height: 1.5;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.reset {
|
|
179
|
+
border: 0;
|
|
180
|
+
background: none;
|
|
181
|
+
color: var(--dsw-alias-label-secondary);
|
|
182
|
+
cursor: pointer;
|
|
183
|
+
font: inherit;
|
|
184
|
+
font-size: 12px;
|
|
185
|
+
line-height: 1.5;
|
|
186
|
+
padding: 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.reset:hover:not(:disabled) {
|
|
190
|
+
color: var(--dsw-alias-label-primary);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.reset:disabled {
|
|
194
|
+
cursor: default;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.input {
|
|
198
|
+
display: flex;
|
|
199
|
+
box-sizing: border-box;
|
|
200
|
+
min-width: 0;
|
|
201
|
+
width: 100%;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.select {
|
|
205
|
+
box-sizing: border-box;
|
|
206
|
+
width: 100%;
|
|
207
|
+
min-width: 0;
|
|
208
|
+
height: 32px;
|
|
209
|
+
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
210
|
+
border-radius: 8px;
|
|
211
|
+
background: transparent;
|
|
212
|
+
color: var(--dsw-alias-label-primary);
|
|
213
|
+
font: inherit;
|
|
214
|
+
font-size: 13px;
|
|
215
|
+
padding: 0 8px;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.select:disabled {
|
|
219
|
+
opacity: 0.5;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.select:focus-visible {
|
|
223
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
224
|
+
outline-offset: 2px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.warning {
|
|
228
|
+
margin: 0;
|
|
229
|
+
color: var(--dsw-alias-label-error);
|
|
230
|
+
font-size: 12px;
|
|
231
|
+
line-height: 1.5;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.pairedFields {
|
|
235
|
+
display: grid;
|
|
236
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
237
|
+
gap: 16px;
|
|
238
|
+
margin: 4px 0;
|
|
239
|
+
padding: 4px 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.pairedFields .field {
|
|
243
|
+
min-width: 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.switch {
|
|
247
|
+
box-sizing: border-box;
|
|
248
|
+
position: relative;
|
|
249
|
+
width: 36px;
|
|
250
|
+
height: 20px;
|
|
251
|
+
border: 0;
|
|
252
|
+
border-radius: 10px;
|
|
253
|
+
background: var(--dsw-alias-border-l3);
|
|
254
|
+
cursor: pointer;
|
|
255
|
+
padding: 2px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.switchOn {
|
|
259
|
+
background: var(--dsw-alias-brand-primary);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.switch:disabled {
|
|
263
|
+
cursor: default;
|
|
264
|
+
opacity: 0.5;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.switch:focus-visible {
|
|
268
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
269
|
+
outline-offset: 2px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.switchThumb {
|
|
273
|
+
display: block;
|
|
274
|
+
width: 16px;
|
|
275
|
+
height: 16px;
|
|
276
|
+
border-radius: 50%;
|
|
277
|
+
corner-shape: round;
|
|
278
|
+
background: var(--dsw-alias-label-primary-foreground);
|
|
279
|
+
transition: transform 120ms ease;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.switchOn .switchThumb {
|
|
283
|
+
transform: translateX(16px);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.modelSelection {
|
|
287
|
+
display: grid;
|
|
288
|
+
gap: 10px;
|
|
289
|
+
padding: 12px 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.notice {
|
|
293
|
+
margin: 0;
|
|
294
|
+
color: var(--dsw-alias-label-tertiary);
|
|
295
|
+
font-size: 12px;
|
|
296
|
+
line-height: 1.5;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.catalogError {
|
|
300
|
+
display: flex;
|
|
301
|
+
align-items: center;
|
|
302
|
+
justify-content: space-between;
|
|
303
|
+
gap: 12px;
|
|
304
|
+
color: var(--dsw-alias-label-error);
|
|
305
|
+
font-size: 12px;
|
|
306
|
+
line-height: 1.5;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.catalogError button {
|
|
310
|
+
border: 0;
|
|
311
|
+
background: transparent;
|
|
312
|
+
color: var(--dsw-alias-brand-primary);
|
|
313
|
+
cursor: pointer;
|
|
314
|
+
font: inherit;
|
|
315
|
+
padding: 0;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.models {
|
|
319
|
+
display: grid;
|
|
320
|
+
min-width: 0;
|
|
321
|
+
max-height: 280px;
|
|
322
|
+
gap: 6px;
|
|
323
|
+
margin: 0;
|
|
324
|
+
padding: 10px;
|
|
325
|
+
overflow: auto;
|
|
326
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
327
|
+
border-radius: 8px;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.models legend {
|
|
331
|
+
padding: 0 4px;
|
|
332
|
+
color: var(--dsw-alias-label-secondary);
|
|
333
|
+
font-size: 12px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.modelGroup {
|
|
337
|
+
display: grid;
|
|
338
|
+
gap: 6px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.modelGroup + .modelGroup {
|
|
342
|
+
margin-top: 4px;
|
|
343
|
+
border-top: 0.5px solid var(--dsw-alias-border-l3);
|
|
344
|
+
padding-top: 10px;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.providerName {
|
|
348
|
+
padding: 0 6px;
|
|
349
|
+
color: var(--dsw-alias-label-tertiary);
|
|
350
|
+
font-size: 11px;
|
|
351
|
+
font-weight: 500;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.model {
|
|
355
|
+
display: grid;
|
|
356
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
357
|
+
align-items: center;
|
|
358
|
+
gap: 8px;
|
|
359
|
+
min-width: 0;
|
|
360
|
+
border-radius: 6px;
|
|
361
|
+
cursor: pointer;
|
|
362
|
+
padding: 6px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.model:hover {
|
|
366
|
+
background: var(--dsw-alias-bg-layer-4);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.modelName,
|
|
370
|
+
.route {
|
|
371
|
+
display: block;
|
|
372
|
+
overflow: hidden;
|
|
373
|
+
text-overflow: ellipsis;
|
|
374
|
+
white-space: nowrap;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.modelName {
|
|
378
|
+
color: var(--dsw-alias-label-primary);
|
|
379
|
+
font-size: 13px;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.route {
|
|
383
|
+
margin-top: 2px;
|
|
384
|
+
color: var(--dsw-alias-label-tertiary);
|
|
385
|
+
font-size: 11px;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.unavailable {
|
|
389
|
+
color: var(--dsw-alias-label-tertiary);
|
|
390
|
+
font-size: 11px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.error {
|
|
394
|
+
color: var(--dsw-alias-label-error);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.advancedHint {
|
|
398
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
399
|
+
padding-top: 12px;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.footer {
|
|
403
|
+
display: flex;
|
|
404
|
+
align-items: center;
|
|
405
|
+
justify-content: flex-end;
|
|
406
|
+
gap: 8px;
|
|
407
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
408
|
+
padding: 12px 0 4px;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
@media (max-width: 480px) {
|
|
412
|
+
.pairedFields {
|
|
413
|
+
grid-template-columns: minmax(0, 1fr);
|
|
414
|
+
gap: 0;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.pairedFields .field + .field {
|
|
418
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
419
|
+
}
|
|
420
|
+
}
|