@djangocfg/ui-tools 2.1.431 → 2.1.433
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 +28 -14
- package/src/common/CodePanelHeader/CodePanelHeader.tsx +121 -0
- package/src/common/CodePanelHeader/index.ts +1 -0
- package/src/common/blocks/BlockError.tsx +46 -0
- package/src/common/blocks/index.ts +34 -0
- package/src/common/blocks/schemas.ts +143 -0
- package/src/common/blocks/types.ts +76 -0
- package/src/common/chips/README.md +67 -0
- package/src/common/chips/UrlChip.tsx +105 -0
- package/src/common/chips/index.ts +20 -0
- package/src/common/link-preview/LinkPreviewCard.tsx +178 -0
- package/src/common/link-preview/index.ts +12 -0
- package/src/common/link-preview/types.ts +44 -0
- package/src/common/tiptap/index.ts +10 -0
- package/src/common/tiptap/useNodeAttrs.ts +128 -0
- package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +1 -1
- package/src/tools/chat/README.md +88 -2
- package/src/tools/chat/composer/Composer.tsx +45 -12
- package/src/tools/chat/composer/ComposerRichTextarea.tsx +16 -4
- package/src/tools/chat/constants.ts +9 -2
- package/src/tools/chat/core/extractFirstUrl.ts +45 -0
- package/src/tools/chat/core/reducer.ts +32 -8
- package/src/tools/chat/core/transport/mappers/pydantic-ai.ts +1 -1
- package/src/tools/chat/core/transport/mock.ts +4 -1
- package/src/tools/chat/hooks/useChat.ts +20 -20
- package/src/tools/chat/lazy.tsx +10 -0
- package/src/tools/chat/messages/Attachments.tsx +267 -42
- package/src/tools/chat/messages/BubbleContextMenu.tsx +203 -0
- package/src/tools/chat/messages/MessageBubble.tsx +175 -31
- package/src/tools/chat/messages/MessageList.tsx +107 -21
- package/src/tools/chat/messages/ToolCalls.tsx +28 -13
- package/src/tools/chat/messages/blocks/MessageBlocks.tsx +70 -17
- package/src/tools/chat/messages/blocks/builtin.tsx +6 -3
- package/src/tools/chat/messages/blocks/renderers/AudioBlock.tsx +6 -0
- package/src/tools/chat/messages/blocks/renderers/ChatImageAlbum.tsx +154 -0
- package/src/tools/chat/messages/blocks/renderers/CodeBlock.tsx +94 -6
- package/src/tools/chat/messages/blocks/renderers/DiffBlock.tsx +29 -0
- package/src/tools/chat/messages/blocks/renderers/GalleryBlock.tsx +17 -10
- package/src/tools/chat/messages/blocks/renderers/ImageBlock.tsx +37 -16
- package/src/tools/chat/messages/blocks/renderers/JsonBlock.tsx +11 -6
- package/src/tools/chat/messages/blocks/renderers/LinkBlock.tsx +32 -0
- package/src/tools/chat/messages/blocks/renderers/MapBlock.tsx +181 -19
- package/src/tools/chat/messages/blocks/renderers/VideoBlock.tsx +2 -5
- package/src/tools/chat/messages/index.ts +13 -0
- package/src/tools/chat/public.ts +5 -1
- package/src/tools/chat/shell/ChatRoot.tsx +29 -0
- package/src/tools/chat/styles/bubbleTokens.ts +18 -0
- package/src/tools/chat/styles/index.ts +1 -0
- package/src/tools/chat/types/attachment.ts +26 -2
- package/src/tools/chat/types/block.ts +56 -9
- package/src/tools/chat/types/blockSchemas.ts +197 -0
- package/src/tools/chat/types/config.ts +33 -0
- package/src/tools/chat/types/events.ts +14 -7
- package/src/tools/chat/types/index.ts +4 -2
- package/src/tools/chat/types/message.ts +7 -0
- package/src/tools/data/JsonTree/JsonViewer.tsx +7 -7
- package/src/tools/dev/Map/README.md +30 -345
- package/src/tools/dev/Map/cards/index.ts +6 -0
- package/src/tools/dev/Map/cards/types.ts +67 -0
- package/src/tools/dev/Map/components/CompassChip.tsx +67 -0
- package/src/tools/dev/Map/components/DraggableMarkers.tsx +79 -0
- package/src/tools/dev/Map/components/GeocoderControl.tsx +148 -63
- package/src/tools/dev/Map/components/GeocoderInput.tsx +181 -0
- package/src/tools/dev/Map/components/MapContainer/FullscreenDialog.tsx +140 -0
- package/src/tools/dev/Map/components/MapContainer/GeolocateHandle.tsx +70 -0
- package/src/tools/dev/Map/components/MapContainer/MapInner.tsx +326 -0
- package/src/tools/dev/Map/components/MapContainer/MapToolbar.tsx +286 -0
- package/src/tools/dev/Map/components/MapContainer/RotateHint.tsx +25 -0
- package/src/tools/dev/Map/components/MapContainer/ScrollLockOverlay.tsx +52 -0
- package/src/tools/dev/Map/components/MapContainer/constants.ts +15 -0
- package/src/tools/dev/Map/components/MapContainer/hooks/useAutoReset.ts +51 -0
- package/src/tools/dev/Map/components/MapContainer/hooks/useBasemapState.ts +48 -0
- package/src/tools/dev/Map/components/MapContainer/hooks/useExternalMapsUrl.ts +29 -0
- package/src/tools/dev/Map/components/MapContainer/index.tsx +29 -0
- package/src/tools/dev/Map/components/MapContainer/types.ts +198 -0
- package/src/tools/dev/Map/components/MapControls.tsx +27 -0
- package/src/tools/dev/Map/components/MapMarkers.tsx +94 -0
- package/src/tools/dev/Map/components/MarkerCard.tsx +102 -0
- package/src/tools/dev/Map/components/MeasureControl.tsx +219 -0
- package/src/tools/dev/Map/components/index.ts +35 -1
- package/src/tools/dev/Map/geocode/index.ts +2 -0
- package/src/tools/dev/Map/geocode/photon.ts +265 -0
- package/src/tools/dev/Map/hooks/index.ts +37 -0
- package/src/tools/dev/Map/hooks/useCompass.ts +44 -0
- package/src/tools/dev/Map/hooks/useDraggableMarkers.ts +112 -0
- package/src/tools/dev/Map/hooks/useGeocoder.ts +155 -0
- package/src/tools/dev/Map/hooks/useGeolocation.ts +174 -0
- package/src/tools/dev/Map/hooks/useMapControl.ts +5 -1
- package/src/tools/dev/Map/hooks/useMapImages.ts +112 -0
- package/src/tools/dev/Map/hooks/useMapScrollProtection.ts +155 -0
- package/src/tools/dev/Map/hooks/useMapTouchMode.ts +21 -0
- package/src/tools/dev/Map/hooks/useMarkerCard.ts +39 -0
- package/src/tools/dev/Map/hooks/useMarkers.ts +9 -3
- package/src/tools/dev/Map/hooks/useMeasure.ts +169 -0
- package/src/tools/dev/Map/hooks/useTerrain.ts +123 -0
- package/src/tools/dev/Map/index.ts +59 -2
- package/src/tools/dev/Map/layers/index.ts +1 -0
- package/src/tools/dev/Map/layers/symbol.ts +93 -0
- package/src/tools/dev/Map/lazy.tsx +120 -0
- package/src/tools/dev/Map/protocols/pmtiles.ts +73 -0
- package/src/tools/dev/Map/styles/index.ts +62 -0
- package/src/tools/dev/Map/styles/terrain.ts +29 -0
- package/src/tools/dev/Map/types.ts +31 -3
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +5 -12
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/shared/SendButton.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/shared/http-colors.ts +61 -0
- package/src/tools/dev/api/OpenapiViewer/components/shared/ui.tsx +4 -21
- package/src/tools/dev/code/MarkdownMessage/MarkdownMessage.tsx +150 -58
- package/src/tools/dev/code/MarkdownMessage/README.md +38 -0
- package/src/tools/dev/code/MarkdownMessage/builtinRules.tsx +92 -0
- package/src/tools/dev/code/MarkdownMessage/index.ts +3 -0
- package/src/tools/dev/code/MarkdownMessage/streamingBlocks.ts +164 -0
- package/src/tools/dev/code/MarkdownMessage/types.ts +20 -0
- package/src/tools/dev/code/PrettyCode/PrettyCode.client.tsx +27 -30
- package/src/tools/dev/code/PrettyCode/index.tsx +4 -1
- package/src/tools/dev/code/PrettyCode/lazy.tsx +3 -1
- package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +168 -3
- package/src/tools/forms/MarkdownEditor/README.md +84 -0
- package/src/tools/forms/MarkdownEditor/chip/ChipNode.ts +454 -0
- package/src/tools/forms/MarkdownEditor/chip/syncChips.ts +80 -0
- package/src/tools/forms/MarkdownEditor/filePath/FilePathChip.tsx +68 -0
- package/src/tools/forms/MarkdownEditor/filePath/__tests__/detect.test.ts +161 -0
- package/src/tools/forms/MarkdownEditor/filePath/detect.ts +289 -0
- package/src/tools/forms/MarkdownEditor/filePath/index.ts +15 -0
- package/src/tools/forms/MarkdownEditor/index.ts +19 -0
- package/src/tools/forms/MarkdownEditor/lazy.tsx +10 -0
- package/src/tools/forms/MarkdownEditor/styles.css +134 -2
- package/src/tools/forms/MarkdownEditor/url/__tests__/detect.test.ts +121 -0
- package/src/tools/forms/MarkdownEditor/url/detect.ts +227 -0
- package/src/tools/forms/NotionEditor/BookmarkNode.ts +158 -0
- package/src/tools/forms/NotionEditor/BookmarkView.tsx +45 -0
- package/src/tools/forms/NotionEditor/MapEditLayer.tsx +199 -0
- package/src/tools/forms/NotionEditor/MapNode.ts +218 -0
- package/src/tools/forms/NotionEditor/MapView.tsx +103 -0
- package/src/tools/forms/NotionEditor/NotionEditor.tsx +7 -2
- package/src/tools/forms/NotionEditor/README.md +109 -188
- package/src/tools/forms/NotionEditor/extensions.ts +15 -0
- package/src/tools/forms/NotionEditor/slashItems.ts +19 -0
- package/src/tools/forms/NotionEditor/styles.css +25 -0
- package/src/tools/forms/NotionEditor/types.ts +9 -0
- package/src/tools/input/SpeechRecognition/core/engine/external.ts +5 -0
- package/src/tools/input/SpeechRecognition/types.ts +10 -0
- package/src/tools/media/Gallery/components/lightbox/GalleryLightbox.tsx +39 -22
- package/src/tools/media/ImageViewer/README.md +9 -0
- package/src/tools/media/ImageViewer/components/ImageViewer.tsx +165 -79
- package/src/tools/media/ImageViewer/components/LightboxChrome.tsx +137 -0
- package/src/tools/media/ImageViewer/components/MiddleEllipsis.tsx +46 -0
- package/src/tools/media/ImageViewer/components/index.ts +2 -0
- package/src/tools/media/ImageViewer/hooks/index.ts +3 -0
- package/src/tools/media/ImageViewer/hooks/useIdleChrome.ts +73 -0
- package/src/tools/media/ImageViewer/types.ts +10 -0
- package/src/tools/media/ImageViewer/utils/constants.ts +28 -0
- package/src/tools/media/ImageViewer/utils/index.ts +4 -0
- package/src/tools/media/VideoPlayer/VideoPlayer.tsx +34 -40
- package/src/tools/media/VideoPlayer/canvas/youtube-canvas.tsx +13 -13
- package/src/tools/media/VideoPlayer/parts/center-play.tsx +26 -0
- package/src/tools/media/VideoPlayer/parts/index.ts +2 -0
- package/src/tools/media/VideoPlayer/styles/video-player.css +102 -53
- package/src/tools/visual/charts/SmoothLine/README.md +97 -0
- package/src/tools/visual/charts/SmoothLine/SmoothLine.tsx +177 -0
- package/src/tools/visual/charts/SmoothLine/index.ts +21 -0
- package/src/tools/visual/charts/SmoothLine/lazy.tsx +23 -0
- package/src/tools/visual/charts/SmoothLine/smoothPath.ts +209 -0
- package/src/tools/visual/charts/SmoothLine/types.ts +98 -0
- package/src/tools/visual/charts/SmoothLine/useSmoothSeries.ts +429 -0
- package/src/tools/visual/indicators/Fps/types.ts +1 -1
- package/src/tools/chat/messages/blocks/renderers/LottieBlock.tsx +0 -11
- package/src/tools/dev/Map/components/MapContainer.tsx +0 -198
|
@@ -1,35 +1,197 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Map block — thin chat wrapper around `LazyMapContainer`. Renders point
|
|
5
|
+
* markers plus optional routes (polylines) and polygons (filled zones) by
|
|
6
|
+
* building GeoJSON from the block's lat/lng payloads and feeding the map's
|
|
7
|
+
* existing `MapSource` + layer factories — no bespoke layer logic here.
|
|
8
|
+
*/
|
|
4
9
|
|
|
5
|
-
import { LazyMapContainer, MapMarker } from '../../../../dev/Map/lazy';
|
|
10
|
+
import { LazyMapContainer, MapMarker, MapPopup, MarkerCard, MapSource, MapLayer, useMarkerCard } from '../../../../dev/Map/lazy';
|
|
6
11
|
import type { MarkerData } from '../../../../dev/Map/lazy';
|
|
12
|
+
import type { MapContainerProps } from '../../../../dev/Map/components';
|
|
13
|
+
import {
|
|
14
|
+
createRouteLayers,
|
|
15
|
+
createPolygonLayer,
|
|
16
|
+
createPolygonOutlineLayer,
|
|
17
|
+
} from '../../../../dev/Map/layers';
|
|
18
|
+
import {
|
|
19
|
+
calculateBounds,
|
|
20
|
+
createLineString,
|
|
21
|
+
createPolygon,
|
|
22
|
+
createFeatureCollection,
|
|
23
|
+
} from '../../../../dev/Map/utils';
|
|
7
24
|
import type { MapBlock } from '../../../types/block';
|
|
8
25
|
import type { BlockRendererProps } from './types';
|
|
9
26
|
|
|
27
|
+
/** `[lng, lat]` tuple — GeoJSON / maplibre coordinate order. */
|
|
28
|
+
type LngLat = [number, number];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Derive an `initialViewport` that frames every coordinate. The map exposes
|
|
32
|
+
* an imperative `fitBounds` only through `useMapControl` (inside its own
|
|
33
|
+
* provider), which this frameless wrapper sits outside of — so instead we
|
|
34
|
+
* seed the initial center/zoom from the bounding box. Mirrors maplibre's own
|
|
35
|
+
* fit math: zoom for whichever axis (lng/lat) needs the most room.
|
|
36
|
+
*/
|
|
37
|
+
function viewportForBounds(points: LngLat[]): { longitude: number; latitude: number; zoom: number } {
|
|
38
|
+
const [[minLng, minLat], [maxLng, maxLat]] = calculateBounds(points) as [LngLat, LngLat];
|
|
39
|
+
const longitude = (minLng + maxLng) / 2;
|
|
40
|
+
const latitude = (minLat + maxLat) / 2;
|
|
41
|
+
|
|
42
|
+
const lngSpan = Math.max(maxLng - minLng, 1e-6);
|
|
43
|
+
const latSpan = Math.max(maxLat - minLat, 1e-6);
|
|
44
|
+
// World is 360° at zoom 0; each zoom level halves the visible span. Add a
|
|
45
|
+
// little padding (0.6) so geometry never hugs the edges, and clamp.
|
|
46
|
+
const zoomForSpan = (span: number, fullSpan: number) =>
|
|
47
|
+
Math.log2((fullSpan / span) * 0.6);
|
|
48
|
+
const zoom = Math.min(16, Math.max(1, Math.min(zoomForSpan(lngSpan, 360), zoomForSpan(latSpan, 180))));
|
|
49
|
+
|
|
50
|
+
return { longitude, latitude, zoom };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type BlockMarker = NonNullable<MapBlock['markers']>[number];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Marker layer for the chat map block. Renders each marker (preserving the
|
|
57
|
+
* `icon` thumbnail / `color` pin) and, when a marker carries a serializable
|
|
58
|
+
* `card`, opens a `MarkerCard` popup ABOVE the pin on click. Selection is
|
|
59
|
+
* single-card via `useMarkerCard`. Lives inside `LazyMapContainer` so it has
|
|
60
|
+
* the MapProvider context.
|
|
61
|
+
*/
|
|
62
|
+
function MapBlockMarkers({ markers }: { markers: BlockMarker[] }) {
|
|
63
|
+
const { openId, toggle, close } = useMarkerCard();
|
|
64
|
+
const open = openId ? markers.find((m) => m.id === openId) : undefined;
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
{markers.map((m) => {
|
|
69
|
+
const marker: MarkerData = {
|
|
70
|
+
id: m.id,
|
|
71
|
+
longitude: m.lng,
|
|
72
|
+
latitude: m.lat,
|
|
73
|
+
data: m.label ? { label: m.label } : undefined,
|
|
74
|
+
};
|
|
75
|
+
return (
|
|
76
|
+
<MapMarker
|
|
77
|
+
key={m.id}
|
|
78
|
+
marker={marker}
|
|
79
|
+
color={m.color}
|
|
80
|
+
ariaLabel={m.card?.title ?? m.label}
|
|
81
|
+
onClick={m.card ? () => toggle(m.id) : undefined}
|
|
82
|
+
>
|
|
83
|
+
{m.icon ? (
|
|
84
|
+
<img
|
|
85
|
+
src={m.icon}
|
|
86
|
+
alt={m.label ?? ''}
|
|
87
|
+
className="h-7 w-7 rounded-full object-cover ring-2 ring-background shadow-md"
|
|
88
|
+
/>
|
|
89
|
+
) : undefined}
|
|
90
|
+
</MapMarker>
|
|
91
|
+
);
|
|
92
|
+
})}
|
|
93
|
+
|
|
94
|
+
{open?.card && (
|
|
95
|
+
<MapPopup
|
|
96
|
+
longitude={open.lng}
|
|
97
|
+
latitude={open.lat}
|
|
98
|
+
anchor="bottom"
|
|
99
|
+
onClose={close}
|
|
100
|
+
maxWidth="none"
|
|
101
|
+
>
|
|
102
|
+
<MarkerCard {...open.card} />
|
|
103
|
+
</MapPopup>
|
|
104
|
+
)}
|
|
105
|
+
</>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
10
109
|
export default function MapBlockRenderer({ block, ctx }: BlockRendererProps<MapBlock>) {
|
|
11
110
|
const height = ctx.appearance === 'compact' ? 240 : 380;
|
|
111
|
+
|
|
112
|
+
// The serializable map shape (center / markers / routes / polygons) is
|
|
113
|
+
// validated centrally at the registry dispatch point (MessageBlocks) before
|
|
114
|
+
// this renderer runs, so we can trust the payload here.
|
|
115
|
+
const routes = block.routes ?? [];
|
|
116
|
+
const polygons = block.polygons ?? [];
|
|
117
|
+
const markers = block.markers ?? [];
|
|
118
|
+
|
|
119
|
+
// Collect every coordinate so the viewport can frame the whole scene when
|
|
120
|
+
// the block doesn't pin an explicit center/zoom.
|
|
121
|
+
const allPoints: LngLat[] = [
|
|
122
|
+
...markers.map((m): LngLat => [m.lng, m.lat]),
|
|
123
|
+
...routes.flatMap((r) => r.points.map((p): LngLat => [p.lng, p.lat])),
|
|
124
|
+
...polygons.flatMap((p) => p.points.map((pt): LngLat => [pt.lng, pt.lat])),
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
// Respect an explicit zoom (author intent); otherwise auto-fit to geometry.
|
|
128
|
+
// Center always defaults to the block's `center` unless we have geometry to
|
|
129
|
+
// frame and no explicit zoom was given.
|
|
130
|
+
const fitted = block.zoom === undefined && allPoints.length > 1 ? viewportForBounds(allPoints) : null;
|
|
131
|
+
const initialViewport = {
|
|
132
|
+
longitude: fitted?.longitude ?? block.center.lng,
|
|
133
|
+
latitude: fitted?.latitude ?? block.center.lat,
|
|
134
|
+
zoom: block.zoom ?? fitted?.zoom ?? 11,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Build the optional props into ONE typed object before spreading, rather
|
|
138
|
+
// than several inline conditional `{...(cond ? {…} : {})}` spreads. The
|
|
139
|
+
// inline form unions each branch to `{} | {prop}` and widens the call to a
|
|
140
|
+
// shape no single `MapContainer` overload accepts (TS2769) under stricter
|
|
141
|
+
// consumer resolution; a single `Partial<MapContainerProps>` literal is
|
|
142
|
+
// checked against the prop type as a unit.
|
|
143
|
+
const mapOptions: Partial<MapContainerProps> = {
|
|
144
|
+
...(block.basemap ? { mapStyle: block.basemap } : {}),
|
|
145
|
+
...(block.terrain ? { terrain: true } : {}),
|
|
146
|
+
...(block.userLocation ? { geolocate: true } : {}),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Frame (radius / border / shadow / clip) comes from the shared
|
|
150
|
+
// `BLOCK_SURFACE` in MessageBlocks — here we only set the height.
|
|
12
151
|
return (
|
|
13
|
-
<div
|
|
14
|
-
className="overflow-hidden rounded-xl border border-border"
|
|
15
|
-
style={{ height }}
|
|
16
|
-
>
|
|
152
|
+
<div className="w-full" style={{ height }}>
|
|
17
153
|
<LazyMapContainer
|
|
18
|
-
initialViewport={
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
zoom: block.zoom ?? 11,
|
|
22
|
-
}}
|
|
154
|
+
initialViewport={initialViewport}
|
|
155
|
+
fullscreenTitle={block.caption ?? 'Map'}
|
|
156
|
+
{...mapOptions}
|
|
23
157
|
>
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
158
|
+
{/* Filled zones first so routes/markers stack above them. */}
|
|
159
|
+
{polygons.map((poly) => {
|
|
160
|
+
const sourceId = `poly-${poly.id}`;
|
|
161
|
+
const data = createFeatureCollection([
|
|
162
|
+
createPolygon(poly.points.map((p): LngLat => [p.lng, p.lat]), poly.label ? { label: poly.label } : {}),
|
|
163
|
+
]);
|
|
164
|
+
const fill = createPolygonLayer(`${sourceId}-fill`, sourceId, {
|
|
165
|
+
fillColor: poly.fillColor,
|
|
166
|
+
strokeColor: poly.strokeColor,
|
|
167
|
+
});
|
|
168
|
+
const outline = createPolygonOutlineLayer(`${sourceId}-outline`, sourceId, {
|
|
169
|
+
color: poly.strokeColor,
|
|
170
|
+
});
|
|
171
|
+
return (
|
|
172
|
+
<MapSource key={sourceId} id={sourceId} data={data}>
|
|
173
|
+
<MapLayer {...fill} />
|
|
174
|
+
<MapLayer {...outline} />
|
|
175
|
+
</MapSource>
|
|
176
|
+
);
|
|
32
177
|
})}
|
|
178
|
+
|
|
179
|
+
{/* Routes (polylines) — white-outlined line via the route factory. */}
|
|
180
|
+
{routes.map((route) => {
|
|
181
|
+
const sourceId = `route-${route.id}`;
|
|
182
|
+
const data = createFeatureCollection([
|
|
183
|
+
createLineString(route.points.map((p): LngLat => [p.lng, p.lat]), route.label ? { label: route.label } : {}),
|
|
184
|
+
]);
|
|
185
|
+
const { outline, route: line } = createRouteLayers({ sourceId, color: route.color });
|
|
186
|
+
return (
|
|
187
|
+
<MapSource key={sourceId} id={sourceId} data={data}>
|
|
188
|
+
<MapLayer {...outline} />
|
|
189
|
+
<MapLayer {...line} />
|
|
190
|
+
</MapSource>
|
|
191
|
+
);
|
|
192
|
+
})}
|
|
193
|
+
|
|
194
|
+
<MapBlockMarkers markers={markers} />
|
|
33
195
|
</LazyMapContainer>
|
|
34
196
|
</div>
|
|
35
197
|
);
|
|
@@ -14,11 +14,8 @@ export default function VideoBlockRenderer({ block }: BlockRendererProps<VideoBl
|
|
|
14
14
|
const source: UrlSource | string = block.poster
|
|
15
15
|
? { type: 'url', url: block.src, poster: block.poster, title: block.title }
|
|
16
16
|
: block.src;
|
|
17
|
+
// Frame (radius / clip) comes from the shared `BLOCK_SURFACE`.
|
|
17
18
|
return (
|
|
18
|
-
<VideoPlayer
|
|
19
|
-
source={source}
|
|
20
|
-
aspectRatio={block.aspectRatio ?? 16 / 9}
|
|
21
|
-
className="overflow-hidden rounded-xl"
|
|
22
|
-
/>
|
|
19
|
+
<VideoPlayer source={source} aspectRatio={block.aspectRatio ?? 16 / 9} />
|
|
23
20
|
);
|
|
24
21
|
}
|
|
@@ -8,7 +8,20 @@ export {
|
|
|
8
8
|
type MessageListHandle,
|
|
9
9
|
} from './MessageList';
|
|
10
10
|
export { MessageBubble, type MessageBubbleProps } from './MessageBubble';
|
|
11
|
+
// Re-exported from the shared `common/link-preview` location (the card was
|
|
12
|
+
// hoisted out of chat so the NotionEditor bookmark node can reuse it). The
|
|
13
|
+
// chat public surface (`@djangocfg/ui-tools/chat`) keeps exporting it from
|
|
14
|
+
// here unchanged.
|
|
15
|
+
export { LinkPreviewCard, type LinkPreviewCardProps } from '../../../common/link-preview';
|
|
11
16
|
export { MessageActions, type MessageActionsProps } from './MessageActions';
|
|
17
|
+
export {
|
|
18
|
+
BubbleContextMenu,
|
|
19
|
+
buildDefaultBubbleMenuItems,
|
|
20
|
+
composeBubbleMenuItems,
|
|
21
|
+
type BubbleContextMenuProps,
|
|
22
|
+
type BubbleMenuItem,
|
|
23
|
+
type DefaultBubbleMenuHandlers,
|
|
24
|
+
} from './BubbleContextMenu';
|
|
12
25
|
export { Sources, type SourcesProps } from './Sources';
|
|
13
26
|
export { ToolCalls, type ToolCallsProps, type ToolPayloadKind } from './ToolCalls';
|
|
14
27
|
export {
|
package/src/tools/chat/public.ts
CHANGED
|
@@ -53,8 +53,12 @@ export type {
|
|
|
53
53
|
JsonBlock,
|
|
54
54
|
MermaidBlock,
|
|
55
55
|
CodeBlock,
|
|
56
|
-
|
|
56
|
+
DiffBlock,
|
|
57
|
+
LinkBlock,
|
|
57
58
|
CustomBlock,
|
|
59
|
+
LinkPreviewData,
|
|
60
|
+
ResolveLinkPreview,
|
|
61
|
+
ChatLinkPreviewConfig,
|
|
58
62
|
} from './types';
|
|
59
63
|
export { DEFAULT_LABELS } from './types';
|
|
60
64
|
|
|
@@ -26,6 +26,7 @@ import { EmptyState } from './EmptyState';
|
|
|
26
26
|
import { ErrorBanner } from './ErrorBanner';
|
|
27
27
|
import { JumpToLatest } from '../messages/JumpToLatest';
|
|
28
28
|
import { MessageBubble } from '../messages/MessageBubble';
|
|
29
|
+
import type { BubbleMenuItem } from '../messages/BubbleContextMenu';
|
|
29
30
|
import { MessageList, type MessageListHandle } from '../messages/MessageList';
|
|
30
31
|
import type { AttachmentRendererMap } from '../messages/Attachments';
|
|
31
32
|
import type { BlockRegistry } from '../messages/blocks';
|
|
@@ -102,6 +103,27 @@ export interface ChatMessagesConfig {
|
|
|
102
103
|
* Omit to use `BUILTIN_BLOCK_REGISTRY`.
|
|
103
104
|
*/
|
|
104
105
|
blockRegistry?: BlockRegistry;
|
|
106
|
+
/**
|
|
107
|
+
* Host-injected EXTRA right-click / long-press menu items per bubble,
|
|
108
|
+
* merged after the built-in Copy / Regenerate / Delete defaults.
|
|
109
|
+
* Forwarded to `<MessageBubble bubbleMenuItems>`.
|
|
110
|
+
*/
|
|
111
|
+
bubbleMenuItems?: BubbleMenuItem[];
|
|
112
|
+
/**
|
|
113
|
+
* Full control over each bubble's context menu — receives the message
|
|
114
|
+
* and the built-in defaults, returns the final ordered list. Beats
|
|
115
|
+
* `bubbleMenuItems`. Forwarded to `<MessageBubble getBubbleMenuItems>`.
|
|
116
|
+
*/
|
|
117
|
+
getBubbleMenuItems?: (message: ChatMessage, defaults: BubbleMenuItem[]) => BubbleMenuItem[];
|
|
118
|
+
/**
|
|
119
|
+
* Host handler for the bubble "Edit" menu item (user messages only — the
|
|
120
|
+
* builtin builder user-gates it). The host loads the message's content into
|
|
121
|
+
* its composer for editing and re-runs the turn on submit via
|
|
122
|
+
* `chat.editMessage` (engine truncate+resend). When omitted the "Edit" item
|
|
123
|
+
* never appears. This is intentionally a host hook, not an inline editor: the
|
|
124
|
+
* edit happens in the real composer, not the bubble.
|
|
125
|
+
*/
|
|
126
|
+
onEdit?: (message: ChatMessage) => void;
|
|
105
127
|
}
|
|
106
128
|
|
|
107
129
|
export interface ChatRootProps {
|
|
@@ -278,8 +300,15 @@ function ChatRootShell({ className, listClassName, parts }: ChatRootShellProps)
|
|
|
278
300
|
blockRegistry={messages.blockRegistry}
|
|
279
301
|
onAttachmentOpen={messages.onAttachmentOpen}
|
|
280
302
|
renderAfterMessage={messages.renderAfter}
|
|
303
|
+
bubbleMenuItems={messages.bubbleMenuItems}
|
|
304
|
+
getBubbleMenuItems={messages.getBubbleMenuItems}
|
|
281
305
|
onCopy={() => copy(m.content)}
|
|
282
306
|
onRegenerate={() => void chat.regenerate(m.id)}
|
|
307
|
+
// Edit (user bubbles): hand the message to the host so it can load the
|
|
308
|
+
// text into the real composer. The host owns the edit-mode UI + the
|
|
309
|
+
// submit branch (chat.editMessage). Omitted handler → no Edit item.
|
|
310
|
+
// No in-bubble editor: editing happens in the composer, not the bubble.
|
|
311
|
+
onEdit={messages.onEdit ? () => messages.onEdit?.(m) : undefined}
|
|
283
312
|
onDelete={() => chat.deleteMessage(m.id)}
|
|
284
313
|
/>
|
|
285
314
|
));
|
|
@@ -23,6 +23,24 @@ export const BUBBLE_SURFACE = {
|
|
|
23
23
|
error: 'bg-destructive/10 text-destructive rounded-tl-md border border-destructive/30',
|
|
24
24
|
} as const;
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Shared media-block surface — the single frame every non-prose
|
|
28
|
+
* `message.blocks` renderer sits in (image, video, gallery, map, audio,
|
|
29
|
+
* code, json, mermaid). Owning the frame here (instead of each
|
|
30
|
+
* renderer inventing its own) guarantees one consistent corner radius,
|
|
31
|
+
* hairline border and soft shadow across all block kinds — the calm,
|
|
32
|
+
* uniform media-card look of Telegram / WhatsApp.
|
|
33
|
+
*
|
|
34
|
+
* `rounded-2xl` (16px) — large, soft messenger corner.
|
|
35
|
+
* `border-border` — hairline, theme-aware.
|
|
36
|
+
* `shadow-sm` — gentle lift off the transcript background.
|
|
37
|
+
* `overflow-hidden` — clips inner content (maps, images) to the radius.
|
|
38
|
+
* `bg-card` — neutral surface so a renderer with a transparent
|
|
39
|
+
* body (json tree) still reads as a card.
|
|
40
|
+
*/
|
|
41
|
+
export const BLOCK_SURFACE =
|
|
42
|
+
'overflow-hidden rounded-2xl border border-border bg-card shadow-sm' as const;
|
|
43
|
+
|
|
26
44
|
/**
|
|
27
45
|
* Anchor (link) classes for markdown content rendered inside a bubble.
|
|
28
46
|
*
|
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
* Attachments + sources attached to a chat message.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Rich-preview payload for a localpreview-style attachment card. Serializable
|
|
7
|
+
* JSON only (no React nodes) so it survives history persistence + SSE — the
|
|
8
|
+
* render seam already exists (`AttachmentRendererMap`); this is the DATA that
|
|
9
|
+
* feeds it. One generic bag instead of a typed field per media kind: a host
|
|
10
|
+
* populates `kind` + `fields[]` (duration, dimensions, pages, EXIF…) and the
|
|
11
|
+
* default/per-type renderer reads it. Absent → renderer falls back to the
|
|
12
|
+
* name+thumbnail chip.
|
|
13
|
+
*/
|
|
14
|
+
export interface AttachmentPreview {
|
|
15
|
+
/** Drives which rich card the renderer mounts (audio/image/pdf/doc/…). */
|
|
16
|
+
kind: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
/** Cover / first-page thumbnail (audio art, pdf page-1). */
|
|
19
|
+
thumbnail?: string;
|
|
20
|
+
/** Ordered label/value rows the card renders (duration, dims, pages, EXIF…). */
|
|
21
|
+
fields?: { label: string; value: string }[];
|
|
22
|
+
}
|
|
23
|
+
|
|
5
24
|
export interface ChatAttachment {
|
|
6
25
|
id: string;
|
|
7
26
|
type: 'image' | 'file' | 'audio' | 'video' | 'text';
|
|
@@ -17,8 +36,6 @@ export interface ChatAttachment {
|
|
|
17
36
|
status?: 'uploading' | 'ready' | 'error';
|
|
18
37
|
/** 0..1 while uploading. */
|
|
19
38
|
progress?: number;
|
|
20
|
-
/** Extracted text from images (OCR). */
|
|
21
|
-
ocrText?: string;
|
|
22
39
|
/**
|
|
23
40
|
* Raw text payload for `type:'text'` chunks — the "Pasted text"
|
|
24
41
|
* attachment minted when a long paste is converted into a chip
|
|
@@ -26,6 +43,13 @@ export interface ChatAttachment {
|
|
|
26
43
|
* Preserves exact content: newlines, indentation, stack traces.
|
|
27
44
|
*/
|
|
28
45
|
text?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Optional rich-preview payload for a localpreview-style card (audio
|
|
48
|
+
* cover+duration, image thumbnail+EXIF, pdf pages, OCR metadata…).
|
|
49
|
+
* Serializable JSON; persists through history/SSE. Absent → the default
|
|
50
|
+
* renderer falls back to the name+thumbnail chip.
|
|
51
|
+
*/
|
|
52
|
+
preview?: AttachmentPreview;
|
|
29
53
|
}
|
|
30
54
|
|
|
31
55
|
export interface ChatSource {
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* persistence and SSE transport. See `@docs/message-blocks.md`.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { LinkPreviewData } from '../../../common/link-preview';
|
|
11
|
+
import type { MapBlockPayload } from '../../../common/blocks';
|
|
12
|
+
|
|
10
13
|
/** Spaciousness hint forwarded from the bubble to renderers. */
|
|
11
14
|
export type BlockAppearance = 'compact' | 'full';
|
|
12
15
|
|
|
@@ -59,11 +62,28 @@ export interface GalleryBlock extends BlockBase {
|
|
|
59
62
|
items: Array<{ id: string; src: string; thumbnail?: string; alt?: string }>;
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Map block — a chat message map. The serializable map SHAPE (center / zoom /
|
|
67
|
+
* basemap / terrain / markers / routes / polygons) is the shared
|
|
68
|
+
* `MapBlockPayload` (defined once in `common/blocks/types.ts`) so the chat
|
|
69
|
+
* map and the NotionEditor `mapBlock` node can't drift. Chat adds only the
|
|
70
|
+
* chat-specific `userLocation` opt-in on top.
|
|
71
|
+
*/
|
|
72
|
+
export interface MapBlock extends BlockBase, MapBlockPayload {
|
|
63
73
|
kind: 'map';
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Opt in to the map's "show my location" chip (privacy-sensitive).
|
|
76
|
+
*
|
|
77
|
+
* This is a plain boolean OPT-IN flag, **not** coordinates — the browser
|
|
78
|
+
* supplies the live position; we never bake lat/lng into the serialized
|
|
79
|
+
* block. Default **off**: only set `true` when the host wants this chat
|
|
80
|
+
* map to offer locate-me. When `true` it surfaces the toolbar locate-me
|
|
81
|
+
* chip (forwarded to `MapContainer`'s `geolocate` prop); the user must
|
|
82
|
+
* still grant the OS location permission, and nothing is requested until
|
|
83
|
+
* they press the chip. Requires a secure context (HTTPS / localhost).
|
|
84
|
+
* @default false
|
|
85
|
+
*/
|
|
86
|
+
userLocation?: boolean;
|
|
67
87
|
}
|
|
68
88
|
|
|
69
89
|
export interface JsonBlock extends BlockBase {
|
|
@@ -84,10 +104,36 @@ export interface CodeBlock extends BlockBase {
|
|
|
84
104
|
language: string;
|
|
85
105
|
}
|
|
86
106
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Code-diff block — wraps the `DiffViewer` tool. Provide EITHER
|
|
109
|
+
* `oldCode` + `newCode` (the viewer runs an internal LCS diff) OR a
|
|
110
|
+
* pre-computed unified `patch` string. `layout` pins side-by-side
|
|
111
|
+
* (`split`) vs single-column (`unified`); when omitted it follows the
|
|
112
|
+
* bubble appearance (compact → unified, full → split).
|
|
113
|
+
*/
|
|
114
|
+
export interface DiffBlock extends BlockBase {
|
|
115
|
+
kind: 'diff';
|
|
116
|
+
oldCode?: string;
|
|
117
|
+
newCode?: string;
|
|
118
|
+
patch?: string;
|
|
119
|
+
language?: string;
|
|
120
|
+
layout?: 'split' | 'unified';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Link-preview (URL unfurl) card — favicon + og:image + title/description/
|
|
125
|
+
* site name, Notion/Telegram style. Either the metadata is already
|
|
126
|
+
* resolved (`data`), or only `url` is given and the host's
|
|
127
|
+
* `resolveLinkPreview` resolver fills it in lazily. See
|
|
128
|
+
* `common/link-preview/types.ts` for the data contract (the browser cannot
|
|
129
|
+
* fetch meta tags itself — resolution is a host concern).
|
|
130
|
+
*/
|
|
131
|
+
export interface LinkBlock extends BlockBase {
|
|
132
|
+
kind: 'link';
|
|
133
|
+
/** The URL to preview (and link to). */
|
|
134
|
+
url: string;
|
|
135
|
+
/** Pre-resolved metadata. When absent, the host resolver is used. */
|
|
136
|
+
data?: LinkPreviewData;
|
|
91
137
|
}
|
|
92
138
|
|
|
93
139
|
/**
|
|
@@ -113,7 +159,8 @@ export type MessageBlock =
|
|
|
113
159
|
| JsonBlock
|
|
114
160
|
| MermaidBlock
|
|
115
161
|
| CodeBlock
|
|
116
|
-
|
|
|
162
|
+
| DiffBlock
|
|
163
|
+
| LinkBlock
|
|
117
164
|
| CustomBlock;
|
|
118
165
|
|
|
119
166
|
/** The discriminant literal — `'audio' | 'video' | …`. */
|