bunnyquery 1.2.2 → 1.3.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/README.md +78 -8
- package/bunnyquery.css +222 -165
- package/bunnyquery.js +4549 -3518
- package/dist/engine.cjs +1942 -0
- package/dist/engine.cjs.map +1 -0
- package/dist/engine.d.mts +475 -0
- package/dist/engine.d.ts +475 -0
- package/dist/engine.mjs +1884 -0
- package/dist/engine.mjs.map +1 -0
- package/package.json +24 -3
- package/src/engine/budget.ts +76 -0
- package/src/engine/config.ts +49 -0
- package/src/engine/errors.ts +58 -0
- package/src/engine/history.ts +117 -0
- package/src/engine/host.ts +110 -0
- package/src/engine/index.ts +74 -0
- package/src/engine/links.ts +74 -0
- package/src/engine/office.ts +105 -0
- package/src/engine/prompts/chat_system_prompt.ts +51 -0
- package/src/engine/prompts/index.ts +14 -0
- package/src/engine/prompts/indexing_system_prompt.ts +37 -0
- package/src/engine/prompts/indexing_user_message.ts +62 -0
- package/src/engine/requests.ts +682 -0
- package/src/engine/session.ts +918 -0
- package/src/widget.css +874 -0
- package/styles/chat.css +194 -0
package/README.md
CHANGED
|
@@ -6,8 +6,13 @@ account login/signup, conversation history, file & folder uploads, and a setting
|
|
|
6
6
|
panel — all talking to your project's **BunnyQuery** AI agent.
|
|
7
7
|
|
|
8
8
|
BunnyQuery is a standalone vanilla-JS port of the BunnyQuery (www.bunnyquery.com) agent
|
|
9
|
-
chatbox.
|
|
10
|
-
stylesheet
|
|
9
|
+
chatbox. The **widget** ships as a single IIFE that exposes `window.BunnyQuery` plus one
|
|
10
|
+
stylesheet — drop it in via `<script>`, no build step or framework required.
|
|
11
|
+
|
|
12
|
+
The package also exports the **framework-agnostic chat engine** that powers it
|
|
13
|
+
(`bunnyquery/engine`) — the same DOM-free core the Skapi admin chatbox consumes — so
|
|
14
|
+
you can build your own chat UI on top of it. See
|
|
15
|
+
[Importing the chat engine](#importing-the-chat-engine).
|
|
11
16
|
|
|
12
17
|
## Features
|
|
13
18
|
|
|
@@ -68,14 +73,18 @@ call `BunnyQuery.init()`:
|
|
|
68
73
|
That's it — BunnyQuery takes over the `#chatbox` element and renders the login or
|
|
69
74
|
chat view depending on the user's session.
|
|
70
75
|
|
|
71
|
-
##
|
|
76
|
+
## What's in the package
|
|
72
77
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
| `bunnyquery.js`
|
|
76
|
-
| `bunnyquery.css`
|
|
78
|
+
| Path | Purpose |
|
|
79
|
+
| ----------------------------- | -------------------------------------------------------------------------------- |
|
|
80
|
+
| `bunnyquery.js` | The widget IIFE. Exposes the global `window.BunnyQuery`. CDN / `<script>` drop-in. |
|
|
81
|
+
| `bunnyquery.css` | The widget's full stylesheet, scoped under `.bq-agent` / `[data-bq-theme]`. |
|
|
82
|
+
| `bunnyquery/engine` | The framework-agnostic chat engine — ships as ESM + CJS with TypeScript types. |
|
|
83
|
+
| `bunnyquery/styles/chat.css` | The shared chat-surface styles (bubbles, markdown, links) for an engine-built UI. |
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
The two widget files can be hosted yourself (same origin recommended) or loaded from a
|
|
86
|
+
CDN — no npm needed. The `engine` / `styles` subpaths are for bundler consumers
|
|
87
|
+
(`npm install bunnyquery`); see [Importing the chat engine](#importing-the-chat-engine).
|
|
79
88
|
|
|
80
89
|
## API
|
|
81
90
|
|
|
@@ -138,6 +147,67 @@ To customize colors, override the variables in your own stylesheet **after**
|
|
|
138
147
|
|
|
139
148
|
The active theme is saved to `localStorage`, so a returning user keeps their choice.
|
|
140
149
|
|
|
150
|
+
## Importing the chat engine
|
|
151
|
+
|
|
152
|
+
`bunnyquery.js` is the ready-made widget. Under it sits a **framework-agnostic,
|
|
153
|
+
DOM-free chat engine** — the same core that powers both this widget and the Skapi
|
|
154
|
+
admin chatbox. Import it from `bunnyquery/engine` when you want to build your own chat
|
|
155
|
+
UI (React, Vue, Svelte, vanilla…) while reusing the engine's message/queue/typewriter/
|
|
156
|
+
cache state machine, request builders, markdown-message composition, and prompts.
|
|
157
|
+
|
|
158
|
+
Install the package, plus the `skapi-js` SDK (for the transport) and — if you don't
|
|
159
|
+
already have one — a markdown renderer such as `marked`:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm install bunnyquery skapi-js marked
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import {
|
|
167
|
+
configureChatEngine,
|
|
168
|
+
ChatSession,
|
|
169
|
+
composeUserMessage,
|
|
170
|
+
type ChatHost,
|
|
171
|
+
} from 'bunnyquery/engine';
|
|
172
|
+
|
|
173
|
+
// Shared chat-surface styles (message bubbles, rendered markdown, links).
|
|
174
|
+
// Pair it with your own container/layout CSS and the --bq-* design tokens.
|
|
175
|
+
import 'bunnyquery/styles/chat.css';
|
|
176
|
+
|
|
177
|
+
// 1. Inject the skapi transport + MCP endpoint ONCE at startup.
|
|
178
|
+
configureChatEngine({
|
|
179
|
+
clientSecretRequest: (opts) => skapi.clientSecretRequest(opts),
|
|
180
|
+
clientSecretRequestHistory: (params, fetchOptions) =>
|
|
181
|
+
skapi.clientSecretRequestHistory(params, fetchOptions),
|
|
182
|
+
mcpBaseUrl: 'https://mcp.broadwayinc.computer',
|
|
183
|
+
poll: 0, // see the note below
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// 2. Implement a ChatHost (identity, render/scroll hooks, the skapi
|
|
187
|
+
// cancel/refresh surface) for your view, then drive a ChatSession.
|
|
188
|
+
const session = new ChatSession(host); // host: ChatHost
|
|
189
|
+
await session.loadHistory();
|
|
190
|
+
session.dispatchComposedMessage('Hello!'); // send a message
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The engine owns chat **state and logic** and calls back into your view through the
|
|
194
|
+
`ChatHost` interface (render, scroll, identity, cancel/refresh). It has **no bundled
|
|
195
|
+
runtime dependencies** — you inject the skapi transport via `configureChatEngine()` and
|
|
196
|
+
render markdown yourself (e.g. with `marked`). Everything is fully typed: `ChatSession`,
|
|
197
|
+
`ChatHost`, `ChatMessage`, `ChatIdentity`, `ChatState`, `composeUserMessage`, the request
|
|
198
|
+
builders (`callClaudeWithPublicMcp` / `callOpenAIWithPublicMcp`, `getChatHistory`,
|
|
199
|
+
`notifyAgentSaveAttachment`), the prompt builders, and the token-budget / link / history
|
|
200
|
+
helpers — see the `.d.ts` shipped with `bunnyquery/engine`.
|
|
201
|
+
|
|
202
|
+
`configureChatEngine` options:
|
|
203
|
+
|
|
204
|
+
| Option | Type | Description |
|
|
205
|
+
| ----------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
|
|
206
|
+
| `clientSecretRequest` | `function` | `skapi.clientSecretRequest`, bound to your Skapi instance. **Required.** |
|
|
207
|
+
| `clientSecretRequestHistory` | `function` | `skapi.clientSecretRequestHistory`, bound to your Skapi instance. **Required.** |
|
|
208
|
+
| `mcpBaseUrl` | `string` | MCP server base URL (you resolve prod vs dev). **Required.** |
|
|
209
|
+
| `poll` | `number?` | Value attached as `poll` on every request. Omit it if your `clientSecretRequest` already resolves with the final body; pass `0` for the deployed `skapi-js@latest` (needed for the early ack + a manual `.poll()` handle that powers queued-send cancel — the widget's case). |
|
|
210
|
+
|
|
141
211
|
## OAuth & redirects
|
|
142
212
|
|
|
143
213
|
BunnyQuery connects to your AI agent through an MCP OAuth server
|
package/bunnyquery.css
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/* GENERATED by scripts/build-css.mjs — DO NOT EDIT.
|
|
2
|
+
* = src/widget.css (widget chrome + tokens + chat container)
|
|
3
|
+
* + styles/chat.css (shared chat surface, also bunnyquery/styles/chat.css).
|
|
4
|
+
* Edit those sources, then run `npm run build`. */
|
|
5
|
+
|
|
1
6
|
/* ============================================================================
|
|
2
7
|
* BunnyQuery — embeddable AI chat widget
|
|
3
8
|
* Ported from the bunnyquery (www.skapi.com) agent.vue chatbox + login views.
|
|
@@ -608,170 +613,13 @@
|
|
|
608
613
|
font-weight: 600;
|
|
609
614
|
}
|
|
610
615
|
|
|
611
|
-
/* messages + bubbles
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
.bq-
|
|
617
|
-
.bq-
|
|
618
|
-
|
|
619
|
-
.bq-bubble {
|
|
620
|
-
max-width: 85%;
|
|
621
|
-
padding: 0.55rem 0.7rem;
|
|
622
|
-
border: 1px solid var(--bq-line);
|
|
623
|
-
white-space: pre-wrap;
|
|
624
|
-
line-height: 1.45;
|
|
625
|
-
font-size: 0.83rem;
|
|
626
|
-
background: var(--bq-paper);
|
|
627
|
-
}
|
|
628
|
-
.bq-message.is-user .bq-bubble { background: var(--bq-paper); }
|
|
629
|
-
.bq-message.is-assistant:not(.is-error) .bq-bubble {
|
|
630
|
-
background: var(--bq-success-bg);
|
|
631
|
-
border-color: var(--bq-success-border);
|
|
632
|
-
}
|
|
633
|
-
.bq-message.is-error .bq-bubble {
|
|
634
|
-
background: var(--bq-danger-bg);
|
|
635
|
-
border-color: var(--bq-danger);
|
|
636
|
-
color: var(--bq-danger);
|
|
637
|
-
}
|
|
638
|
-
.bq-message.is-cancelled .bq-bubble {
|
|
639
|
-
background: var(--bq-danger-bg);
|
|
640
|
-
border-color: var(--bq-danger);
|
|
641
|
-
color: var(--bq-danger);
|
|
642
|
-
opacity: 0.65;
|
|
643
|
-
}
|
|
644
|
-
.bq-message.is-pending-older .bq-bubble {
|
|
645
|
-
background: var(--bq-warning-bg);
|
|
646
|
-
border-color: var(--bq-warning-border);
|
|
647
|
-
color: var(--bq-warning);
|
|
648
|
-
transition: opacity 0.25s ease;
|
|
649
|
-
}
|
|
650
|
-
.bq-message.is-sending-to-server .bq-bubble { opacity: 0.4; }
|
|
651
|
-
|
|
652
|
-
.bq-empty-greeting .bq-bubble {
|
|
653
|
-
color: var(--bq-muted);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
.bq-pending-note {
|
|
657
|
-
display: block;
|
|
658
|
-
margin-top: 0.35rem;
|
|
659
|
-
font-size: 0.7rem;
|
|
660
|
-
color: var(--bq-warning);
|
|
661
|
-
font-style: italic;
|
|
662
|
-
}
|
|
663
|
-
.bq-cancel-error {
|
|
664
|
-
display: block;
|
|
665
|
-
margin-top: 0.3rem;
|
|
666
|
-
font-size: 0.68rem;
|
|
667
|
-
color: var(--bq-danger);
|
|
668
|
-
font-style: italic;
|
|
669
|
-
clear: both;
|
|
670
|
-
}
|
|
671
|
-
.bq-cancel-queue-btn {
|
|
672
|
-
float: right;
|
|
673
|
-
margin: -0.25rem -0.25rem 0.2rem 0.4rem;
|
|
674
|
-
width: 1.1rem;
|
|
675
|
-
height: 1.1rem;
|
|
676
|
-
display: inline-flex;
|
|
677
|
-
align-items: center;
|
|
678
|
-
justify-content: center;
|
|
679
|
-
padding: 0;
|
|
680
|
-
border: 1px solid var(--bq-warning-border);
|
|
681
|
-
background: transparent;
|
|
682
|
-
color: var(--bq-warning);
|
|
683
|
-
font-size: 0.85rem;
|
|
684
|
-
line-height: 1;
|
|
685
|
-
cursor: pointer;
|
|
686
|
-
flex-shrink: 0;
|
|
687
|
-
}
|
|
688
|
-
.bq-cancel-queue-btn:hover:not(.is-disabled) { background: var(--bq-warning-bg); }
|
|
689
|
-
.bq-cancel-queue-btn.is-disabled { opacity: 0.3; cursor: not-allowed; pointer-events: none; }
|
|
690
|
-
|
|
691
|
-
/* in-bubble file/link anchors */
|
|
692
|
-
.bq-file-download,
|
|
693
|
-
.bq-link-button {
|
|
694
|
-
color: var(--bq-main);
|
|
695
|
-
text-decoration: none;
|
|
696
|
-
cursor: pointer;
|
|
697
|
-
background: transparent;
|
|
698
|
-
border: 0;
|
|
699
|
-
padding: 0;
|
|
700
|
-
font: inherit;
|
|
701
|
-
}
|
|
702
|
-
.bq-file-download:hover,
|
|
703
|
-
.bq-link-button:hover { text-decoration: underline; }
|
|
704
|
-
.bq-link-button {
|
|
705
|
-
display: inline-block;
|
|
706
|
-
max-width: 100%;
|
|
707
|
-
overflow: hidden;
|
|
708
|
-
text-overflow: ellipsis;
|
|
709
|
-
white-space: nowrap;
|
|
710
|
-
vertical-align: bottom;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
/* ---- markdown body -------------------------------------------------------*/
|
|
714
|
-
.bq-md { white-space: normal; word-break: break-word; }
|
|
715
|
-
.bq-md > :first-child { margin-top: 0; }
|
|
716
|
-
.bq-md > :last-child { margin-bottom: 0; }
|
|
717
|
-
.bq-md p { margin: 0.4em 0; }
|
|
718
|
-
.bq-md h1 { font-size: 1.3em; }
|
|
719
|
-
.bq-md h2 { font-size: 1.18em; }
|
|
720
|
-
.bq-md h3 { font-size: 1.08em; }
|
|
721
|
-
.bq-md h4, .bq-md h5, .bq-md h6 { font-size: 1em; }
|
|
722
|
-
.bq-md h1, .bq-md h2, .bq-md h3, .bq-md h4, .bq-md h5, .bq-md h6 {
|
|
723
|
-
margin: 0.8em 0 0.35em;
|
|
724
|
-
line-height: 1.25;
|
|
725
|
-
font-weight: 600;
|
|
726
|
-
}
|
|
727
|
-
.bq-md ul, .bq-md ol { margin: 0.4em 0; padding-left: 1.4em; }
|
|
728
|
-
.bq-md li { margin: 0.15em 0; }
|
|
729
|
-
.bq-md blockquote {
|
|
730
|
-
margin: 0.5em 0;
|
|
731
|
-
padding: 0.2em 0.8em;
|
|
732
|
-
border-left: 3px solid var(--bq-line);
|
|
733
|
-
color: var(--bq-muted);
|
|
734
|
-
background: rgba(127, 127, 127, 0.08);
|
|
735
|
-
}
|
|
736
|
-
.bq-md hr { border: none; border-top: 1px solid var(--bq-line); margin: 0.8em 0; }
|
|
737
|
-
.bq-md a { color: var(--bq-main); text-decoration: underline; }
|
|
738
|
-
.bq-md a:hover { text-decoration: none; }
|
|
739
|
-
.bq-md code {
|
|
740
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
741
|
-
font-size: 0.9em;
|
|
742
|
-
padding: 0.1em 0.35em;
|
|
743
|
-
background: rgba(127, 127, 127, 0.16);
|
|
744
|
-
white-space: pre-wrap;
|
|
745
|
-
word-break: break-word;
|
|
746
|
-
}
|
|
747
|
-
.bq-md pre {
|
|
748
|
-
margin: 0.5em 0;
|
|
749
|
-
padding: 0.6em 0.8em;
|
|
750
|
-
background: var(--bq-code-bg);
|
|
751
|
-
color: var(--bq-code-fg);
|
|
752
|
-
overflow-x: auto;
|
|
753
|
-
white-space: pre;
|
|
754
|
-
}
|
|
755
|
-
.bq-md pre code {
|
|
756
|
-
background: transparent;
|
|
757
|
-
color: inherit;
|
|
758
|
-
padding: 0;
|
|
759
|
-
font-size: 0.85em;
|
|
760
|
-
word-break: normal;
|
|
761
|
-
}
|
|
762
|
-
.bq-md table {
|
|
763
|
-
border-collapse: collapse;
|
|
764
|
-
margin: 0.5em 0;
|
|
765
|
-
font-size: 0.95em;
|
|
766
|
-
display: block;
|
|
767
|
-
overflow-x: auto;
|
|
768
|
-
}
|
|
769
|
-
.bq-md th, .bq-md td { border: 1px solid var(--bq-line); padding: 0.3em 0.55em; text-align: left; }
|
|
770
|
-
.bq-md th { background: rgba(127, 127, 127, 0.1); font-weight: 600; }
|
|
771
|
-
.bq-md img { max-width: 100%; height: auto; border-radius: 4px; }
|
|
772
|
-
.bq-md strong { font-weight: 600; }
|
|
773
|
-
.bq-md em { font-style: italic; }
|
|
774
|
-
.bq-md del { text-decoration: line-through; }
|
|
616
|
+
/* messages + bubbles: the CORE conversation styling (bubble padding/colors,
|
|
617
|
+
rendered markdown, in-bubble link anchors, queue/cancel affordances) is
|
|
618
|
+
authored ONCE in @skapi/chat-engine/styles/chat.css and concatenated onto
|
|
619
|
+
this file by the build (scripts/build-css.mjs). Only the widget-specific
|
|
620
|
+
container bits stay here. */
|
|
621
|
+
.bq-bubble { max-width: 85%; }
|
|
622
|
+
.bq-empty-greeting .bq-bubble { color: var(--bq-muted); }
|
|
775
623
|
|
|
776
624
|
/* ---- composer / input row ------------------------------------------------*/
|
|
777
625
|
.bq-input-row {
|
|
@@ -860,7 +708,8 @@
|
|
|
860
708
|
position: absolute;
|
|
861
709
|
inset: 0 auto 0 0;
|
|
862
710
|
width: var(--att-progress, 0%);
|
|
863
|
-
|
|
711
|
+
/* theme-reactive tint (matches agent.vue's fade()→color-mix() change) */
|
|
712
|
+
background: color-mix(in srgb, var(--bq-main) 18%, transparent);
|
|
864
713
|
transition: width 0.15s linear;
|
|
865
714
|
z-index: 0;
|
|
866
715
|
pointer-events: none;
|
|
@@ -996,6 +845,19 @@
|
|
|
996
845
|
color: var(--bq-paper);
|
|
997
846
|
}
|
|
998
847
|
|
|
848
|
+
/* "apply to all remaining files" checkbox in the overwrite/reindex prompt */
|
|
849
|
+
.bq-overwrite-applyall {
|
|
850
|
+
display: flex;
|
|
851
|
+
align-items: center;
|
|
852
|
+
gap: 0.4rem;
|
|
853
|
+
margin: -0.5rem 0 1.25rem;
|
|
854
|
+
font-size: 0.82rem;
|
|
855
|
+
color: var(--bq-ink);
|
|
856
|
+
cursor: pointer;
|
|
857
|
+
user-select: none;
|
|
858
|
+
}
|
|
859
|
+
.bq-overwrite-applyall input { cursor: pointer; margin: 0; }
|
|
860
|
+
|
|
999
861
|
/* ============================================================================
|
|
1000
862
|
* RESPONSIVE
|
|
1001
863
|
* ==========================================================================*/
|
|
@@ -1015,3 +877,198 @@
|
|
|
1015
877
|
.bq-modal-root[data-bq-theme="dark"] .bq-icon-invertible {
|
|
1016
878
|
filter: invert(var(--bq-icon-invert));
|
|
1017
879
|
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* @skapi/chat-engine — shared chat-display styles.
|
|
883
|
+
*
|
|
884
|
+
* The conversation surface (message bubbles, rendered markdown, in-bubble file/
|
|
885
|
+
* link anchors, the queue/cancel affordances) must look IDENTICAL in agent.vue
|
|
886
|
+
* (the Vue admin chatbox) and the BunnyQuery widget. These rules are authored
|
|
887
|
+
* ONCE here and consumed by both:
|
|
888
|
+
* - agent.vue imports this file as a NON-scoped <style> block (so the rules
|
|
889
|
+
* also reach the v-html `.bq-md` markdown, which has no scope attribute).
|
|
890
|
+
* - the widget concatenates it into the shipped bunnyquery.css at build time.
|
|
891
|
+
*
|
|
892
|
+
* Each consumer supplies the `--bq-*` design tokens (light + dark) and keeps its
|
|
893
|
+
* OWN container/layout rules — `.bq-chat`, `.bq-messages` padding, the
|
|
894
|
+
* `.bq-history-loading` treatment, the empty-greeting, and the bubble max-width
|
|
895
|
+
* — which legitimately differ between an embedded admin panel and a standalone
|
|
896
|
+
* widget. Colors here are theme-reactive (tokens + rgba(127,127,127,…) neutral
|
|
897
|
+
* transparency) so the surface works in both light and dark schemes.
|
|
898
|
+
*
|
|
899
|
+
* Token contract (the consumer must define these): --bq-paper, --bq-line,
|
|
900
|
+
* --bq-muted, --bq-main, --bq-success-bg, --bq-success-border, --bq-danger,
|
|
901
|
+
* --bq-danger-bg, --bq-warning, --bq-warning-bg, --bq-warning-border,
|
|
902
|
+
* --bq-code-bg, --bq-code-fg.
|
|
903
|
+
*/
|
|
904
|
+
|
|
905
|
+
/* ---- messages + bubbles --------------------------------------------------*/
|
|
906
|
+
.bq-message { display: flex; margin-bottom: 0.5rem; }
|
|
907
|
+
.bq-message.is-user { justify-content: flex-end; }
|
|
908
|
+
.bq-message.is-assistant { justify-content: flex-start; }
|
|
909
|
+
|
|
910
|
+
.bq-bubble {
|
|
911
|
+
padding: 0.55rem 0.7rem;
|
|
912
|
+
border: 1px solid var(--bq-line);
|
|
913
|
+
white-space: pre-wrap;
|
|
914
|
+
line-height: 1.45;
|
|
915
|
+
font-size: 0.83rem;
|
|
916
|
+
background: var(--bq-paper);
|
|
917
|
+
}
|
|
918
|
+
.bq-bubble span { word-break: break-word; }
|
|
919
|
+
.bq-message.is-user .bq-bubble { background: var(--bq-paper); }
|
|
920
|
+
.bq-message.is-assistant:not(.is-error) .bq-bubble {
|
|
921
|
+
background: var(--bq-success-bg);
|
|
922
|
+
border-color: var(--bq-success-border);
|
|
923
|
+
}
|
|
924
|
+
.bq-message.is-error .bq-bubble {
|
|
925
|
+
background: var(--bq-danger-bg);
|
|
926
|
+
border-color: var(--bq-danger);
|
|
927
|
+
color: var(--bq-danger);
|
|
928
|
+
}
|
|
929
|
+
.bq-message.is-cancelled .bq-bubble {
|
|
930
|
+
background: var(--bq-danger-bg);
|
|
931
|
+
border-color: var(--bq-danger);
|
|
932
|
+
color: var(--bq-danger);
|
|
933
|
+
opacity: 0.65;
|
|
934
|
+
}
|
|
935
|
+
.bq-message.is-pending-older .bq-bubble {
|
|
936
|
+
background: var(--bq-warning-bg);
|
|
937
|
+
border-color: var(--bq-warning-border);
|
|
938
|
+
color: var(--bq-warning);
|
|
939
|
+
transition: opacity 0.25s ease;
|
|
940
|
+
}
|
|
941
|
+
.bq-message.is-sending-to-server .bq-bubble { opacity: 0.4; }
|
|
942
|
+
|
|
943
|
+
/* ---- queue / cancel affordances -----------------------------------------*/
|
|
944
|
+
.bq-pending-note {
|
|
945
|
+
display: block;
|
|
946
|
+
margin-top: 0.35rem;
|
|
947
|
+
font-size: 0.7rem;
|
|
948
|
+
color: var(--bq-warning);
|
|
949
|
+
font-style: italic;
|
|
950
|
+
}
|
|
951
|
+
.bq-cancel-error {
|
|
952
|
+
display: block;
|
|
953
|
+
margin-top: 0.3rem;
|
|
954
|
+
font-size: 0.68rem;
|
|
955
|
+
color: var(--bq-danger);
|
|
956
|
+
font-style: italic;
|
|
957
|
+
clear: both;
|
|
958
|
+
}
|
|
959
|
+
.bq-cancel-queue-btn {
|
|
960
|
+
float: right;
|
|
961
|
+
margin: -0.25rem -0.25rem 0.2rem 0.4rem;
|
|
962
|
+
width: 1.1rem;
|
|
963
|
+
height: 1.1rem;
|
|
964
|
+
min-height: 0;
|
|
965
|
+
display: inline-flex;
|
|
966
|
+
align-items: center;
|
|
967
|
+
justify-content: center;
|
|
968
|
+
padding: 0;
|
|
969
|
+
border: 1px solid var(--bq-warning-border);
|
|
970
|
+
background: transparent;
|
|
971
|
+
color: var(--bq-warning);
|
|
972
|
+
font-size: 0.85rem;
|
|
973
|
+
line-height: 1;
|
|
974
|
+
cursor: pointer;
|
|
975
|
+
box-shadow: none;
|
|
976
|
+
border-radius: 0;
|
|
977
|
+
flex-shrink: 0;
|
|
978
|
+
}
|
|
979
|
+
.bq-cancel-queue-btn:hover:not(.is-disabled) { background: var(--bq-warning-bg); color: var(--bq-warning); }
|
|
980
|
+
.bq-cancel-queue-btn.is-disabled { opacity: 0.3; cursor: not-allowed; pointer-events: none; }
|
|
981
|
+
|
|
982
|
+
/* ---- in-bubble file / link anchors --------------------------------------*/
|
|
983
|
+
.bq-file-download,
|
|
984
|
+
.bq-link-button {
|
|
985
|
+
color: var(--bq-main);
|
|
986
|
+
text-decoration: none;
|
|
987
|
+
cursor: pointer;
|
|
988
|
+
background: transparent;
|
|
989
|
+
border: 0;
|
|
990
|
+
padding: 0;
|
|
991
|
+
font: inherit;
|
|
992
|
+
}
|
|
993
|
+
.bq-file-download:hover,
|
|
994
|
+
.bq-link-button:hover { color: var(--bq-main); text-decoration: underline; background: transparent; }
|
|
995
|
+
.bq-link-button {
|
|
996
|
+
display: inline-block;
|
|
997
|
+
max-width: 100%;
|
|
998
|
+
overflow: hidden;
|
|
999
|
+
text-overflow: ellipsis;
|
|
1000
|
+
white-space: nowrap;
|
|
1001
|
+
vertical-align: bottom;
|
|
1002
|
+
}
|
|
1003
|
+
/* While the temporary URL is being re-resolved the click is swallowed — show a
|
|
1004
|
+
busy cursor and dim it so it reads as "working, don't click again". */
|
|
1005
|
+
.bq-link-button.is-refreshing { cursor: progress; opacity: 0.6; }
|
|
1006
|
+
|
|
1007
|
+
/* ---- rendered-markdown body ----------------------------------------------*/
|
|
1008
|
+
/* Overrides the bubble's `white-space: pre-wrap` since marked already produces
|
|
1009
|
+
structural HTML; pre/code reinstate pre-wrap for their own content. */
|
|
1010
|
+
.bq-md { white-space: normal; word-break: break-word; }
|
|
1011
|
+
.bq-md > :first-child { margin-top: 0; }
|
|
1012
|
+
.bq-md > :last-child { margin-bottom: 0; }
|
|
1013
|
+
.bq-md p { margin: 0.4em 0; }
|
|
1014
|
+
.bq-md h1 { font-size: 1.3em; }
|
|
1015
|
+
.bq-md h2 { font-size: 1.18em; }
|
|
1016
|
+
.bq-md h3 { font-size: 1.08em; }
|
|
1017
|
+
.bq-md h4, .bq-md h5, .bq-md h6 { font-size: 1em; }
|
|
1018
|
+
.bq-md h1, .bq-md h2, .bq-md h3, .bq-md h4, .bq-md h5, .bq-md h6 {
|
|
1019
|
+
margin: 0.8em 0 0.35em;
|
|
1020
|
+
line-height: 1.25;
|
|
1021
|
+
font-weight: 600;
|
|
1022
|
+
}
|
|
1023
|
+
.bq-md ul, .bq-md ol { margin: 0.4em 0; padding-left: 1.4em; }
|
|
1024
|
+
.bq-md li { margin: 0.15em 0; }
|
|
1025
|
+
.bq-md li > p { margin: 0.2em 0; }
|
|
1026
|
+
.bq-md blockquote {
|
|
1027
|
+
margin: 0.5em 0;
|
|
1028
|
+
padding: 0.2em 0.8em;
|
|
1029
|
+
border-left: 3px solid var(--bq-line);
|
|
1030
|
+
color: var(--bq-muted);
|
|
1031
|
+
background: rgba(127, 127, 127, 0.08);
|
|
1032
|
+
}
|
|
1033
|
+
.bq-md hr { border: none; border-top: 1px solid var(--bq-line); margin: 0.8em 0; }
|
|
1034
|
+
.bq-md a { color: var(--bq-main); text-decoration: underline; }
|
|
1035
|
+
.bq-md a:hover { text-decoration: none; }
|
|
1036
|
+
.bq-md code {
|
|
1037
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
1038
|
+
font-size: 0.9em;
|
|
1039
|
+
padding: 0.1em 0.35em;
|
|
1040
|
+
background: rgba(127, 127, 127, 0.16);
|
|
1041
|
+
border-radius: 0;
|
|
1042
|
+
white-space: pre-wrap;
|
|
1043
|
+
word-break: break-word;
|
|
1044
|
+
}
|
|
1045
|
+
.bq-md pre {
|
|
1046
|
+
margin: 0.5em 0;
|
|
1047
|
+
padding: 0.6em 0.8em;
|
|
1048
|
+
background: var(--bq-code-bg);
|
|
1049
|
+
color: var(--bq-code-fg);
|
|
1050
|
+
border-radius: 0;
|
|
1051
|
+
overflow-x: auto;
|
|
1052
|
+
white-space: pre;
|
|
1053
|
+
}
|
|
1054
|
+
.bq-md pre code {
|
|
1055
|
+
background: transparent;
|
|
1056
|
+
color: inherit;
|
|
1057
|
+
padding: 0;
|
|
1058
|
+
font-size: 0.85em;
|
|
1059
|
+
white-space: pre;
|
|
1060
|
+
word-break: normal;
|
|
1061
|
+
}
|
|
1062
|
+
.bq-md table {
|
|
1063
|
+
border-collapse: collapse;
|
|
1064
|
+
margin: 0.5em 0;
|
|
1065
|
+
font-size: 0.95em;
|
|
1066
|
+
display: block;
|
|
1067
|
+
overflow-x: auto;
|
|
1068
|
+
}
|
|
1069
|
+
.bq-md th, .bq-md td { border: 1px solid var(--bq-line); padding: 0.3em 0.55em; text-align: left; }
|
|
1070
|
+
.bq-md th { background: rgba(127, 127, 127, 0.1); font-weight: 600; }
|
|
1071
|
+
.bq-md img { max-width: 100%; height: auto; border-radius: 4px; }
|
|
1072
|
+
.bq-md strong { font-weight: 600; }
|
|
1073
|
+
.bq-md em { font-style: italic; }
|
|
1074
|
+
.bq-md del { text-decoration: line-through; }
|