slotlock 0.6.3 → 0.8.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 +511 -0
- package/CONTRIBUTING.md +12 -5
- package/README.md +50 -1551
- package/SECURITY.md +5 -2
- package/SPEC.md +1 -1
- package/agent-server.d.ts +160 -8
- package/agent-server.js +559 -44
- package/agent-store-backend.d.ts +34 -1
- package/agent-store-backend.js +1399 -38
- package/api-keys.d.ts +69 -3
- package/api-keys.js +217 -28
- package/application-api.d.ts +56 -3
- package/application-api.js +826 -15
- package/approvals.d.ts +88 -0
- package/approvals.js +293 -0
- package/assistant.d.ts +70 -1
- package/assistant.js +478 -50
- package/billing.d.ts +114 -0
- package/billing.js +258 -0
- package/config.d.ts +25 -4
- package/config.js +72 -3
- package/dashboard-assistant-ui.d.ts +21 -0
- package/dashboard-assistant-ui.js +438 -0
- package/dashboard-icon.d.ts +1 -0
- package/dashboard-icon.js +5 -0
- package/dashboard-markdown.d.ts +2 -0
- package/dashboard-markdown.js +157 -0
- package/dashboard-settings-ui.d.ts +1 -0
- package/dashboard-settings-ui.js +78 -0
- package/dashboard.d.ts +227 -7
- package/dashboard.js +2313 -247
- package/ddl.d.ts +51 -12
- package/ddl.js +2566 -54
- package/event-feed.d.ts +30 -4
- package/event-feed.js +171 -19
- package/index.d.ts +15 -4
- package/index.js +8 -2
- package/maintenance.d.ts +10 -0
- package/maintenance.js +16 -2
- package/metrics.d.ts +41 -0
- package/metrics.js +22 -0
- package/openapi.json +14675 -5957
- package/otel.d.ts +86 -0
- package/otel.js +273 -0
- package/overage.d.ts +33 -0
- package/overage.js +115 -0
- package/package.json +1 -1
- package/partner-billing.d.ts +81 -0
- package/partner-billing.js +333 -0
- package/partner-names.d.ts +8 -0
- package/partner-names.js +10 -0
- package/partners.d.ts +147 -0
- package/partners.js +375 -0
- package/plans.d.ts +92 -0
- package/plans.js +115 -0
- package/quotas.d.ts +108 -0
- package/quotas.js +258 -0
- package/rate-limit.d.ts +21 -1
- package/rate-limit.js +82 -15
- package/resource-profile.d.ts +135 -0
- package/resource-profile.js +263 -0
- package/rest-api.d.ts +2 -0
- package/rest-api.js +68 -3
- package/sandbox.d.ts +70 -0
- package/sandbox.js +295 -0
- package/self-host.d.ts +33 -5
- package/self-host.js +950 -22
- package/slotlock.manifest.json +1 -1
- package/store.d.ts +373 -4
- package/store.js +1212 -118
- package/teams.d.ts +25 -0
- package/teams.js +63 -0
- package/timezone.js +13 -1
- package/types.d.ts +95 -1
- package/usage.d.ts +106 -0
- package/usage.js +238 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
// The dashboard assistant's words, styles and browser script: how a tool step and a proposed change
|
|
2
|
+
// are named, the chat's CSS, and the script that streams a reply into the page. dashboard.ts renders
|
|
3
|
+
// the page; this keeps the chat's presentation in one place.
|
|
4
|
+
const STEP_WORDS = Object.freeze({
|
|
5
|
+
slotlock_list_resources: ['Reading resources', 'Read resources', 'read resources'],
|
|
6
|
+
slotlock_get_free_busy: ['Checking free and busy time', 'Checked free and busy time', 'check free and busy time'],
|
|
7
|
+
slotlock_find_next_available: ['Finding the next free time', 'Found the next free time', 'find the next free time'],
|
|
8
|
+
slotlock_find_available_slots: ['Checking availability', 'Checked availability', 'check availability'],
|
|
9
|
+
slotlock_get_pool_availability: ['Checking the pool', 'Checked the pool', 'check the pool'],
|
|
10
|
+
slotlock_get_event: ['Reading a booking', 'Read a booking', 'read the booking'],
|
|
11
|
+
slotlock_list_events: ['Reading bookings', 'Read bookings', 'read bookings'],
|
|
12
|
+
});
|
|
13
|
+
/** A tool step in plain words: "Checking availability…", "Checked availability", "Couldn't check availability". */
|
|
14
|
+
export function slotlockAssistantStepLabel(name, title, state, write = false) {
|
|
15
|
+
const words = STEP_WORDS[name];
|
|
16
|
+
if (write) {
|
|
17
|
+
if (state === 'run')
|
|
18
|
+
return 'Preparing a change…';
|
|
19
|
+
if (state === 'ok')
|
|
20
|
+
return 'Prepared a change for you to approve';
|
|
21
|
+
return "Couldn't prepare the change";
|
|
22
|
+
}
|
|
23
|
+
if (words)
|
|
24
|
+
return state === 'run' ? `${words[0]}…` : state === 'ok' ? words[1] : `Couldn't ${words[2]}`;
|
|
25
|
+
const lower = title.charAt(0).toLowerCase() + title.slice(1);
|
|
26
|
+
return state === 'run' ? `${title}…` : state === 'ok' ? title : `Couldn't ${lower}`;
|
|
27
|
+
}
|
|
28
|
+
/** The verb a proposed change's card is approved with, and whether it removes something. */
|
|
29
|
+
export function slotlockAssistantActionVerb(operation) {
|
|
30
|
+
switch (operation) {
|
|
31
|
+
case 'slotlock_create_event':
|
|
32
|
+
case 'slotlock_book_pool':
|
|
33
|
+
case 'slotlock_book_together':
|
|
34
|
+
return { verb: 'Book', busy: 'Booking…', done: 'Booked', danger: false };
|
|
35
|
+
case 'slotlock_confirm_slot':
|
|
36
|
+
return { verb: 'Book', busy: 'Booking…', done: 'Booked', danger: false };
|
|
37
|
+
case 'slotlock_reschedule':
|
|
38
|
+
return { verb: 'Move', busy: 'Moving…', done: 'Moved', danger: false };
|
|
39
|
+
case 'slotlock_update_event':
|
|
40
|
+
return { verb: 'Change', busy: 'Changing…', done: 'Changed', danger: false };
|
|
41
|
+
case 'slotlock_cancel':
|
|
42
|
+
return { verb: 'Cancel booking', busy: 'Cancelling…', done: 'Cancelled', danger: true };
|
|
43
|
+
case 'slotlock_delete_event':
|
|
44
|
+
return { verb: 'Delete booking', busy: 'Deleting…', done: 'Deleted', danger: true };
|
|
45
|
+
case 'slotlock_hold_slots':
|
|
46
|
+
return { verb: 'Hold', busy: 'Holding…', done: 'Held', danger: false };
|
|
47
|
+
case 'slotlock_release_slots':
|
|
48
|
+
return { verb: 'Release', busy: 'Releasing…', done: 'Released', danger: true };
|
|
49
|
+
default:
|
|
50
|
+
return { verb: 'Approve', busy: 'Running…', done: 'Done', danger: false };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** The chat's styles: one centred column, the composer docked under it, quiet steps and cards. */
|
|
54
|
+
export const SLOTLOCK_ASSISTANT_CSS = `
|
|
55
|
+
.assistant-page { display: grid; }
|
|
56
|
+
.assistant { position: relative; display: flex; flex-direction: column; width: min(100%, 720px); margin: 0 auto; min-height: calc(100dvh - 180px); }
|
|
57
|
+
.assistant .thread { flex: 1; }
|
|
58
|
+
.assistant[data-empty] { justify-content: center; padding-bottom: 12vh; }
|
|
59
|
+
.assistant[data-empty] .thread { display: none; }
|
|
60
|
+
.assistant[data-empty] .dock { order: 1; position: static; padding: 0; background: none; }
|
|
61
|
+
.assistant[data-empty] .assistant-start { order: 2; padding-top: var(--space-3); }
|
|
62
|
+
.assistant:not([data-empty]) .assistant-start { display: none; }
|
|
63
|
+
.chips { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); }
|
|
64
|
+
.chips form { margin: 0; }
|
|
65
|
+
.chip { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 var(--space-3); border-radius: var(--radius-full); border: 1px solid var(--line); background: var(--surface); color: var(--ink-2); font: inherit; font-size: var(--text-md); cursor: pointer; transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease); }
|
|
66
|
+
.chip:hover { background: var(--hover); border-color: var(--line-strong); color: var(--ink); }
|
|
67
|
+
.thread { list-style: none; margin: 0; padding: var(--space-2) 0 var(--space-6); display: grid; gap: var(--space-6); align-content: start; }
|
|
68
|
+
.turn { margin: 0; min-width: 0; }
|
|
69
|
+
.turn.mine { justify-self: end; max-width: 80%; }
|
|
70
|
+
.bubble { padding: 10px 14px; border-radius: 18px; background: var(--sunken); color: var(--ink); font-size: var(--text-lg); line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
71
|
+
.turn.bot { justify-self: stretch; }
|
|
72
|
+
.prose { max-width: 68ch; color: var(--ink); font-size: var(--text-lg); line-height: 1.6; overflow-wrap: anywhere; }
|
|
73
|
+
.prose > :first-child { margin-top: 0; }
|
|
74
|
+
.prose > :last-child { margin-bottom: 0; }
|
|
75
|
+
.prose p { margin: 0 0 var(--space-3); color: inherit; }
|
|
76
|
+
.prose strong { font-weight: 600; }
|
|
77
|
+
.prose h4 { margin: var(--space-4) 0 var(--space-2); font-size: var(--text-lg); font-weight: 600; }
|
|
78
|
+
.prose ul, .prose ol { margin: 0 0 var(--space-3); padding-left: 20px; }
|
|
79
|
+
.prose li { margin: 4px 0; }
|
|
80
|
+
.prose li > ul, .prose li > ol { margin: 4px 0 0; }
|
|
81
|
+
.prose a { color: var(--accent); text-underline-offset: 2px; }
|
|
82
|
+
.prose code { font: 400 12.5px/1.4 var(--mono); background: var(--sunken); border-radius: 4px; padding: 1px 5px; }
|
|
83
|
+
.prose pre { margin: 0 0 var(--space-3); padding: var(--space-3); background: var(--sunken); border: 1px solid var(--line); border-radius: var(--radius); overflow-x: auto; }
|
|
84
|
+
.prose pre code { background: none; padding: 0; font-size: 12.5px; }
|
|
85
|
+
.prose blockquote { margin: 0 0 var(--space-3); padding-left: var(--space-3); border-left: 2px solid var(--line-strong); color: var(--ink-2); }
|
|
86
|
+
.prose hr { border: 0; border-top: 1px solid var(--line); margin: var(--space-4) 0; }
|
|
87
|
+
.md-table { overflow-x: auto; margin: 0 0 var(--space-3); }
|
|
88
|
+
.md-table table { border-collapse: collapse; font-size: var(--text-md); min-width: 50%; }
|
|
89
|
+
.md-table table { width: auto; }
|
|
90
|
+
.md-table th { font: 600 var(--text-md)/1.4 var(--font); text-transform: none; letter-spacing: 0; background: none; text-align: left; color: var(--ink-2); padding: 6px 16px 6px 0; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
|
91
|
+
.md-table td { padding: 6px 16px 6px 0; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
|
|
92
|
+
.md-table tr:last-child td { border-bottom: 0; }
|
|
93
|
+
.change details.more { margin: 0; }
|
|
94
|
+
.change details.more > summary { cursor: pointer; color: var(--ink-3); font-size: var(--text-sm); list-style: none; }
|
|
95
|
+
.change details.more > summary::-webkit-details-marker { display: none; }
|
|
96
|
+
.change details.more > summary:hover { color: var(--ink-2); }
|
|
97
|
+
.change details.more[open] > summary { margin-bottom: var(--space-2); }
|
|
98
|
+
.turn.steps { justify-self: start; }
|
|
99
|
+
details.steps > summary { list-style: none; display: inline-flex; align-items: center; gap: 6px; cursor: pointer; color: var(--ink-3); font-size: var(--text-md); padding: 2px 0; }
|
|
100
|
+
details.steps > summary::-webkit-details-marker { display: none; }
|
|
101
|
+
details.steps > summary::after { content: ""; width: 6px; height: 6px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); transition: transform var(--fast) var(--ease); margin-left: 2px; }
|
|
102
|
+
details.steps[open] > summary::after { transform: rotate(45deg); }
|
|
103
|
+
details.steps > summary:hover { color: var(--ink-2); }
|
|
104
|
+
details.steps.has-bad > summary::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--bad); }
|
|
105
|
+
.step-list { list-style: none; margin: var(--space-2) 0 0; padding: 0 0 0 var(--space-3); border-left: 1px solid var(--line); display: grid; gap: 6px; }
|
|
106
|
+
.step { display: flex; align-items: baseline; gap: var(--space-2); font-size: var(--text-md); color: var(--ink-2); }
|
|
107
|
+
.step::before { content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--good); transform: translateY(-1px); }
|
|
108
|
+
.step.bad::before { background: var(--bad); }
|
|
109
|
+
.step.run::before { background: var(--ink-3); }
|
|
110
|
+
.step .why { color: var(--ink-3); font-size: var(--text-sm); }
|
|
111
|
+
.live-steps { display: grid; gap: 6px; }
|
|
112
|
+
.live-steps .step.run, .thinking { background: linear-gradient(90deg, var(--ink-3) 0%, var(--ink) 50%, var(--ink-3) 100%); background-size: 200% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; animation: assistant-shimmer 1.6s linear infinite; }
|
|
113
|
+
.thinking { font-size: var(--text-md); justify-self: start; }
|
|
114
|
+
@keyframes assistant-shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }
|
|
115
|
+
.prose.streaming > :last-child::after { content: ""; display: inline-block; width: 8px; height: 8px; margin-left: 4px; border-radius: 50%; background: var(--ink-3); vertical-align: baseline; animation: assistant-pulse 1s ease-in-out infinite; }
|
|
116
|
+
@keyframes assistant-pulse { 50% { opacity: 0.25; } }
|
|
117
|
+
.turn.note { color: var(--ink-3); font-size: var(--text-md); }
|
|
118
|
+
.turn.error { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: 10px 12px; border: 1px solid var(--bad-line); background: var(--bad-wash); border-radius: var(--radius); font-size: var(--text-md); color: var(--ink); }
|
|
119
|
+
.turn.error::before { content: ""; flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--bad); }
|
|
120
|
+
.turn.error > span { flex: 1; }
|
|
121
|
+
.turn.error form, .turn.note form { margin: 0; }
|
|
122
|
+
.text-button { appearance: none; border: 0; background: none; padding: 0; font: inherit; font-size: var(--text-md); color: var(--accent); cursor: pointer; }
|
|
123
|
+
.text-button:hover { text-decoration: underline; text-underline-offset: 2px; }
|
|
124
|
+
.turn.action { justify-self: start; width: min(100%, 560px); }
|
|
125
|
+
.change { padding: var(--space-4) var(--space-5); background: var(--surface); border: 1px solid var(--line-strong); border-radius: var(--radius-lg); display: grid; gap: var(--space-3); }
|
|
126
|
+
.change-title { margin: 0; font-size: var(--text-lg); font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
|
|
127
|
+
.change .facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px var(--space-4); margin: 0; font-size: var(--text-md); }
|
|
128
|
+
.change .facts dt { color: var(--ink-3); font-size: var(--text-sm); padding-top: 1px; }
|
|
129
|
+
.change .facts dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }
|
|
130
|
+
.change .facts dd pre { margin: 0; white-space: pre-wrap; font: 400 12px/1.5 var(--mono); }
|
|
131
|
+
.change .facts .was { color: var(--ink-3); text-decoration: line-through; }
|
|
132
|
+
.change-buttons { display: flex; gap: var(--space-2); flex-wrap: wrap; }
|
|
133
|
+
.change-buttons form { margin: 0; }
|
|
134
|
+
.change-buttons button { height: 32px; }
|
|
135
|
+
button.danger { background: var(--bad); color: var(--bad-ink); border-color: var(--bad); }
|
|
136
|
+
button.danger:hover { filter: brightness(1.08); }
|
|
137
|
+
button.ghost { background: transparent; border-color: transparent; color: var(--ink-2); }
|
|
138
|
+
button.ghost:hover { background: var(--hover); color: var(--ink); }
|
|
139
|
+
.receipt { margin: 0; display: flex; align-items: baseline; gap: var(--space-2); font-size: var(--text-md); color: var(--ink-2); }
|
|
140
|
+
.receipt::before { content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--ink-3); transform: translateY(-1px); }
|
|
141
|
+
.receipt.ok { color: var(--ink); }
|
|
142
|
+
.receipt.ok::before { background: var(--good); }
|
|
143
|
+
.receipt.bad::before { background: var(--bad); }
|
|
144
|
+
.receipt .why { color: var(--ink-3); }
|
|
145
|
+
.dock { position: sticky; bottom: 0; padding: var(--space-6) 0 var(--space-6); background: linear-gradient(to bottom, transparent, var(--canvas) 24px); z-index: 2; }
|
|
146
|
+
.composer { display: grid; margin: 0; background: var(--surface); border: 1px solid var(--line); border-radius: 16px; transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease); }
|
|
147
|
+
.composer:focus-within { border-color: var(--line-strong); box-shadow: 0 0 0 3px var(--ring); }
|
|
148
|
+
.composer textarea { display: block; width: 100%; min-height: 26px; max-height: 200px; resize: none; border: 0; outline: 0; background: transparent; padding: 14px 16px 0; color: var(--ink); font: inherit; font-size: var(--text-lg); line-height: 1.5; box-shadow: none; }
|
|
149
|
+
.composer textarea:focus { box-shadow: none; border: 0; }
|
|
150
|
+
.composer-bar { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2); padding: var(--space-2); }
|
|
151
|
+
.composer-bar .count { margin-right: auto; padding-left: var(--space-2); font-size: var(--text-sm); color: var(--ink-3); }
|
|
152
|
+
.composer-bar .count.over { color: var(--warn); }
|
|
153
|
+
.send { display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0; border-radius: 50%; border: 0; background: var(--button); color: var(--button-ink); cursor: pointer; transition: opacity var(--fast) var(--ease), background var(--fast) var(--ease); }
|
|
154
|
+
.send:hover { background: var(--button-hover); }
|
|
155
|
+
.send:disabled { opacity: 0.4; cursor: default; }
|
|
156
|
+
.send svg { width: 16px; height: 16px; }
|
|
157
|
+
.send .stop { display: none; }
|
|
158
|
+
.assistant.streaming .send .arrow { display: none; }
|
|
159
|
+
.assistant.streaming .send .stop { display: block; }
|
|
160
|
+
.jump { position: absolute; left: 50%; transform: translate(-50%, 0); top: -8px; width: 32px; height: 32px; padding: 0; border-radius: 50%; border: 1px solid var(--line); background: var(--surface); color: var(--ink-2); display: grid; place-items: center; cursor: pointer; box-shadow: var(--pop-shadow); }
|
|
161
|
+
.jump[hidden] { display: none; }
|
|
162
|
+
.new-chat svg { width: 14px; height: 14px; }
|
|
163
|
+
.assistant-page[data-empty] .new-chat { display: none; }
|
|
164
|
+
@media (prefers-reduced-motion: reduce) {
|
|
165
|
+
.live-steps .step.run, .thinking { animation: none; color: var(--ink-3); background: none; }
|
|
166
|
+
.prose.streaming > :last-child::after { animation: none; }
|
|
167
|
+
}
|
|
168
|
+
@media (max-width: 760px) {
|
|
169
|
+
.assistant { min-height: calc(100dvh - 140px); }
|
|
170
|
+
.assistant[data-empty] { justify-content: flex-end; padding-bottom: 0; }
|
|
171
|
+
.assistant[data-empty] .assistant-start { order: 1; padding: 0 0 var(--space-3); }
|
|
172
|
+
.assistant[data-empty] .dock { order: 2; position: sticky; padding-bottom: max(12px, env(safe-area-inset-bottom)); }
|
|
173
|
+
.chips { flex-wrap: nowrap; justify-content: flex-start; overflow-x: auto; max-width: 100%; padding-bottom: 2px; scrollbar-width: none; }
|
|
174
|
+
.chips::-webkit-scrollbar { display: none; }
|
|
175
|
+
.chip { flex: none; }
|
|
176
|
+
.turn.mine { max-width: 88%; }
|
|
177
|
+
.md-table table { display: table; }
|
|
178
|
+
.md-table thead { display: table-header-group; }
|
|
179
|
+
.md-table tbody { display: table-row-group; }
|
|
180
|
+
.md-table tr { display: table-row; padding: 0; border: 0; }
|
|
181
|
+
.md-table th, .md-table td { display: table-cell; padding: 6px 12px 6px 0; }
|
|
182
|
+
.md-table td { border-bottom: 1px solid var(--line-soft); }
|
|
183
|
+
.composer textarea { font-size: 16px; }
|
|
184
|
+
.dock { padding-bottom: max(12px, env(safe-area-inset-bottom)); }
|
|
185
|
+
.change { padding: var(--space-4); }
|
|
186
|
+
.change .facts { grid-template-columns: minmax(0, 1fr); gap: 2px; }
|
|
187
|
+
.change .facts dd { margin-bottom: var(--space-2); }
|
|
188
|
+
.change-buttons form { flex: 1; }
|
|
189
|
+
.change-buttons button { width: 100%; height: 40px; }
|
|
190
|
+
}
|
|
191
|
+
`;
|
|
192
|
+
/**
|
|
193
|
+
* The browser side of the chat, appended to app.js: Enter sends, the box grows, a write chip fills
|
|
194
|
+
* the box instead of sending, and a reply streams in from `<root>/assistant/stream` (newline-
|
|
195
|
+
* delimited JSON: text deltas, steps, then the turn as the server renders it). Without it the
|
|
196
|
+
* page still works: every form posts and the page reloads at the latest message.
|
|
197
|
+
* `markdownSource` is renderSlotlockMarkdown's source, so a reply renders as it will once stored.
|
|
198
|
+
*/
|
|
199
|
+
export function slotlockAssistantScript(markdownSource) {
|
|
200
|
+
return `
|
|
201
|
+
(function () {
|
|
202
|
+
var page = document.querySelector('[data-assistant]');
|
|
203
|
+
if (!page || typeof fetch !== 'function' || typeof TextDecoder !== 'function') return;
|
|
204
|
+
var __name = function (fn) { return fn; };
|
|
205
|
+
var renderMarkdown = (${markdownSource});
|
|
206
|
+
var form = page.querySelector('form.composer');
|
|
207
|
+
var box = form ? form.querySelector('textarea') : null;
|
|
208
|
+
var send = form ? form.querySelector('button.send') : null;
|
|
209
|
+
var thread = page.querySelector('ol.thread');
|
|
210
|
+
var jump = page.querySelector('button.jump');
|
|
211
|
+
var count = form ? form.querySelector('.count') : null;
|
|
212
|
+
var shell = document.querySelector('.assistant-page');
|
|
213
|
+
if (!form || !box || !send || !thread) return;
|
|
214
|
+
var max = Number(box.getAttribute('maxlength')) || 2000;
|
|
215
|
+
// Two rows without script; one that grows with it.
|
|
216
|
+
box.setAttribute('rows', '1');
|
|
217
|
+
var busy = null;
|
|
218
|
+
|
|
219
|
+
function grow() {
|
|
220
|
+
box.style.height = 'auto';
|
|
221
|
+
box.style.height = Math.min(box.scrollHeight, 200) + 'px';
|
|
222
|
+
}
|
|
223
|
+
function update() {
|
|
224
|
+
if (!busy) send.disabled = box.value.trim() === '';
|
|
225
|
+
if (count) {
|
|
226
|
+
var left = box.value.length;
|
|
227
|
+
count.hidden = left < max - 200;
|
|
228
|
+
count.textContent = left + ' / ' + max;
|
|
229
|
+
count.classList.toggle('over', left >= max);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function nearBottom() {
|
|
233
|
+
return window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 80;
|
|
234
|
+
}
|
|
235
|
+
function follow(was) {
|
|
236
|
+
if (was) window.scrollTo(0, document.documentElement.scrollHeight);
|
|
237
|
+
else if (jump) jump.hidden = false;
|
|
238
|
+
}
|
|
239
|
+
if (jump) {
|
|
240
|
+
jump.addEventListener('click', function () {
|
|
241
|
+
window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' });
|
|
242
|
+
jump.hidden = true;
|
|
243
|
+
});
|
|
244
|
+
window.addEventListener('scroll', function () { if (nearBottom()) jump.hidden = true; }, { passive: true });
|
|
245
|
+
}
|
|
246
|
+
function item(className) {
|
|
247
|
+
var li = document.createElement('li');
|
|
248
|
+
li.className = 'turn ' + className;
|
|
249
|
+
thread.appendChild(li);
|
|
250
|
+
return li;
|
|
251
|
+
}
|
|
252
|
+
function setOnce(value) {
|
|
253
|
+
if (!value) return;
|
|
254
|
+
var field = form.querySelector('input[name="once"]');
|
|
255
|
+
if (field) field.value = value;
|
|
256
|
+
}
|
|
257
|
+
function stopped(reason) {
|
|
258
|
+
page.classList.remove('streaming');
|
|
259
|
+
send.setAttribute('aria-label', 'Send');
|
|
260
|
+
busy = null;
|
|
261
|
+
update();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
box.addEventListener('input', function () { grow(); update(); });
|
|
265
|
+
box.addEventListener('keydown', function (event) {
|
|
266
|
+
if (event.key !== 'Enter' || event.shiftKey || event.isComposing || event.keyCode === 229) return;
|
|
267
|
+
event.preventDefault();
|
|
268
|
+
if (!busy && box.value.trim() !== '') form.requestSubmit();
|
|
269
|
+
});
|
|
270
|
+
for (var chip of page.querySelectorAll('button[data-fill]')) {
|
|
271
|
+
chip.addEventListener('click', function (event) {
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
box.value = this.getAttribute('data-fill') || '';
|
|
274
|
+
grow();
|
|
275
|
+
update();
|
|
276
|
+
box.focus();
|
|
277
|
+
box.setSelectionRange(box.value.length, box.value.length);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
for (var decide of document.querySelectorAll('.change-buttons form')) {
|
|
281
|
+
decide.addEventListener('submit', function () {
|
|
282
|
+
var buttons = this.closest('.change-buttons').querySelectorAll('button');
|
|
283
|
+
for (var b of buttons) b.disabled = true;
|
|
284
|
+
var own = this.querySelector('button');
|
|
285
|
+
if (own && own.getAttribute('data-busy')) own.textContent = own.getAttribute('data-busy');
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
send.addEventListener('click', function (event) {
|
|
290
|
+
if (!busy) return;
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
busy.abort();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
form.addEventListener('submit', function (event) {
|
|
296
|
+
var text = box.value.trim();
|
|
297
|
+
if (busy || text === '') { event.preventDefault(); return; }
|
|
298
|
+
event.preventDefault();
|
|
299
|
+
var controller = new AbortController();
|
|
300
|
+
busy = controller;
|
|
301
|
+
var body = new URLSearchParams(new FormData(form));
|
|
302
|
+
body.set('message', text);
|
|
303
|
+
if (page.hasAttribute('data-empty')) {
|
|
304
|
+
page.removeAttribute('data-empty');
|
|
305
|
+
if (shell) shell.removeAttribute('data-empty');
|
|
306
|
+
}
|
|
307
|
+
page.classList.add('streaming');
|
|
308
|
+
send.disabled = false;
|
|
309
|
+
send.setAttribute('aria-label', 'Stop');
|
|
310
|
+
var was = true;
|
|
311
|
+
var mine = item('mine');
|
|
312
|
+
var bubble = document.createElement('div');
|
|
313
|
+
bubble.className = 'bubble';
|
|
314
|
+
bubble.textContent = text;
|
|
315
|
+
mine.appendChild(bubble);
|
|
316
|
+
box.value = '';
|
|
317
|
+
box.setAttribute('placeholder', 'Reply…');
|
|
318
|
+
grow();
|
|
319
|
+
var added = [mine];
|
|
320
|
+
var thinking = item('note thinking');
|
|
321
|
+
thinking.textContent = 'Thinking';
|
|
322
|
+
added.push(thinking);
|
|
323
|
+
var prose = null;
|
|
324
|
+
var proseText = '';
|
|
325
|
+
var steps = null;
|
|
326
|
+
var stepNodes = {};
|
|
327
|
+
var frame = 0;
|
|
328
|
+
follow(was);
|
|
329
|
+
function draw() {
|
|
330
|
+
frame = 0;
|
|
331
|
+
if (prose) prose.innerHTML = renderMarkdown(proseText);
|
|
332
|
+
}
|
|
333
|
+
function clearThinking() {
|
|
334
|
+
if (thinking && thinking.parentNode) thinking.parentNode.removeChild(thinking);
|
|
335
|
+
thinking = null;
|
|
336
|
+
}
|
|
337
|
+
function onEvent(e) {
|
|
338
|
+
was = nearBottom();
|
|
339
|
+
if (e.t === 'text') {
|
|
340
|
+
clearThinking();
|
|
341
|
+
if (!prose) {
|
|
342
|
+
var li = item('bot');
|
|
343
|
+
added.push(li);
|
|
344
|
+
prose = document.createElement('div');
|
|
345
|
+
prose.className = 'prose streaming';
|
|
346
|
+
li.appendChild(prose);
|
|
347
|
+
proseText = '';
|
|
348
|
+
steps = null;
|
|
349
|
+
}
|
|
350
|
+
proseText += e.d;
|
|
351
|
+
if (!frame) frame = requestAnimationFrame(draw);
|
|
352
|
+
} else if (e.t === 'step') {
|
|
353
|
+
clearThinking();
|
|
354
|
+
if (prose) { if (frame) { cancelAnimationFrame(frame); draw(); } prose.classList.remove('streaming'); prose = null; }
|
|
355
|
+
if (!steps) {
|
|
356
|
+
var holder = item('steps');
|
|
357
|
+
added.push(holder);
|
|
358
|
+
steps = document.createElement('div');
|
|
359
|
+
steps.className = 'live-steps';
|
|
360
|
+
holder.appendChild(steps);
|
|
361
|
+
}
|
|
362
|
+
var node = stepNodes[e.id];
|
|
363
|
+
if (!node) {
|
|
364
|
+
node = document.createElement('div');
|
|
365
|
+
stepNodes[e.id] = node;
|
|
366
|
+
steps.appendChild(node);
|
|
367
|
+
}
|
|
368
|
+
node.className = 'step ' + e.s;
|
|
369
|
+
node.textContent = e.label;
|
|
370
|
+
} else if (e.t === 'done' || e.t === 'error') {
|
|
371
|
+
clearThinking();
|
|
372
|
+
if (frame) cancelAnimationFrame(frame);
|
|
373
|
+
for (var n of added) if (n.parentNode) n.parentNode.removeChild(n);
|
|
374
|
+
added = [];
|
|
375
|
+
thread.insertAdjacentHTML('beforeend', e.html || '');
|
|
376
|
+
setOnce(e.once);
|
|
377
|
+
}
|
|
378
|
+
follow(was);
|
|
379
|
+
}
|
|
380
|
+
fetch(page.getAttribute('data-stream'), {
|
|
381
|
+
method: 'POST',
|
|
382
|
+
body: body,
|
|
383
|
+
headers: { accept: 'application/x-ndjson' },
|
|
384
|
+
credentials: 'same-origin',
|
|
385
|
+
signal: controller.signal,
|
|
386
|
+
}).then(function (response) {
|
|
387
|
+
var type = response.headers.get('content-type') || '';
|
|
388
|
+
if (!response.body || type.indexOf('application/x-ndjson') !== 0) {
|
|
389
|
+
location.assign(page.getAttribute('data-page') + '#latest');
|
|
390
|
+
location.reload();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
var reader = response.body.getReader();
|
|
394
|
+
var decoder = new TextDecoder();
|
|
395
|
+
var buffer = '';
|
|
396
|
+
function pump() {
|
|
397
|
+
return reader.read().then(function (chunk) {
|
|
398
|
+
if (chunk.done) {
|
|
399
|
+
if (buffer.trim()) onEvent(JSON.parse(buffer));
|
|
400
|
+
stopped();
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
404
|
+
var lines = buffer.split('\\n');
|
|
405
|
+
buffer = lines.pop() || '';
|
|
406
|
+
for (var line of lines) if (line.trim()) onEvent(JSON.parse(line));
|
|
407
|
+
return pump();
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
return pump();
|
|
411
|
+
}).catch(function () {
|
|
412
|
+
clearThinking();
|
|
413
|
+
if (controller.signal.aborted) {
|
|
414
|
+
if (prose) prose.classList.remove('streaming');
|
|
415
|
+
var note = item('note');
|
|
416
|
+
note.textContent = 'Stopped';
|
|
417
|
+
stopped();
|
|
418
|
+
// The server keeps what it had; the page it renders next has a fresh form.
|
|
419
|
+
setTimeout(function () { location.assign(page.getAttribute('data-page') + '#latest'); location.reload(); }, 900);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
for (var n of added) if (n.parentNode && n !== mine) n.parentNode.removeChild(n);
|
|
423
|
+
if (mine.parentNode) mine.parentNode.removeChild(mine);
|
|
424
|
+
box.value = text;
|
|
425
|
+
grow();
|
|
426
|
+
var fail = item('error');
|
|
427
|
+
var why = document.createElement('span');
|
|
428
|
+
why.textContent = "Couldn't send. Check the connection and try again.";
|
|
429
|
+
fail.appendChild(why);
|
|
430
|
+
stopped();
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
grow();
|
|
434
|
+
update();
|
|
435
|
+
if (location.hash === '#latest' || !page.hasAttribute('data-empty')) window.scrollTo(0, document.documentElement.scrollHeight);
|
|
436
|
+
})();
|
|
437
|
+
`;
|
|
438
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SLOTLOCK_DASHBOARD_ICON_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\"><rect width=\"64\" height=\"64\" rx=\"14\" fill=\"#111110\"/><g transform=\"translate(12.2 14.1) scale(0.465)\"><path fill=\"#F6F4EE\" d=\"M13.13 34.46L72.2 34.46C79.45 34.46 85.32 40.33 85.32 47.58L85.32 63.99C85.32 71.24 79.45 77.12 72.2 77.12L13.13 77.12C5.88 77.12 0 71.24 0 63.99L0 47.58C0 40.33 5.88 34.46 13.13 34.46ZM22.97 39.38L22.97 19.69C22.97 8.82 31.79 0 42.66 0C53.54 0 62.35 8.82 62.35 19.69L62.35 39.38L56.61 39.38L56.61 19.69C56.61 11.99 50.36 5.74 42.66 5.74C34.96 5.74 28.71 11.99 28.71 19.69L28.71 39.38Z\"/><path fill=\"#FF5B2E\" d=\"M19.69 52.51L65.63 52.51C67.45 52.51 68.91 53.98 68.91 55.79C68.91 57.6 67.45 59.07 65.63 59.07L19.69 59.07C17.88 59.07 16.41 57.6 16.41 55.79C16.41 53.98 17.88 52.51 19.69 52.51Z\"/></g></svg>\n";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The Slotlock mark the dashboard serves as its favicon. A copy of site/public/favicon.svg, which a
|
|
2
|
+
// test keeps identical; the dashboard's Content-Security-Policy allows images from its own origin
|
|
3
|
+
// only, so the mark is served, not inlined.
|
|
4
|
+
export const SLOTLOCK_DASHBOARD_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#111110"/><g transform="translate(12.2 14.1) scale(0.465)"><path fill="#F6F4EE" d="M13.13 34.46L72.2 34.46C79.45 34.46 85.32 40.33 85.32 47.58L85.32 63.99C85.32 71.24 79.45 77.12 72.2 77.12L13.13 77.12C5.88 77.12 0 71.24 0 63.99L0 47.58C0 40.33 5.88 34.46 13.13 34.46ZM22.97 39.38L22.97 19.69C22.97 8.82 31.79 0 42.66 0C53.54 0 62.35 8.82 62.35 19.69L62.35 39.38L56.61 39.38L56.61 19.69C56.61 11.99 50.36 5.74 42.66 5.74C34.96 5.74 28.71 11.99 28.71 19.69L28.71 39.38Z"/><path fill="#FF5B2E" d="M19.69 52.51L65.63 52.51C67.45 52.51 68.91 53.98 68.91 55.79C68.91 57.6 67.45 59.07 65.63 59.07L19.69 59.07C17.88 59.07 16.41 57.6 16.41 55.79C16.41 53.98 17.88 52.51 19.69 52.51Z"/></g></svg>
|
|
5
|
+
`;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// The assistant's replies are Markdown. This renders the part of it a chat reply uses (paragraphs,
|
|
2
|
+
// headings, lists, quotes, rules, tables, code, bold, italic, strikethrough, http(s) links) as HTML,
|
|
3
|
+
// escaping everything first, so no input reaches the page as markup.
|
|
4
|
+
//
|
|
5
|
+
// It is one self-contained function on purpose: the dashboard's app.js embeds its source
|
|
6
|
+
// (Function.prototype.toString) to render a reply while it streams, and the server renders the
|
|
7
|
+
// stored reply with the very same function, so both always agree. Keep it free of imports and of
|
|
8
|
+
// anything outside its own body.
|
|
9
|
+
/** Safe HTML for a chat reply written in Markdown. */
|
|
10
|
+
export function renderSlotlockMarkdown(source) {
|
|
11
|
+
const text = String(source).replace(/\u0000/g, '').replace(/\r\n?/g, '\n');
|
|
12
|
+
const escape = (value) => value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
13
|
+
const inline = (value) => {
|
|
14
|
+
const codes = [];
|
|
15
|
+
let out = value.replace(/`([^`\n]+)`/g, (_match, code) => {
|
|
16
|
+
codes.push(`<code>${escape(code)}</code>`);
|
|
17
|
+
return `\u0000${codes.length - 1}\u0000`;
|
|
18
|
+
});
|
|
19
|
+
const links = [];
|
|
20
|
+
out = out.replace(/\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)/g, (_match, label, url) => {
|
|
21
|
+
links.push(`<a href="${escape(url)}" rel="noopener noreferrer">${escape(label)}</a>`);
|
|
22
|
+
return `\u0001${links.length - 1}\u0001`;
|
|
23
|
+
});
|
|
24
|
+
out = escape(out)
|
|
25
|
+
.replace(/\*\*(?=\S)([^\n]*?\S)\*\*/g, '<strong>$1</strong>')
|
|
26
|
+
.replace(/__(?=\S)([^\n]*?\S)__/g, '<strong>$1</strong>')
|
|
27
|
+
.replace(/~~(?=\S)([^\n]*?\S)~~/g, '<del>$1</del>')
|
|
28
|
+
.replace(/(^|[^*\w])\*(?=[^\s*])([^*\n]*?[^\s*])\*(?!\*)/g, '$1<em>$2</em>')
|
|
29
|
+
.replace(/(^|[^_\w])_(?=[^\s_])([^_\n]*?[^\s_])_(?![_\w])/g, '$1<em>$2</em>');
|
|
30
|
+
return out
|
|
31
|
+
.replace(/\u0001(\d+)\u0001/g, (_match, index) => {
|
|
32
|
+
const link = links[Number(index)] ?? '';
|
|
33
|
+
// A link's label keeps its own emphasis out: it was escaped whole.
|
|
34
|
+
return link;
|
|
35
|
+
})
|
|
36
|
+
.replace(/\u0000(\d+)\u0000/g, (_match, index) => codes[Number(index)] ?? '');
|
|
37
|
+
};
|
|
38
|
+
const cells = (line) => line
|
|
39
|
+
.trim()
|
|
40
|
+
.replace(/^\|/, '')
|
|
41
|
+
.replace(/\|$/, '')
|
|
42
|
+
.split('|')
|
|
43
|
+
.map((cell) => cell.trim());
|
|
44
|
+
const isRule = (line) => /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/.test(line);
|
|
45
|
+
const listItem = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/;
|
|
46
|
+
const lines = text.split('\n');
|
|
47
|
+
const blocks = [];
|
|
48
|
+
let paragraph = [];
|
|
49
|
+
const flush = () => {
|
|
50
|
+
if (paragraph.length > 0)
|
|
51
|
+
blocks.push(`<p>${paragraph.map(inline).join('<br>')}</p>`);
|
|
52
|
+
paragraph = [];
|
|
53
|
+
};
|
|
54
|
+
for (let i = 0; i < lines.length;) {
|
|
55
|
+
const line = lines[i] ?? '';
|
|
56
|
+
const fence = /^\s*(```|~~~)/.exec(line);
|
|
57
|
+
if (fence) {
|
|
58
|
+
flush();
|
|
59
|
+
const close = fence[1] ?? '```';
|
|
60
|
+
const body = [];
|
|
61
|
+
i += 1;
|
|
62
|
+
while (i < lines.length && !(lines[i] ?? '').trim().startsWith(close)) {
|
|
63
|
+
body.push(lines[i] ?? '');
|
|
64
|
+
i += 1;
|
|
65
|
+
}
|
|
66
|
+
i += 1;
|
|
67
|
+
blocks.push(`<pre><code>${escape(body.join('\n'))}</code></pre>`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (line.trim() === '') {
|
|
71
|
+
flush();
|
|
72
|
+
i += 1;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const heading = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/.exec(line);
|
|
76
|
+
if (heading) {
|
|
77
|
+
flush();
|
|
78
|
+
blocks.push(`<h4>${inline(heading[2] ?? '')}</h4>`);
|
|
79
|
+
i += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (/^\s{0,3}([-*_])(\s*\1){2,}\s*$/.test(line) && !listItem.test(line.replace(/\s+$/, '') + ' x')) {
|
|
83
|
+
flush();
|
|
84
|
+
blocks.push('<hr>');
|
|
85
|
+
i += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (line.includes('|') && isRule(lines[i + 1] ?? '') && cells(line).length > 1) {
|
|
89
|
+
flush();
|
|
90
|
+
const head = cells(line);
|
|
91
|
+
const rows = [];
|
|
92
|
+
i += 2;
|
|
93
|
+
while (i < lines.length && (lines[i] ?? '').includes('|') && (lines[i] ?? '').trim() !== '') {
|
|
94
|
+
rows.push(cells(lines[i] ?? ''));
|
|
95
|
+
i += 1;
|
|
96
|
+
}
|
|
97
|
+
blocks.push(`<div class="md-table"><table><thead><tr>${head.map((cell) => `<th>${inline(cell)}</th>`).join('')}</tr></thead><tbody>${rows
|
|
98
|
+
.map((row) => `<tr>${head.map((_cell, index) => `<td>${inline(row[index] ?? '')}</td>`).join('')}</tr>`)
|
|
99
|
+
.join('')}</tbody></table></div>`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (/^\s{0,3}>/.test(line)) {
|
|
103
|
+
flush();
|
|
104
|
+
const quoted = [];
|
|
105
|
+
while (i < lines.length && /^\s{0,3}>/.test(lines[i] ?? '')) {
|
|
106
|
+
quoted.push((lines[i] ?? '').replace(/^\s{0,3}>\s?/, ''));
|
|
107
|
+
i += 1;
|
|
108
|
+
}
|
|
109
|
+
blocks.push(`<blockquote>${quoted.map(inline).join('<br>')}</blockquote>`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (listItem.test(line)) {
|
|
113
|
+
flush();
|
|
114
|
+
// Lists nest by indentation; each level is the kind (ordered or not) of its first item.
|
|
115
|
+
let html = '';
|
|
116
|
+
const open = [];
|
|
117
|
+
while (i < lines.length) {
|
|
118
|
+
const current = lines[i] ?? '';
|
|
119
|
+
const item = listItem.exec(current);
|
|
120
|
+
if (!item) {
|
|
121
|
+
// A wrapped line continues the item above it; a blank line or anything else ends the list.
|
|
122
|
+
if (current.trim() !== '' && /^\s+\S/.test(current) && open.length > 0) {
|
|
123
|
+
html += `<br>${inline(current.trim())}`;
|
|
124
|
+
i += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
const indent = (item[1] ?? '').replace(/\t/g, ' ').length;
|
|
130
|
+
const tag = /\d/.test(item[2] ?? '') ? 'ol' : 'ul';
|
|
131
|
+
const top = open.at(-1);
|
|
132
|
+
if (!top || indent > top.indent + 1) {
|
|
133
|
+
if (top)
|
|
134
|
+
html = html.replace(/<\/li>$/, '');
|
|
135
|
+
html += `<${tag}>`;
|
|
136
|
+
open.push({ indent, tag });
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
while (open.length > 1 && indent < (open.at(-1)?.indent ?? 0) - 1) {
|
|
140
|
+
html += `</li></${open.pop()?.tag}>`;
|
|
141
|
+
}
|
|
142
|
+
html += '</li>';
|
|
143
|
+
}
|
|
144
|
+
html += `<li>${inline(item[3] ?? '')}`;
|
|
145
|
+
i += 1;
|
|
146
|
+
}
|
|
147
|
+
while (open.length > 0)
|
|
148
|
+
html += `</li></${open.pop()?.tag}>`;
|
|
149
|
+
blocks.push(html);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
paragraph.push(line.trim());
|
|
153
|
+
i += 1;
|
|
154
|
+
}
|
|
155
|
+
flush();
|
|
156
|
+
return blocks.join('');
|
|
157
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SLOTLOCK_SETTINGS_CSS = "\n.settings { display: grid; grid-template-columns: 176px minmax(0, 640px); gap: var(--space-8); align-items: start; }\n.subnav { display: grid; gap: 2px; position: sticky; top: var(--space-6); }\n.subnav a { display: flex; align-items: center; gap: var(--space-2); height: 32px; padding: 0 var(--space-3); border-radius: var(--radius-sm); color: var(--ink-2); font-size: 14px; text-decoration: none; }\n.subnav a:hover { background: var(--hover); color: var(--ink); }\n.subnav a[aria-current=\"page\"] { background: var(--surface); color: var(--ink); font-weight: 500; box-shadow: inset 0 0 0 1px var(--line); }\n.subnav .nav-dot { width: 6px; height: 6px; border-radius: 50%; margin-left: auto; background: var(--good); }\n.settings-body { display: grid; gap: var(--space-6); min-width: 0; }\n.settings-body > .notice { margin: 0; }\n.card-head { margin: 0 0 calc(var(--space-2) - var(--space-6)); font-size: 13px; font-weight: 600; color: var(--ink-3); }\n.set-card { background: var(--surface); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; margin: 0; }\n.row { display: grid; grid-template-columns: minmax(120px, 160px) minmax(0, 1fr); gap: var(--space-4); align-items: center; min-height: 56px; padding: 12px 20px; border-top: 1px solid var(--line-soft); }\n.row.stack { grid-template-columns: minmax(0, 1fr); gap: var(--space-3); align-items: start; padding-top: 16px; padding-bottom: 16px; }\n.row:first-child { border-top: 0; }\n.row-label { font-size: 14px; font-weight: 500; color: var(--ink); }\n.row-value { font-size: 14px; color: var(--ink); min-width: 0; overflow-wrap: anywhere; }\n.row-value.muted { color: var(--ink-2); }\n.row input:not([type=\"radio\"]):not([type=\"checkbox\"]), .row select { width: 100%; }\n.mono { font-family: var(--mono); font-size: 13px; }\n.card-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: 12px 20px; border-top: 1px solid var(--line); background: color-mix(in srgb, var(--surface) 70%, var(--canvas)); }\n.card-foot .fine { margin: 0; }\n.card-foot .primary { flex: none; }\n.status-head { display: grid; grid-template-columns: 32px minmax(0, 1fr) auto auto; gap: var(--space-3); align-items: center; padding: 16px 20px; }\n.status-head form { margin: 0; }\n.mono-tile { display: grid; place-items: center; width: 32px; height: 32px; border-radius: var(--radius); background: var(--sunken); border: 1px solid var(--line); color: var(--ink); font-weight: 600; font-size: 14px; }\n.status-name { display: grid; gap: 2px; min-width: 0; }\n.status-name strong { font-size: 15px; font-weight: 600; color: var(--ink); }\n.status-name code { color: var(--ink-3); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; background: none; padding: 0; }\n.badge { display: inline-flex; align-items: center; gap: 6px; height: 22px; padding: 0 8px; border-radius: var(--radius-full); font-size: 12px; font-weight: 500; color: var(--ink-2); background: var(--sunken); white-space: nowrap; }\n.badge::before { content: \"\"; width: 6px; height: 6px; border-radius: 50%; background: var(--ink-3); }\n.badge.ok { color: var(--good); background: color-mix(in srgb, var(--good) 14%, transparent); }\n.badge.ok::before { background: var(--good); }\n.badge.bad { color: var(--bad); background: color-mix(in srgb, var(--bad) 14%, transparent); }\n.badge.bad::before { background: var(--bad); }\n.status-card .notice { margin: 0; border-radius: 0; border-width: 1px 0 0; box-shadow: none; font-size: 13px; padding: 10px 20px; }\n.status-card .notice.done { color: var(--ink); }\n.tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-2); }\n.tile { position: relative; display: flex; align-items: center; gap: var(--space-2); height: 40px; padding: 0 10px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); font-size: 14px; color: var(--ink); cursor: pointer; transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease); }\n.tile:hover { background: var(--hover); }\n.tile input { position: absolute; opacity: 0; pointer-events: none; }\n.tile .tile-mark { display: grid; place-items: center; flex: none; width: 20px; height: 20px; border-radius: 5px; background: var(--sunken); color: var(--ink-2); font-size: 11px; font-weight: 600; }\n.tile:has(input:checked) { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }\n.tile:has(input:checked) .tile-mark { background: var(--accent); color: var(--surface); }\n.tile:has(input:focus-visible) { box-shadow: 0 0 0 3px var(--ring); }\n.provider-form:not(:has(input[name=\"preset\"][value=\"custom\"]:checked)) .custom-only { display: none; }\n.segmented { justify-self: start; display: inline-flex; padding: 2px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--sunken); gap: 2px; }\n.segmented label { position: relative; display: inline-flex; align-items: center; height: 30px; padding: 0 12px; border-radius: 6px; font-size: 13px; color: var(--ink-2); cursor: pointer; }\n.segmented input { position: absolute; opacity: 0; pointer-events: none; }\n.segmented label:has(input:checked) { background: var(--surface); color: var(--ink); box-shadow: 0 0 0 1px var(--line); }\n.field-with-button { display: flex; gap: var(--space-2); align-items: center; }\n.field-with-button input { flex: 1; min-width: 0; }\n.field-with-button button { flex: none; }\n.key-set { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }\n.key-set code { background: none; padding: 0; color: var(--ink-2); letter-spacing: 0.04em; }\n.key-set details { display: contents; }\n.key-set details > summary { list-style: none; cursor: pointer; color: var(--accent); font-size: 13px; }\n.key-set details > summary::-webkit-details-marker { display: none; }\n.key-set details[open] > summary { display: none; }\n.key-set details[open] input { flex-basis: 100%; }\n.danger-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: 14px 20px; }\n.danger-row strong { font-size: 14px; font-weight: 500; color: var(--ink); }\n.danger-row details.act > summary { color: var(--bad); }\n@media (max-width: 760px) {\n .settings { grid-template-columns: minmax(0, 1fr); gap: var(--space-4); }\n .subnav { position: static; display: flex; overflow-x: auto; gap: 0; border-bottom: 1px solid var(--line); scrollbar-width: none; }\n .subnav::-webkit-scrollbar { display: none; }\n .subnav a { height: 40px; border-radius: 0; flex: none; }\n .subnav a[aria-current=\"page\"] { background: none; box-shadow: inset 0 -2px 0 var(--accent); }\n .row { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); padding: 12px 16px; }\n .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n .status-head { grid-template-columns: 32px minmax(0, 1fr) auto; padding: 14px 16px; }\n .status-head form { grid-column: 1 / -1; }\n .card-foot { flex-direction: column; align-items: stretch; padding: 12px 16px; }\n .card-foot .primary { height: 44px; }\n}\n";
|