@perspective-dev/viewer 5.1.0 → 5.2.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.
Files changed (175) hide show
  1. package/dist/cdn/perspective-viewer.js +9 -9
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/intl/de.css +1 -1
  8. package/dist/css/intl/es.css +1 -1
  9. package/dist/css/intl/fr.css +1 -1
  10. package/dist/css/intl/ja.css +1 -1
  11. package/dist/css/intl/pt.css +1 -1
  12. package/dist/css/intl/zh.css +1 -1
  13. package/dist/css/intl.css +1 -1
  14. package/dist/css/monokai.css +1 -1
  15. package/dist/css/phosphor.css +1 -1
  16. package/dist/css/pro-dark.css +1 -1
  17. package/dist/css/pro.css +1 -1
  18. package/dist/css/solarized-dark.css +1 -1
  19. package/dist/css/solarized.css +1 -1
  20. package/dist/css/themes.css +1 -1
  21. package/dist/css/vaporwave.css +1 -1
  22. package/dist/docs/perspective-docs.json +1 -0
  23. package/dist/esm/column-format.d.ts +19 -28
  24. package/dist/esm/perspective-viewer.d.ts +2 -0
  25. package/dist/esm/perspective-viewer.inline.js +8 -8
  26. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  27. package/dist/esm/perspective-viewer.js +9 -9
  28. package/dist/esm/perspective-viewer.js.map +4 -4
  29. package/dist/esm/providers.d.ts +79 -0
  30. package/dist/esm/ts-rs/CompactDisplay.d.ts +5 -0
  31. package/dist/esm/ts-rs/CurrencyCode.d.ts +1 -0
  32. package/dist/esm/ts-rs/CurrencyDisplay.d.ts +1 -0
  33. package/dist/esm/ts-rs/CurrencyNumberFormatStyle.d.ts +8 -0
  34. package/dist/esm/ts-rs/CurrencySign.d.ts +1 -0
  35. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +1 -0
  36. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +54 -0
  37. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +34 -0
  38. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +1 -0
  39. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +6 -0
  40. package/dist/esm/ts-rs/FormatMode.d.ts +1 -0
  41. package/dist/esm/ts-rs/FormatUnit.d.ts +1 -0
  42. package/dist/esm/ts-rs/Notation.d.ts +17 -0
  43. package/dist/esm/ts-rs/NumberFormatStyle.d.ts +18 -0
  44. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +41 -1
  45. package/dist/esm/ts-rs/PluginStaticConfig.d.ts +35 -0
  46. package/dist/esm/ts-rs/RestoreOptions.d.ts +19 -0
  47. package/dist/esm/ts-rs/RoundingMode.d.ts +1 -0
  48. package/dist/esm/ts-rs/RoundingPriority.d.ts +1 -0
  49. package/dist/esm/ts-rs/SignDisplay.d.ts +1 -0
  50. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +1 -0
  51. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +24 -0
  52. package/dist/esm/ts-rs/SplitRollupMode.d.ts +9 -0
  53. package/dist/esm/ts-rs/StringColorMode.d.ts +1 -0
  54. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +1 -0
  55. package/dist/esm/ts-rs/Unit.d.ts +1 -0
  56. package/dist/esm/ts-rs/UnitDisplay.d.ts +1 -0
  57. package/dist/esm/ts-rs/UnitNumberFormatStyle.d.ts +6 -0
  58. package/dist/esm/ts-rs/UseGrouping.d.ts +1 -0
  59. package/dist/esm/ts-rs/ViewerConfig.d.ts +41 -1
  60. package/dist/esm/ts-rs/ViewerConfigInitial.d.ts +175 -0
  61. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +63 -0
  62. package/dist/esm/ts-rs/WindowSpec.d.ts +3 -2
  63. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +7 -6
  64. package/dist/wasm/perspective-viewer.d.ts +134 -25
  65. package/dist/wasm/perspective-viewer.js +367 -124
  66. package/dist/wasm/perspective-viewer.wasm +0 -0
  67. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -15
  68. package/package.json +2 -1
  69. package/src/css/_viewer_bundle.css +1 -0
  70. package/src/css/column-selector.css +8 -0
  71. package/src/css/config-selector.css +38 -3
  72. package/src/css/form/code-editor.css +2 -3
  73. package/src/css/form/mirrored-textarea.css +81 -0
  74. package/src/css/viewer.css +328 -0
  75. package/src/rust/agent/client/mod.rs +19 -0
  76. package/src/rust/agent/client/protocol.rs +320 -0
  77. package/src/rust/agent/client/transport.rs +287 -0
  78. package/src/rust/agent/client/turn.rs +174 -0
  79. package/src/rust/agent/config.rs +159 -0
  80. package/src/rust/agent/docs.rs +293 -0
  81. package/src/rust/agent/markdown/block.rs +509 -0
  82. package/src/rust/agent/markdown/entity.rs +52 -0
  83. package/src/rust/agent/markdown/events.rs +71 -0
  84. package/src/rust/agent/markdown/inline.rs +497 -0
  85. package/src/rust/agent/markdown/mod.rs +466 -0
  86. package/src/rust/agent/mod.rs +23 -0
  87. package/src/rust/agent/preamble.md +16 -0
  88. package/src/rust/agent/runtime.rs +201 -0
  89. package/src/rust/agent/slot.rs +236 -0
  90. package/src/rust/agent/tools.rs +1110 -0
  91. package/src/rust/components/chat_panel.rs +289 -0
  92. package/src/rust/components/column_selector/config_selector.rs +76 -7
  93. package/src/rust/components/column_selector.rs +7 -0
  94. package/src/rust/components/containers/dragdrop_list.rs +37 -0
  95. package/src/rust/components/form/code_editor.rs +14 -23
  96. package/src/rust/components/form/debug.rs +8 -5
  97. package/src/rust/components/form/mirrored_textarea.rs +165 -0
  98. package/src/rust/components/form/mod.rs +1 -0
  99. package/src/rust/components/mod.rs +2 -0
  100. package/src/rust/components/settings_panel.rs +80 -2
  101. package/src/rust/components/viewer/panels.rs +9 -14
  102. package/src/rust/components/viewer/render.rs +2 -0
  103. package/src/rust/components/window_editor.rs +75 -103
  104. package/src/rust/config/datetime_column_style/custom.rs +17 -0
  105. package/src/rust/config/datetime_column_style/simple.rs +10 -0
  106. package/src/rust/config/number_string_format.rs +33 -0
  107. package/src/rust/config/options.rs +18 -0
  108. package/src/rust/config/plugin_static_config.rs +120 -5
  109. package/src/rust/config/viewer_config.rs +220 -1
  110. package/src/rust/config/workspace_config.rs +7 -6
  111. package/src/rust/custom_elements/viewer.rs +209 -18
  112. package/src/rust/custom_events.rs +29 -0
  113. package/src/rust/lib.rs +13 -0
  114. package/src/rust/presentation.rs +8 -0
  115. package/src/rust/queries/get_viewer_config.rs +5 -1
  116. package/src/rust/session/column_defaults_update.rs +18 -0
  117. package/src/rust/session/metadata.rs +18 -20
  118. package/src/rust/session/replace_expression_update.rs +1 -0
  119. package/src/rust/tasks/create_panel.rs +18 -8
  120. package/src/rust/tasks/edit_window.rs +1 -1
  121. package/src/rust/tasks/restore_panel.rs +34 -17
  122. package/src/rust/tasks/sync_update_panels.rs +7 -23
  123. package/src/rust/workspace.rs +112 -20
  124. package/src/themes/intl/de.css +16 -0
  125. package/src/themes/intl/es.css +16 -0
  126. package/src/themes/intl/fr.css +16 -0
  127. package/src/themes/intl/ja.css +16 -0
  128. package/src/themes/intl/pt.css +16 -0
  129. package/src/themes/intl/zh.css +16 -0
  130. package/src/themes/intl.css +16 -0
  131. package/src/ts/column-format.ts +67 -46
  132. package/src/ts/perspective-viewer.ts +2 -0
  133. package/src/ts/providers.ts +96 -0
  134. package/src/ts/ts-rs/CompactDisplay.ts +3 -0
  135. package/src/ts/ts-rs/CurrencyCode.ts +3 -0
  136. package/src/ts/ts-rs/CurrencyDisplay.ts +3 -0
  137. package/src/ts/ts-rs/CurrencyNumberFormatStyle.ts +6 -0
  138. package/src/ts/ts-rs/CurrencySign.ts +3 -0
  139. package/src/ts/ts-rs/CustomDatetimeFormat.ts +3 -0
  140. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +54 -0
  141. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +25 -0
  142. package/src/ts/ts-rs/DatetimeColorMode.ts +3 -0
  143. package/src/ts/ts-rs/DatetimeFormatType.ts +8 -0
  144. package/src/ts/ts-rs/FormatMode.ts +3 -0
  145. package/src/ts/ts-rs/FormatUnit.ts +3 -0
  146. package/src/ts/ts-rs/Notation.ts +11 -0
  147. package/src/ts/ts-rs/NumberFormatStyle.ts +12 -0
  148. package/src/ts/ts-rs/PanelViewerConfig.ts +47 -1
  149. package/src/ts/ts-rs/PluginStaticConfig.ts +36 -1
  150. package/src/ts/ts-rs/RestoreOptions.ts +20 -0
  151. package/src/ts/ts-rs/RoundingMode.ts +3 -0
  152. package/src/ts/ts-rs/RoundingPriority.ts +3 -0
  153. package/src/ts/ts-rs/SignDisplay.ts +3 -0
  154. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +3 -0
  155. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +25 -0
  156. package/src/ts/ts-rs/SplitRollupMode.ts +11 -0
  157. package/src/ts/ts-rs/StringColorMode.ts +3 -0
  158. package/src/ts/ts-rs/TrailingZeroDisplay.ts +3 -0
  159. package/src/ts/ts-rs/Unit.ts +3 -0
  160. package/src/ts/ts-rs/UnitDisplay.ts +3 -0
  161. package/src/ts/ts-rs/UnitNumberFormatStyle.ts +5 -0
  162. package/src/ts/ts-rs/UseGrouping.ts +3 -0
  163. package/src/ts/ts-rs/ViewerConfig.ts +47 -1
  164. package/src/ts/ts-rs/ViewerConfigInitial.ts +164 -0
  165. package/src/ts/ts-rs/ViewerConfigUpdate.ts +72 -2
  166. package/src/ts/ts-rs/WindowSpec.ts +3 -2
  167. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -6
  168. package/dist/esm/ts-rs/WindowAggregate.d.ts +0 -1
  169. package/src/ts/ts-rs/WindowAggregate.ts +0 -3
  170. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-02e461f3d7062b26}/inline0.js +0 -0
  171. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-02e461f3d7062b26}/inline1.js +0 -0
  172. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-02e461f3d7062b26}/inline2.js +0 -0
  173. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-02e461f3d7062b26}/inline3.js +0 -0
  174. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-02e461f3d7062b26}/inline4.js +0 -0
  175. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-02e461f3d7062b26}/inline5.js +0 -0
@@ -0,0 +1,466 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ mod block;
14
+ mod entity;
15
+ mod events;
16
+ mod inline;
17
+
18
+ use events::{Align, Event, Tag};
19
+ use yew::virtual_dom::{VNode, VTag, VText};
20
+ use yew::{Html, html};
21
+
22
+ /// Render one chat message's markdown as a Yew tree.
23
+ pub fn render_markdown(text: &str) -> Html {
24
+ let mut writer = Writer::default();
25
+ for event in block::parse(text) {
26
+ match event {
27
+ Event::Start(tag) => writer.start(tag),
28
+ Event::End => writer.end(),
29
+ event => writer.leaf(event),
30
+ }
31
+ }
32
+
33
+ while !writer.stack.is_empty() {
34
+ writer.end();
35
+ }
36
+
37
+ html! { <>{ for writer.root.into_iter() }</> }
38
+ }
39
+
40
+ fn is_allowed_url(url: &str) -> bool {
41
+ let url = url.trim().to_ascii_lowercase();
42
+ url.starts_with("http://") || url.starts_with("https://") || url.starts_with("mailto:")
43
+ }
44
+
45
+ fn heading_name(level: u8) -> &'static str {
46
+ match level {
47
+ 1 => "h1",
48
+ 2 => "h2",
49
+ 3 => "h3",
50
+ 4 => "h4",
51
+ 5 => "h5",
52
+ _ => "h6",
53
+ }
54
+ }
55
+
56
+ fn align_class(align: &Align) -> Option<&'static str> {
57
+ match align {
58
+ Align::None => None,
59
+ Align::Left => Some("chat-md-align-left"),
60
+ Align::Center => Some("chat-md-align-center"),
61
+ Align::Right => Some("chat-md-align-right"),
62
+ }
63
+ }
64
+
65
+ fn external_link(dest_url: &str) -> VTag {
66
+ let mut anchor = VTag::new("a");
67
+ anchor.add_attribute("href", dest_url.to_string());
68
+ anchor.add_attribute("target", "_blank");
69
+ anchor.add_attribute("rel", "noopener noreferrer");
70
+ anchor
71
+ }
72
+
73
+ enum Frame {
74
+ Node(VTag),
75
+ Pre(VTag),
76
+ HeadRow(VTag),
77
+ Transparent(Vec<VNode>),
78
+ Image {
79
+ dest_url: String,
80
+ children: Vec<VNode>,
81
+ },
82
+ }
83
+
84
+ #[derive(Default)]
85
+ struct Writer {
86
+ stack: Vec<Frame>,
87
+ root: Vec<VNode>,
88
+ aligns: Vec<Align>,
89
+ in_head: bool,
90
+ cell_idx: usize,
91
+ }
92
+
93
+ impl Writer {
94
+ fn start(&mut self, tag: Tag) {
95
+ let frame = match tag {
96
+ Tag::Paragraph => Frame::Node(VTag::new("p")),
97
+ Tag::Heading(level) => Frame::Node(VTag::new(heading_name(level))),
98
+ Tag::BlockQuote => Frame::Node(VTag::new("blockquote")),
99
+ Tag::CodeBlock(lang) => {
100
+ let mut code = VTag::new("code");
101
+ if let Some(lang) = lang
102
+ && !lang.is_empty()
103
+ {
104
+ code.add_attribute("data-lang", lang);
105
+ }
106
+
107
+ Frame::Pre(code)
108
+ },
109
+ Tag::List(None) => Frame::Node(VTag::new("ul")),
110
+ Tag::List(Some(start)) => {
111
+ let mut list = VTag::new("ol");
112
+ if start != 1 {
113
+ list.add_attribute("start", start.to_string());
114
+ }
115
+
116
+ Frame::Node(list)
117
+ },
118
+ Tag::Item => Frame::Node(VTag::new("li")),
119
+ Tag::Emphasis => Frame::Node(VTag::new("em")),
120
+ Tag::Strong => Frame::Node(VTag::new("strong")),
121
+ Tag::Strikethrough => Frame::Node(VTag::new("del")),
122
+ Tag::Link { dest_url, title } if is_allowed_url(&dest_url) => {
123
+ let mut anchor = external_link(&dest_url);
124
+ if !title.is_empty() {
125
+ anchor.add_attribute("title", title);
126
+ }
127
+
128
+ Frame::Node(anchor)
129
+ },
130
+ Tag::Link { .. } => Frame::Transparent(vec![]),
131
+ Tag::Image { dest_url } => Frame::Image {
132
+ dest_url,
133
+ children: vec![],
134
+ },
135
+ Tag::Table(aligns) => {
136
+ self.aligns = aligns;
137
+ Frame::Node(VTag::new("table"))
138
+ },
139
+ Tag::TableHead => {
140
+ self.in_head = true;
141
+ self.cell_idx = 0;
142
+ Frame::HeadRow(VTag::new("tr"))
143
+ },
144
+ Tag::TableRow => {
145
+ self.cell_idx = 0;
146
+ Frame::Node(VTag::new("tr"))
147
+ },
148
+ Tag::TableCell => {
149
+ let mut cell = VTag::new(if self.in_head { "th" } else { "td" });
150
+ if let Some(class) = self.aligns.get(self.cell_idx).and_then(align_class) {
151
+ cell.add_attribute("class", class);
152
+ }
153
+
154
+ self.cell_idx += 1;
155
+ Frame::Node(cell)
156
+ },
157
+ };
158
+
159
+ self.stack.push(frame);
160
+ }
161
+
162
+ fn end(&mut self) {
163
+ match self.stack.pop() {
164
+ Some(Frame::Node(tag)) => self.append(tag.into()),
165
+ Some(Frame::Pre(code)) => {
166
+ let mut pre = VTag::new("pre");
167
+
168
+ // Code blocks scroll horizontally (`viewer.css`), so they
169
+ // opt into the viewer's scrollbar styling like every
170
+ // other scroller in the shadow root.
171
+ pre.add_attribute("class", "scrollable");
172
+ pre.add_child(code.into());
173
+ self.append(pre.into());
174
+ },
175
+ Some(Frame::HeadRow(row)) => {
176
+ self.in_head = false;
177
+ let mut head = VTag::new("thead");
178
+ head.add_child(row.into());
179
+ self.append(head.into());
180
+ },
181
+ Some(Frame::Transparent(children)) => {
182
+ for child in children {
183
+ self.append(child);
184
+ }
185
+ },
186
+ Some(Frame::Image { dest_url, children }) => {
187
+ let mut alt = VTag::new("span");
188
+ alt.add_attribute("class", "chat-md-image");
189
+ for child in children {
190
+ alt.add_child(child);
191
+ }
192
+
193
+ self.append(alt.into());
194
+ if is_allowed_url(&dest_url) {
195
+ let mut anchor = external_link(&dest_url);
196
+ anchor.add_child(VText::new(" (image)").into());
197
+ self.append(anchor.into());
198
+ }
199
+ },
200
+ None => (),
201
+ }
202
+ }
203
+
204
+ fn leaf(&mut self, event: Event) {
205
+ match event {
206
+ Event::Text(text) => self.append(VText::new(text).into()),
207
+ Event::Code(text) => {
208
+ let mut code = VTag::new("code");
209
+ code.add_child(VText::new(text).into());
210
+ self.append(code.into());
211
+ },
212
+ Event::SoftBreak => self.append(VText::new(" ").into()),
213
+ Event::HardBreak => self.append(VTag::new("br").into()),
214
+ Event::Rule => self.append(VTag::new("hr").into()),
215
+ Event::Start(_) | Event::End => (),
216
+ }
217
+ }
218
+
219
+ fn append(&mut self, node: VNode) {
220
+ match self.stack.last_mut() {
221
+ Some(Frame::Node(tag) | Frame::Pre(tag) | Frame::HeadRow(tag)) => tag.add_child(node),
222
+ Some(Frame::Transparent(children) | Frame::Image { children, .. }) => {
223
+ children.push(node)
224
+ },
225
+ None => self.root.push(node),
226
+ }
227
+ }
228
+ }
229
+
230
+ #[cfg(test)]
231
+ mod tests {
232
+ use super::events::{Align, Event, Tag};
233
+
234
+ fn parse(text: &str) -> Vec<Event> {
235
+ super::block::parse(text)
236
+ }
237
+
238
+ fn start(tag: Tag) -> Event {
239
+ Event::Start(tag)
240
+ }
241
+
242
+ fn text(x: &str) -> Event {
243
+ Event::Text(x.to_owned())
244
+ }
245
+
246
+ #[test]
247
+ fn paragraph_and_emphasis() {
248
+ assert_eq!(parse("**Done!** Key *stats* here."), vec![
249
+ start(Tag::Paragraph),
250
+ start(Tag::Strong),
251
+ text("Done!"),
252
+ Event::End,
253
+ text(" Key "),
254
+ start(Tag::Emphasis),
255
+ text("stats"),
256
+ Event::End,
257
+ text(" here."),
258
+ Event::End,
259
+ ]);
260
+ }
261
+
262
+ #[test]
263
+ fn snake_case_is_not_emphasis() {
264
+ assert_eq!(parse("call get_schema then set_view_config"), vec![
265
+ start(Tag::Paragraph),
266
+ text("call get_schema then set_view_config"),
267
+ Event::End,
268
+ ]);
269
+ }
270
+
271
+ #[test]
272
+ fn nested_strong_emphasis() {
273
+ assert_eq!(parse("**bold with *ital* inside**"), vec![
274
+ start(Tag::Paragraph),
275
+ start(Tag::Strong),
276
+ text("bold with "),
277
+ start(Tag::Emphasis),
278
+ text("ital"),
279
+ Event::End,
280
+ text(" inside"),
281
+ Event::End,
282
+ Event::End,
283
+ ]);
284
+ }
285
+
286
+ #[test]
287
+ fn unpaired_delimiters_stay_literal() {
288
+ // Space on both sides ⇒ the runs can neither open nor close, so
289
+ // they stay in the running text buffer.
290
+ assert_eq!(parse("2 * 3 * 4 and a ~ tilde"), vec![
291
+ start(Tag::Paragraph),
292
+ text("2 * 3 * 4 and a ~ tilde"),
293
+ Event::End,
294
+ ]);
295
+ }
296
+
297
+ #[test]
298
+ fn code_spans_and_strikethrough() {
299
+ assert_eq!(parse("run `pnpm test` and ~~skip~~ this"), vec![
300
+ start(Tag::Paragraph),
301
+ text("run "),
302
+ Event::Code("pnpm test".to_owned()),
303
+ text(" and "),
304
+ start(Tag::Strikethrough),
305
+ text("skip"),
306
+ Event::End,
307
+ text(" this"),
308
+ Event::End,
309
+ ]);
310
+ }
311
+
312
+ #[test]
313
+ fn heading_rule_and_fence() {
314
+ assert_eq!(parse("## Stats\n\n---\n\n```sql\nSELECT 1;\n```"), vec![
315
+ start(Tag::Heading(2)),
316
+ text("Stats"),
317
+ Event::End,
318
+ Event::Rule,
319
+ start(Tag::CodeBlock(Some("sql".to_owned()))),
320
+ text("SELECT 1;\n"),
321
+ Event::End,
322
+ ]);
323
+ }
324
+
325
+ #[test]
326
+ fn nested_list_with_code_block_in_item() {
327
+ assert_eq!(
328
+ parse("- first\n- second:\n ```\n x = 1\n ```\n - inner"),
329
+ vec![
330
+ start(Tag::List(None)),
331
+ start(Tag::Item),
332
+ text("first"),
333
+ Event::End,
334
+ start(Tag::Item),
335
+ text("second:"),
336
+ start(Tag::CodeBlock(None)),
337
+ text("x = 1\n"),
338
+ Event::End,
339
+ start(Tag::List(None)),
340
+ start(Tag::Item),
341
+ text("inner"),
342
+ Event::End,
343
+ Event::End,
344
+ Event::End,
345
+ Event::End,
346
+ ]
347
+ );
348
+ }
349
+
350
+ #[test]
351
+ fn ordered_list_start() {
352
+ assert_eq!(parse("3. three\n4. four"), vec![
353
+ start(Tag::List(Some(3))),
354
+ start(Tag::Item),
355
+ text("three"),
356
+ Event::End,
357
+ start(Tag::Item),
358
+ text("four"),
359
+ Event::End,
360
+ Event::End,
361
+ ]);
362
+ }
363
+
364
+ #[test]
365
+ fn table_with_alignment_and_pipe_in_code() {
366
+ assert_eq!(
367
+ parse("| Region | Sales |\n| :--- | ---: |\n| West | `a\\|b` |"),
368
+ vec![
369
+ start(Tag::Table(vec![Align::Left, Align::Right])),
370
+ start(Tag::TableHead),
371
+ start(Tag::TableCell),
372
+ text("Region"),
373
+ Event::End,
374
+ start(Tag::TableCell),
375
+ text("Sales"),
376
+ Event::End,
377
+ Event::End,
378
+ start(Tag::TableRow),
379
+ start(Tag::TableCell),
380
+ text("West"),
381
+ Event::End,
382
+ start(Tag::TableCell),
383
+ Event::Code("a|b".to_owned()),
384
+ Event::End,
385
+ Event::End,
386
+ Event::End,
387
+ ]
388
+ );
389
+ }
390
+
391
+ #[test]
392
+ fn dashes_after_paragraph_are_a_rule_not_a_table() {
393
+ assert_eq!(parse("Title\n---"), vec![
394
+ start(Tag::Paragraph),
395
+ text("Title"),
396
+ Event::End,
397
+ Event::Rule,
398
+ ]);
399
+ }
400
+
401
+ #[test]
402
+ fn links_images_and_autolinks() {
403
+ assert_eq!(
404
+ parse("[safe](https://example.com \"hi\") ![leak](https://example.com/x.png) <https://a.co>"),
405
+ vec![
406
+ start(Tag::Paragraph),
407
+ start(Tag::Link {
408
+ dest_url: "https://example.com".to_owned(),
409
+ title: "hi".to_owned(),
410
+ }),
411
+ text("safe"),
412
+ Event::End,
413
+ text(" "),
414
+ start(Tag::Image {
415
+ dest_url: "https://example.com/x.png".to_owned(),
416
+ }),
417
+ text("leak"),
418
+ Event::End,
419
+ text(" "),
420
+ start(Tag::Link {
421
+ dest_url: "https://a.co".to_owned(),
422
+ title: String::new(),
423
+ }),
424
+ text("https://a.co"),
425
+ Event::End,
426
+ Event::End,
427
+ ]
428
+ );
429
+ }
430
+
431
+ #[test]
432
+ fn raw_html_is_literal_text() {
433
+ assert_eq!(parse("<script>alert(1)</script>"), vec![
434
+ start(Tag::Paragraph),
435
+ text("<script>alert(1)</script>"),
436
+ Event::End,
437
+ ]);
438
+ }
439
+
440
+ #[test]
441
+ fn blockquote_and_breaks() {
442
+ assert_eq!(parse("> quoted\n> more\n\nafter \nbreak"), vec![
443
+ start(Tag::BlockQuote),
444
+ start(Tag::Paragraph),
445
+ text("quoted"),
446
+ Event::SoftBreak,
447
+ text("more"),
448
+ Event::End,
449
+ Event::End,
450
+ start(Tag::Paragraph),
451
+ text("after"),
452
+ Event::HardBreak,
453
+ text("break"),
454
+ Event::End,
455
+ ]);
456
+ }
457
+
458
+ #[test]
459
+ fn entities_decode_or_stay_literal() {
460
+ assert_eq!(parse("a &amp; b &#65; &unknown; c"), vec![
461
+ start(Tag::Paragraph),
462
+ text("a & b A &unknown; c"),
463
+ Event::End,
464
+ ]);
465
+ }
466
+ }
@@ -0,0 +1,23 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ mod client;
14
+ mod config;
15
+ mod docs;
16
+ mod markdown;
17
+ mod runtime;
18
+ mod slot;
19
+ mod tools;
20
+
21
+ pub use markdown::render_markdown;
22
+ pub use runtime::AgentRuntime;
23
+ pub use slot::{AgentSlot, ChatEntry};
@@ -0,0 +1,16 @@
1
+ You are an assistant embedded in a Perspective data viewer, a component for interactive analysis of large datasets. You act on the viewer through tools.
2
+
3
+ Workflow:
4
+ 1. Call `get_schema` first to learn the exact column names and types.
5
+ 2. Use `list_plugins` to pick a visualization when the user asks for a chart, AND to read that plugin's column roles before you write `columns`. `columns` is positional and each plugin reads the positions differently: a `Y Line` plots `columns[0..]` as Y series against `group_by` as its X axis (natural row order when `group_by` is empty), while an `X/Y Line` plots `columns[0]` as X against `columns[1]` as Y. Never assume the roles.
6
+ 3. Call `search_docs` with concrete keywords (e.g. `expressions bucket date`, not full sentences) whenever you are unsure which field, value or syntax expresses the user's intent: expression syntax beyond basic arithmetic, `plugin_config` / `columns_config` keys, aggregate or window semantics, or the host's data definitions. It is cheap - it does not re-render the viewer - and far cheaper than a failed configuration call.
7
+ 4. If the configuration will include `expressions` (computed columns), draft each expression with `validate_expression` BEFORE applying the configuration; skip this step when there are no expressions.
8
+ 5. Plan the COMPLETE target configuration - plugin, columns, group_by, sort, expressions (with their names referenced in `columns`, `group_by`, etc.) - and apply it in a single `set_view_config` call, which applies it as one partial patch and returns the resulting configuration. Tools that take a config re-render the viewer on every call, so batch all changes into one call; never build a view up field-by-field.
9
+
10
+ Panels: the viewer is a dashboard that can hold multiple panels (independent side-by-side views). View tools act on the active panel unless you pass `panel`; manage the layout with `list_panels`, `add_panel`, `remove_panel` and `activate_panel`. `add_panel` takes the new panel's complete configuration up front - `table` and `columns` are required. To duplicate a panel, pass its `get_view_config` result to `add_panel`.
11
+
12
+ Rules:
13
+ - Column names in configs must match the schema exactly, including case.
14
+ - If a tool returns an error, correct your input and retry. If the same call fails twice with the same error, stop and report the problem instead.
15
+ - You have a fixed budget of model requests per prompt - be economical with tool calls.
16
+ - After acting, answer with a single short sentence describing what changed. If the user asks a question you can answer from tool results, answer it directly.