@djangocfg/ui-tools 2.1.433 → 2.1.434
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/package.json +6 -6
- package/src/tools/chat/CLAUDE.md +51 -0
- package/src/tools/chat/README.md +6 -0
- package/src/tools/chat/lazy.tsx +9 -0
- package/src/tools/chat/messages/AutoLinkPreview.tsx +74 -0
- package/src/tools/chat/messages/MessageBubble.tsx +11 -23
- package/src/tools/chat/messages/ToolCalls.tsx +9 -22
- package/src/tools/chat/messages/ToolPayloadValue.tsx +106 -0
- package/src/tools/chat/messages/blocks/renderers/MapBlock.tsx +7 -0
- package/src/tools/chat/messages/index.ts +6 -0
- package/src/tools/dev/Map/components/MapContainer/MapInner.tsx +6 -1
- package/src/tools/dev/Map/components/MapContainer/types.ts +12 -0
- package/src/tools/dev/Map/hooks/useMapTouchMode.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-tools",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.434",
|
|
4
4
|
"description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-tools",
|
|
@@ -334,8 +334,8 @@
|
|
|
334
334
|
"test:watch": "vitest"
|
|
335
335
|
},
|
|
336
336
|
"peerDependencies": {
|
|
337
|
-
"@djangocfg/i18n": "^2.1.
|
|
338
|
-
"@djangocfg/ui-core": "^2.1.
|
|
337
|
+
"@djangocfg/i18n": "^2.1.434",
|
|
338
|
+
"@djangocfg/ui-core": "^2.1.434",
|
|
339
339
|
"consola": "^3.4.2",
|
|
340
340
|
"lodash-es": "^4.18.1",
|
|
341
341
|
"lucide-react": "^0.545.0",
|
|
@@ -418,9 +418,9 @@
|
|
|
418
418
|
"@maplibre/maplibre-gl-geocoder": "^1.7.0"
|
|
419
419
|
},
|
|
420
420
|
"devDependencies": {
|
|
421
|
-
"@djangocfg/i18n": "^2.1.
|
|
422
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
423
|
-
"@djangocfg/ui-core": "^2.1.
|
|
421
|
+
"@djangocfg/i18n": "^2.1.434",
|
|
422
|
+
"@djangocfg/typescript-config": "^2.1.434",
|
|
423
|
+
"@djangocfg/ui-core": "^2.1.434",
|
|
424
424
|
"@types/lodash-es": "^4.17.12",
|
|
425
425
|
"@types/mapbox__mapbox-gl-draw": "^1.4.8",
|
|
426
426
|
"@types/node": "^25.2.3",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# chat — `@djangocfg/ui-tools/chat`
|
|
2
|
+
|
|
3
|
+
Decomposed, transport-agnostic chat UI: bubbles, composer, tool-call panels,
|
|
4
|
+
message blocks, link previews. Consumed from npm AND vendored into cmdop
|
|
5
|
+
desktop via `pnpm sync:cfg`. See `README.md` for the full API; `@docs/` for
|
|
6
|
+
design + troubleshooting.
|
|
7
|
+
|
|
8
|
+
## Public entry — `lazy.tsx` (the `./chat` subpath)
|
|
9
|
+
|
|
10
|
+
`package.json` maps `"./chat"` → `src/tools/chat/lazy.tsx`. It does
|
|
11
|
+
`export * from './messages'` (+ composer/shell/launcher) and a few **explicit**
|
|
12
|
+
re-exports.
|
|
13
|
+
|
|
14
|
+
**Invariant — explicit-export gotcha.** A newly added **value** export that an
|
|
15
|
+
external host imports from `@djangocfg/ui-tools/chat` MUST be listed explicitly
|
|
16
|
+
in `lazy.tsx` (like `MarkdownMessage`, `ToolPayloadValue`). `export *` forwards
|
|
17
|
+
types but drops a fresh value export at runtime under `'use client'` + Vite →
|
|
18
|
+
`does not provide an export named X`. Not a cache bug. See
|
|
19
|
+
[`@docs/troubleshooting.md`](./@docs/troubleshooting.md) §1.
|
|
20
|
+
|
|
21
|
+
## Shared slots — extract, don't fork
|
|
22
|
+
|
|
23
|
+
A host can fork `MessageBubble` (cmdop does, to inject `@machine` chips). To
|
|
24
|
+
stop forks silently losing features, bubble features are **extracted
|
|
25
|
+
components** the fork re-renders, not inline code:
|
|
26
|
+
|
|
27
|
+
- `ToolPayloadValue` — the one payload renderer (object→JsonTree, string→`<pre>`,
|
|
28
|
+
XSS-safe). Used by the default `ToolCalls` panel AND any host card that owns
|
|
29
|
+
the panel via `renderToolCall`. [`@docs/tool-call-rendering.md`](./@docs/tool-call-rendering.md)
|
|
30
|
+
- `AutoLinkPreview` — auto-detect URL→preview slot, rendered by `MessageBubble`
|
|
31
|
+
and every fork. [`@docs/link-preview.md`](./@docs/link-preview.md)
|
|
32
|
+
|
|
33
|
+
**Rule:** adding a shared bubble feature → extract a component + export it (and
|
|
34
|
+
list it in `lazy.tsx` if a host imports it directly).
|
|
35
|
+
|
|
36
|
+
## Producer contract — tool payloads
|
|
37
|
+
|
|
38
|
+
`ChatToolCall.input`/`.output` are `unknown` so a real object flows through and
|
|
39
|
+
renders as a JSON tree. A producer that sends a JSON **string** (or
|
|
40
|
+
double-encodes one) gets a flat/escaped `<pre>` — the frontend never
|
|
41
|
+
un-escapes (sanitization). Fix on the producer: send a value, encode once.
|
|
42
|
+
[`@docs/troubleshooting.md`](./@docs/troubleshooting.md) §2-3.
|
|
43
|
+
|
|
44
|
+
## Docs
|
|
45
|
+
|
|
46
|
+
| Doc | Covers |
|
|
47
|
+
|---|---|
|
|
48
|
+
| [`@docs/tool-call-rendering.md`](./@docs/tool-call-rendering.md) | `ToolCalls` panels, `renderToolCall` vs payload props, `ToolPayloadValue`, JSON-tree contract |
|
|
49
|
+
| [`@docs/link-preview.md`](./@docs/link-preview.md) | `AutoLinkPreview` auto-detect, `LinkPreviewCard`, fork-drift fix |
|
|
50
|
+
| [`@docs/message-blocks.md`](./@docs/message-blocks.md) | typed `blocks[]` registry (`json`/`link`/`map`/… renderers) |
|
|
51
|
+
| [`@docs/troubleshooting.md`](./@docs/troubleshooting.md) | export gotcha, double-encoding, `<pre>`-vs-tree, fork drift, sync:cfg |
|
package/src/tools/chat/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Decomposed, transport-agnostic chat. Streaming-aware, markdown-native, mobile-ready, role-aware styling, pluggable backends.
|
|
4
4
|
|
|
5
|
+
> **Docs & gotchas:** [`CLAUDE.md`](./CLAUDE.md) (quick map + invariants) ·
|
|
6
|
+
> [`@docs/tool-call-rendering.md`](./@docs/tool-call-rendering.md) ·
|
|
7
|
+
> [`@docs/link-preview.md`](./@docs/link-preview.md) ·
|
|
8
|
+
> [`@docs/message-blocks.md`](./@docs/message-blocks.md) ·
|
|
9
|
+
> [`@docs/troubleshooting.md`](./@docs/troubleshooting.md)
|
|
10
|
+
|
|
5
11
|
## TL;DR
|
|
6
12
|
|
|
7
13
|
```tsx
|
package/src/tools/chat/lazy.tsx
CHANGED
|
@@ -85,6 +85,15 @@ export {
|
|
|
85
85
|
type MarkdownMessageProps,
|
|
86
86
|
} from '../dev/code/MarkdownMessage';
|
|
87
87
|
|
|
88
|
+
// Same gotcha as above: a host that owns its own tool-call card (cmdop's
|
|
89
|
+
// `<ToolResult>` via the `renderToolCall` override) imports ToolPayloadValue
|
|
90
|
+
// from `@djangocfg/ui-tools/chat` → THIS file. The `export * from './messages'`
|
|
91
|
+
// above re-exports it for TYPES, but under `'use client'` + Vite the runtime
|
|
92
|
+
// barrel drops the freshly-added symbol — so list it explicitly, like
|
|
93
|
+
// MarkdownMessage. (Symbols imported directly by MessageBubble — e.g.
|
|
94
|
+
// AutoLinkPreview — ride its chunk and don't need this.)
|
|
95
|
+
export { ToolPayloadValue } from './messages/ToolPayloadValue';
|
|
96
|
+
|
|
88
97
|
// Bridge — AI-driven `point` directives (lightweight, no heavy deps).
|
|
89
98
|
export {
|
|
90
99
|
HighlightOverlay,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AutoLinkPreview — the auto-detect link-preview slot, extracted so EVERY
|
|
5
|
+
* bubble surface shares ONE implementation.
|
|
6
|
+
*
|
|
7
|
+
* The default `MessageBubble` and any forked bubble (e.g. cmdop's
|
|
8
|
+
* mention-aware bubble, which owns its own content `<MarkdownMessage>` to
|
|
9
|
+
* inject `@machine` chips) both render this. Before extraction the logic
|
|
10
|
+
* lived inline in `MessageBubble` only, so a fork silently lost link
|
|
11
|
+
* previews — this component is the anti-drift fix: import it, render it, done.
|
|
12
|
+
*
|
|
13
|
+
* Behaviour (unchanged from the old inline path): when the host enables
|
|
14
|
+
* `config.linkPreview.autoDetect` AND provides a `resolve`-r, surface ONE
|
|
15
|
+
* preview card under the bubble for the FIRST URL in the message text.
|
|
16
|
+
* Suppressed while streaming (text isn't final), for error turns, and when
|
|
17
|
+
* the message already carries an explicit `link` block (so we never double
|
|
18
|
+
* up). Reads everything off the chat context — the caller passes only the
|
|
19
|
+
* message + presentational flags.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { useMemo } from 'react';
|
|
23
|
+
|
|
24
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
25
|
+
|
|
26
|
+
import { useChatContextOptional } from '../context';
|
|
27
|
+
import { extractFirstUrl } from '../core/extractFirstUrl';
|
|
28
|
+
import type { ChatMessage } from '../types';
|
|
29
|
+
import type { ComposerAppearance } from '../composer/types';
|
|
30
|
+
import { LinkPreviewCard } from '../../../common/link-preview';
|
|
31
|
+
|
|
32
|
+
export interface AutoLinkPreviewProps {
|
|
33
|
+
message: ChatMessage;
|
|
34
|
+
/** Streaming turn → suppress (text not final yet). */
|
|
35
|
+
isStreaming?: boolean;
|
|
36
|
+
/** Error turn → suppress. */
|
|
37
|
+
isError?: boolean;
|
|
38
|
+
/** Right-align the card under a user bubble. */
|
|
39
|
+
isUser?: boolean;
|
|
40
|
+
appearance?: ComposerAppearance;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function AutoLinkPreview({
|
|
44
|
+
message,
|
|
45
|
+
isStreaming = false,
|
|
46
|
+
isError = false,
|
|
47
|
+
isUser = false,
|
|
48
|
+
appearance = 'compact',
|
|
49
|
+
}: AutoLinkPreviewProps) {
|
|
50
|
+
const ctx = useChatContextOptional();
|
|
51
|
+
const linkCfg = ctx?.config.linkPreview;
|
|
52
|
+
|
|
53
|
+
const url = useMemo(() => {
|
|
54
|
+
if (!linkCfg?.autoDetect || !linkCfg.resolve) return null;
|
|
55
|
+
if (isStreaming || isError) return null;
|
|
56
|
+
if (message.blocks?.some((b) => b.kind === 'link')) return null;
|
|
57
|
+
return extractFirstUrl(message.content);
|
|
58
|
+
}, [
|
|
59
|
+
linkCfg?.autoDetect,
|
|
60
|
+
linkCfg?.resolve,
|
|
61
|
+
isStreaming,
|
|
62
|
+
isError,
|
|
63
|
+
message.blocks,
|
|
64
|
+
message.content,
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
if (!url || !linkCfg?.resolve) return null;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className={cn('mt-1.5 w-full', isUser && 'flex justify-end')}>
|
|
71
|
+
<LinkPreviewCard url={url} resolver={linkCfg.resolve} appearance={appearance} />
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -33,8 +33,7 @@ import {
|
|
|
33
33
|
type BubbleMenuItem,
|
|
34
34
|
} from './BubbleContextMenu';
|
|
35
35
|
import { MessageBlocks } from './blocks';
|
|
36
|
-
import {
|
|
37
|
-
import { extractFirstUrl } from '../core/extractFirstUrl';
|
|
36
|
+
import { AutoLinkPreview } from './AutoLinkPreview';
|
|
38
37
|
import type { BlockRegistry } from './blocks';
|
|
39
38
|
import type { ToolCallsProps } from './ToolCalls';
|
|
40
39
|
import type { ComposerAppearance } from '../composer/types';
|
|
@@ -212,18 +211,9 @@ const MessageBubbleInner = ({
|
|
|
212
211
|
// registry when both are absent.
|
|
213
212
|
const resolvedBlockRegistry = blockRegistry ?? ctx?.blockRegistry;
|
|
214
213
|
|
|
215
|
-
// Auto link-preview (Telegram-style)
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
// turns, and when the message already carries an explicit `link` block (so
|
|
219
|
-
// we never double up).
|
|
220
|
-
const linkCfg = ctx?.config.linkPreview;
|
|
221
|
-
const autoPreviewUrl = useMemo(() => {
|
|
222
|
-
if (!linkCfg?.autoDetect || !linkCfg.resolve) return null;
|
|
223
|
-
if (isStreaming || isErr) return null;
|
|
224
|
-
if (message.blocks?.some((b) => b.kind === 'link')) return null;
|
|
225
|
-
return extractFirstUrl(message.content);
|
|
226
|
-
}, [linkCfg?.autoDetect, linkCfg?.resolve, isStreaming, isErr, message.blocks, message.content]);
|
|
214
|
+
// Auto link-preview (Telegram-style) is rendered below the content via the
|
|
215
|
+
// shared <AutoLinkPreview> slot (so a forked bubble shares the exact same
|
|
216
|
+
// detect+render and can't drift). See AutoLinkPreview.tsx.
|
|
227
217
|
|
|
228
218
|
// URL chips (plan22): when the host opts in via `config.linkChips`, render
|
|
229
219
|
// bare web URLs in the bubble as compact favicon chips (matching the
|
|
@@ -397,15 +387,13 @@ const MessageBubbleInner = ({
|
|
|
397
387
|
/>
|
|
398
388
|
) : null}
|
|
399
389
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
</div>
|
|
408
|
-
) : null}
|
|
390
|
+
<AutoLinkPreview
|
|
391
|
+
message={message}
|
|
392
|
+
isStreaming={isStreaming}
|
|
393
|
+
isError={isErr}
|
|
394
|
+
isUser={isUser}
|
|
395
|
+
appearance={appearance}
|
|
396
|
+
/>
|
|
409
397
|
|
|
410
398
|
{message.toolCalls?.length
|
|
411
399
|
? toolCallsRenderer
|
|
@@ -6,8 +6,11 @@ import { ChevronDown, ChevronRight, Loader2 } from 'lucide-react';
|
|
|
6
6
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
7
7
|
|
|
8
8
|
import type { ChatToolCall } from '../types';
|
|
9
|
+
import { ToolPayloadValue, type ToolPayloadKind } from './ToolPayloadValue';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
// Re-exported for back-compat: `ToolPayloadKind` is now OWNED by
|
|
12
|
+
// ToolPayloadValue (breaking the old ToolCalls↔ToolPayloadValue import cycle).
|
|
13
|
+
export type { ToolPayloadKind };
|
|
11
14
|
|
|
12
15
|
export interface ToolCallsProps {
|
|
13
16
|
calls: ChatToolCall[];
|
|
@@ -207,26 +210,10 @@ const ToolCallItem = memo(function ToolCallItem({
|
|
|
207
210
|
);
|
|
208
211
|
});
|
|
209
212
|
|
|
213
|
+
// The default payload renderer delegates to the shared <ToolPayloadValue>
|
|
214
|
+
// (object → JsonTree, string/streaming → <pre>) so a host that owns its own
|
|
215
|
+
// tool-call card via `renderToolCall` can render the exact same payload with
|
|
216
|
+
// no duplicated routing. See ToolPayloadValue.tsx.
|
|
210
217
|
function DefaultPayload({ value, kind }: { value: unknown; kind: ToolPayloadKind }) {
|
|
211
|
-
|
|
212
|
-
const muted = kind === 'input';
|
|
213
|
-
return (
|
|
214
|
-
<pre
|
|
215
|
-
className={cn(
|
|
216
|
-
'overflow-auto rounded bg-background/60 p-1.5 font-mono',
|
|
217
|
-
kind === 'input' ? 'max-h-32' : 'max-h-48',
|
|
218
|
-
muted ? 'text-muted-foreground' : 'text-foreground/90',
|
|
219
|
-
)}
|
|
220
|
-
>
|
|
221
|
-
{isStreamingOrString ? String(value) : safeStringify(value)}
|
|
222
|
-
</pre>
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function safeStringify(value: unknown): string {
|
|
227
|
-
try {
|
|
228
|
-
return JSON.stringify(value, null, 2);
|
|
229
|
-
} catch {
|
|
230
|
-
return String(value);
|
|
231
|
-
}
|
|
218
|
+
return <ToolPayloadValue value={value} kind={kind} />;
|
|
232
219
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ToolPayloadValue — the single, shared renderer for a tool call's
|
|
5
|
+
* `input` / `output` / live `streaming` payload.
|
|
6
|
+
*
|
|
7
|
+
* Routing (one implementation, reused everywhere):
|
|
8
|
+
* - object / array (and NOT streaming) → interactive collapsible
|
|
9
|
+
* `LazyJsonTree` (the same viewer the chat `json` block + document
|
|
10
|
+
* inspector use). It renders values as React children — never
|
|
11
|
+
* `innerHTML` — so a tool result carrying HTML/markdown (e.g. a fetched
|
|
12
|
+
* web page) is shown as text, not executed: XSS-safe.
|
|
13
|
+
* - string / scalar / streaming → plain `<pre>` text. A streaming payload
|
|
14
|
+
* arrives as a partial string and must NOT be parsed mid-flight; prose /
|
|
15
|
+
* scalars have no tree to show.
|
|
16
|
+
*
|
|
17
|
+
* Both the default `DefaultPayload` (inside `ToolCalls`) AND a host that owns
|
|
18
|
+
* its own tool-call card (e.g. cmdop's `<ToolResult>` action card, reached via
|
|
19
|
+
* the `renderToolCall` override) render THROUGH this — so the object→tree
|
|
20
|
+
* behaviour can't drift between the two. `surfaceClassName` lets a host match
|
|
21
|
+
* its own card's surface token (the only thing that differed when the two
|
|
22
|
+
* implementations were separate).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
26
|
+
|
|
27
|
+
import { LazyJsonTree } from '../../data/JsonTree/lazy';
|
|
28
|
+
|
|
29
|
+
// Defined locally (NOT imported from ./ToolCalls) on purpose: ToolCalls
|
|
30
|
+
// imports ToolPayloadValue, so importing the type back would form an import
|
|
31
|
+
// cycle. Under the `'use client'` + subpath-barrel (`./chat` → lazy.tsx) setup
|
|
32
|
+
// that cycle makes Vite drop this module's symbol from the re-export graph at
|
|
33
|
+
// runtime (the export typechecks via index.ts but fails to bundle — the exact
|
|
34
|
+
// `MarkdownMessage` gotcha documented in lazy.tsx). The union is the SAME shape
|
|
35
|
+
// ToolCalls re-exports as `ToolPayloadKind`; keep them in sync (trivial).
|
|
36
|
+
export type ToolPayloadKind = 'input' | 'output' | 'streaming';
|
|
37
|
+
|
|
38
|
+
/** A structured value worth showing in the JSON tree (object or array). */
|
|
39
|
+
function isStructured(value: unknown): value is object {
|
|
40
|
+
return value !== null && typeof value === 'object';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function safeStringify(value: unknown): string {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.stringify(value, null, 2);
|
|
46
|
+
} catch {
|
|
47
|
+
return String(value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ToolPayloadValueProps {
|
|
52
|
+
value: unknown;
|
|
53
|
+
/** Drives expand depth (input collapsed, output 1 level), muted text, and
|
|
54
|
+
* whether a streaming partial is parsed (it never is). */
|
|
55
|
+
kind: ToolPayloadKind;
|
|
56
|
+
/** Surface token for the tree wrapper + `<pre>` background. Defaults to the
|
|
57
|
+
* chat panel's `bg-background/60`; a host card can pass e.g. `bg-muted/40`. */
|
|
58
|
+
surfaceClassName?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Show the JsonTree toolbar (search / expand / copy). Default **false**:
|
|
61
|
+
* the tool-call panel already has its own header (tool name + status), so a
|
|
62
|
+
* second toolbar is visual noise — and in `'auto'` mode JsonTree RESERVES the
|
|
63
|
+
* toolbar's height even while hidden, leaving an empty gap above the tree.
|
|
64
|
+
* Opt in (`toolbar`) only when copy/search on the raw payload is worth the
|
|
65
|
+
* extra chrome. When true the toolbar is hover-revealed (`'auto'`).
|
|
66
|
+
*/
|
|
67
|
+
toolbar?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function ToolPayloadValue({
|
|
71
|
+
value,
|
|
72
|
+
kind,
|
|
73
|
+
surfaceClassName = 'bg-background/60',
|
|
74
|
+
toolbar = false,
|
|
75
|
+
}: ToolPayloadValueProps) {
|
|
76
|
+
if (kind !== 'streaming' && isStructured(value)) {
|
|
77
|
+
return (
|
|
78
|
+
<div className={cn('overflow-auto rounded', surfaceClassName)}>
|
|
79
|
+
<LazyJsonTree
|
|
80
|
+
data={value}
|
|
81
|
+
bordered={false}
|
|
82
|
+
size="sm"
|
|
83
|
+
// Input collapsed, output 1 level. Toolbar OFF by default — the
|
|
84
|
+
// tool-call panel owns the header; `auto` would reserve height and
|
|
85
|
+
// leave an empty gap above the tree (see the `toolbar` prop note).
|
|
86
|
+
defaultExpandedDepth={kind === 'input' ? 0 : 1}
|
|
87
|
+
actions={['search', 'expand', 'copy']}
|
|
88
|
+
toolbar={toolbar ? 'auto' : 'never'}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<pre
|
|
96
|
+
className={cn(
|
|
97
|
+
'overflow-auto rounded p-1.5 font-mono',
|
|
98
|
+
surfaceClassName,
|
|
99
|
+
kind === 'input' ? 'max-h-32' : 'max-h-48',
|
|
100
|
+
kind === 'input' ? 'text-muted-foreground' : 'text-foreground/90',
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
{typeof value === 'string' ? value : safeStringify(value)}
|
|
104
|
+
</pre>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -153,6 +153,13 @@ export default function MapBlockRenderer({ block, ctx }: BlockRendererProps<MapB
|
|
|
153
153
|
<LazyMapContainer
|
|
154
154
|
initialViewport={initialViewport}
|
|
155
155
|
fullscreenTitle={block.caption ?? 'Map'}
|
|
156
|
+
// A chat transcript column is < 768px, which would otherwise trip the
|
|
157
|
+
// map's width-based "mobile" collapse (gestures off, scroll-lock
|
|
158
|
+
// overlay invisible, just a static tile) even on a desktop with a fine
|
|
159
|
+
// pointer. `0` opts out of width-based collapse — the map keeps its
|
|
160
|
+
// desktop scroll-lock + controls in the narrow column, and a REAL
|
|
161
|
+
// touchscreen still degrades to the mobile preview (coarse pointer).
|
|
162
|
+
mobileBreakpoint={0}
|
|
156
163
|
{...mapOptions}
|
|
157
164
|
>
|
|
158
165
|
{/* Filled zones first so routes/markers stack above them. */}
|
|
@@ -8,6 +8,9 @@ export {
|
|
|
8
8
|
type MessageListHandle,
|
|
9
9
|
} from './MessageList';
|
|
10
10
|
export { MessageBubble, type MessageBubbleProps } from './MessageBubble';
|
|
11
|
+
// The auto-detect link-preview slot — shared by MessageBubble and any forked
|
|
12
|
+
// bubble (e.g. cmdop's mention bubble) so previews never silently drift.
|
|
13
|
+
export { AutoLinkPreview, type AutoLinkPreviewProps } from './AutoLinkPreview';
|
|
11
14
|
// Re-exported from the shared `common/link-preview` location (the card was
|
|
12
15
|
// hoisted out of chat so the NotionEditor bookmark node can reuse it). The
|
|
13
16
|
// chat public surface (`@djangocfg/ui-tools/chat`) keeps exporting it from
|
|
@@ -24,6 +27,9 @@ export {
|
|
|
24
27
|
} from './BubbleContextMenu';
|
|
25
28
|
export { Sources, type SourcesProps } from './Sources';
|
|
26
29
|
export { ToolCalls, type ToolCallsProps, type ToolPayloadKind } from './ToolCalls';
|
|
30
|
+
// Shared payload renderer (object → JsonTree, string/streaming → <pre>) so a
|
|
31
|
+
// host that overrides `renderToolCall` renders payloads identically.
|
|
32
|
+
export { ToolPayloadValue, type ToolPayloadValueProps } from './ToolPayloadValue';
|
|
27
33
|
export {
|
|
28
34
|
Attachments,
|
|
29
35
|
AttachmentsGrid,
|
|
@@ -40,6 +40,7 @@ export function MapInner({
|
|
|
40
40
|
showResetButton = false,
|
|
41
41
|
scrollProtection = true,
|
|
42
42
|
scrollProtectionHint = 'Click to interact',
|
|
43
|
+
mobileBreakpoint,
|
|
43
44
|
fullscreenButton = true,
|
|
44
45
|
fullscreenLabel = 'Expand map',
|
|
45
46
|
fullscreenTitle = 'Map',
|
|
@@ -148,7 +149,11 @@ export function MapInner({
|
|
|
148
149
|
// keeps the Google-Maps scroll-isolation. The expanded map is always
|
|
149
150
|
// live. Detection is `matchMedia`-based so it reacts to DevTools device
|
|
150
151
|
// emulation, real touchscreens and window resizes (see useMapTouchMode).
|
|
151
|
-
|
|
152
|
+
// `mobileBreakpoint` lets a narrow-column embed (chat transcript < md) opt
|
|
153
|
+
// out of width-based mobile collapse: pass 0 → only a real coarse pointer
|
|
154
|
+
// (touchscreen) triggers the mobile preview, not the column's width. When
|
|
155
|
+
// undefined, useMapTouchMode keeps its 768px default.
|
|
156
|
+
const isTouch = useMapTouchMode(mobileBreakpoint)
|
|
152
157
|
const protectionMode = !scrollProtection || isFullscreenInstance
|
|
153
158
|
? 'off'
|
|
154
159
|
: isTouch
|
|
@@ -94,6 +94,18 @@ export interface MapContainerProps {
|
|
|
94
94
|
scrollProtection?: boolean
|
|
95
95
|
/** Hint shown on the lock overlay while scroll-zoom is blocked. */
|
|
96
96
|
scrollProtectionHint?: string
|
|
97
|
+
/**
|
|
98
|
+
* Width (px) at/under which the map collapses to the touch/"mobile"
|
|
99
|
+
* preview (gestures off, tap-to-expand) even with a fine pointer. Passed
|
|
100
|
+
* to `useMapTouchMode`. Default 768 (Tailwind `md`).
|
|
101
|
+
*
|
|
102
|
+
* Set `0` to OPT OUT of width-based collapse — the map then only goes
|
|
103
|
+
* mobile on a real coarse pointer (touchscreen), not because its column
|
|
104
|
+
* is narrow. Use this for a map embedded in a narrow column (e.g. a chat
|
|
105
|
+
* transcript < 768px) that should keep the desktop scroll-lock + controls.
|
|
106
|
+
* @default 768
|
|
107
|
+
*/
|
|
108
|
+
mobileBreakpoint?: number
|
|
97
109
|
/**
|
|
98
110
|
* Show a button that opens the map full-screen in a large dialog
|
|
99
111
|
* (Google-Maps-style "expand"). Scroll protection is off inside the
|
|
@@ -14,7 +14,7 @@ import { useIsTouch, useMediaQuery, MEDIA_BREAKPOINTS } from '@djangocfg/ui-core
|
|
|
14
14
|
* @param breakpoint width (px) at/under which the map is treated as mobile
|
|
15
15
|
* even with a fine pointer. Default 768 (Tailwind `md`).
|
|
16
16
|
*/
|
|
17
|
-
export function useMapTouchMode(breakpoint = MEDIA_BREAKPOINTS.md): boolean {
|
|
17
|
+
export function useMapTouchMode(breakpoint: number = MEDIA_BREAKPOINTS.md): boolean {
|
|
18
18
|
const isTouch = useIsTouch()
|
|
19
19
|
const isNarrow = useMediaQuery(`(max-width: ${breakpoint - 1}px)`)
|
|
20
20
|
return isTouch || isNarrow
|