@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,165 @@
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
+ //! A `<textarea>` whose box is sized by its own text.
14
+ //!
15
+ //! A textarea cannot grow with its content — it scrolls at whatever height
16
+ //! it was given. The technique here, shared by [`CodeEditor`] and the chat
17
+ //! prompt, is to make the textarea a purely functional layer: it takes the
18
+ //! keystrokes, the selection and the caret, but paints no glyphs
19
+ //! (`color: transparent`). Underneath sits a `<pre>` mirror of the same
20
+ //! characters which IS painted and IS in flow, so the ordinary layout
21
+ //! engine sizes the box.
22
+ //!
23
+ //! This is deliberately not a JS measure-and-resize loop
24
+ //! (`scrollHeight` → inline `height`): that re-measures on a reflow it
25
+ //! just forced, drifts whenever font-size or the loaded font changes
26
+ //! without an `input` event to re-trigger it, and jitters at fractional
27
+ //! line heights. Here the two layers share their text metrics, so growth
28
+ //! is exact by construction and free at runtime.
29
+ //!
30
+ //! Consumers decide the AXES: the mirror's `white-space` governs growth,
31
+ //! so `pre-wrap` grows only downward (the chat prompt) while `pre` also
32
+ //! grows sideways (the expression editor). They also own the box —
33
+ //! padding, borders, min/max size — with `mirrored-textarea.css` fixing
34
+ //! only what MUST agree between the two layers.
35
+ //!
36
+ //! [`CodeEditor`]: super::code_editor::CodeEditor
37
+
38
+ use yew::html::IntoPropValue;
39
+ use yew::prelude::*;
40
+
41
+ #[derive(Clone)]
42
+ pub struct Mirror(pub Html);
43
+
44
+ impl PartialEq for Mirror {
45
+ fn eq(&self, _other: &Self) -> bool {
46
+ false
47
+ }
48
+ }
49
+
50
+ impl IntoPropValue<Mirror> for Html {
51
+ fn into_prop_value(self) -> Mirror {
52
+ Mirror(self)
53
+ }
54
+ }
55
+
56
+ #[derive(Properties, PartialEq)]
57
+ pub struct MirroredTextareaProps {
58
+ /// `id` of the `<textarea>` itself — consumer CSS and tests key on it.
59
+ pub id: AttrValue,
60
+
61
+ /// The mirror's content: the same characters the textarea holds,
62
+ /// as plain text, or marked up (`CodeEditor` passes highlighted
63
+ /// spans). A stale mirror means a caret that drifts from the glyphs,
64
+ /// so this is re-rendered unconditionally — see [`Mirror`].
65
+ pub mirror: Mirror,
66
+
67
+ /// `id` of the mirror `<pre>`, for consumers that style it directly.
68
+ #[prop_or_default]
69
+ pub mirror_id: Option<AttrValue>,
70
+
71
+ /// Extra classes for the positioned container.
72
+ #[prop_or_default]
73
+ pub class: Classes,
74
+
75
+ #[prop_or_default]
76
+ pub textarea_ref: NodeRef,
77
+
78
+ #[prop_or_default]
79
+ pub mirror_ref: NodeRef,
80
+
81
+ /// The `placeholder` ATTRIBUTE, which exists for assistive tech: it
82
+ /// is painted transparent, because a CSS-`content` label cannot be
83
+ /// written into an attribute and so cannot be localized. The text a
84
+ /// sighted user reads comes from the mirror instead — see
85
+ /// [`Self::is_empty`].
86
+ #[prop_or_default]
87
+ pub placeholder: AttrValue,
88
+
89
+ /// Whether [`Self::mirror`] is empty, which the component cannot see
90
+ /// for itself (the mirror is opaque `Html`). Tags the mirror with
91
+ /// `is-empty` so a consumer's stylesheet can supply placeholder text
92
+ /// through `content`, and therefore through the intl label
93
+ /// mechanism, rather than through the untranslatable attribute.
94
+ #[prop_or_default]
95
+ pub is_empty: bool,
96
+
97
+ #[prop_or_default]
98
+ pub disabled: bool,
99
+
100
+ #[prop_or_default]
101
+ pub oninput: Callback<InputEvent>,
102
+
103
+ #[prop_or_default]
104
+ pub onkeydown: Callback<KeyboardEvent>,
105
+
106
+ #[prop_or_default]
107
+ pub onscroll: Callback<Event>,
108
+
109
+ /// Extra nodes inside the positioned container, e.g. `CodeEditor`'s
110
+ /// minimum-height sizer.
111
+ #[prop_or_default]
112
+ pub children: Children,
113
+ }
114
+
115
+ #[function_component]
116
+ pub fn MirroredTextarea(props: &MirroredTextareaProps) -> Html {
117
+ html! {
118
+ <div class={classes!("mirrored-textarea", props.class.clone())}>
119
+ // `scrollable` is the viewer's webkit scrollbar styling: the
120
+ // input layer scrolls wherever a consumer lets it (the
121
+ // expression editor syncs its `scrollTop` to the mirror and
122
+ // line numbers), and is inert where it does not (the chat
123
+ // prompt, whose wrapper scrolls instead).
124
+ <textarea
125
+ id={props.id.clone()}
126
+ ref={props.textarea_ref.clone()}
127
+ class="mirrored-textarea-input scrollable"
128
+ spellcheck="false"
129
+ placeholder={props.placeholder.clone()}
130
+ disabled={props.disabled}
131
+ oninput={props.oninput.clone()}
132
+ onkeydown={props.onkeydown.clone()}
133
+ onscroll={props.onscroll.clone()}
134
+ />
135
+ { props.children.iter().collect::<Html>() }
136
+ <pre
137
+ id={props.mirror_id.clone()}
138
+ ref={props.mirror_ref.clone()}
139
+ class={classes!(
140
+ "mirrored-textarea-mirror",
141
+ props.is_empty.then_some("is-empty"),
142
+ )}
143
+ >
144
+ { props.mirror.0.clone() }
145
+ // A trailing newline opens a line box in the textarea but
146
+ // not in the `<pre>` (which has no caret to place there),
147
+ // so the mirror would come up one line short exactly when
148
+ // the text is about to overflow. The space forces it.
149
+ { " " }
150
+ </pre>
151
+ </div>
152
+ }
153
+ }
154
+
155
+ #[cfg(test)]
156
+ mod tests {
157
+ use super::*;
158
+
159
+ #[test]
160
+ fn mirror_never_compares_equal() {
161
+ let mirror = Mirror(Html::default());
162
+ assert!(mirror != mirror.clone());
163
+ assert!(Mirror(Html::default()) != Mirror(Html::default()));
164
+ }
165
+ }
@@ -20,6 +20,7 @@ pub mod code_editor;
20
20
  pub mod color_range_selector;
21
21
  pub mod color_selector;
22
22
  pub mod debug;
23
+ pub mod mirrored_textarea;
23
24
  pub mod number_field;
24
25
  pub mod number_input;
25
26
  pub mod number_range_field;
@@ -14,6 +14,8 @@
14
14
  //! necessary for public Custom Elements. The rest are internal components of
15
15
  //! these 4.
16
16
 
17
+ #[cfg(feature = "llm-agent")]
18
+ pub mod chat_panel;
17
19
  pub mod column_dropdown;
18
20
  pub mod column_selector;
19
21
  pub mod column_settings_sidebar;
@@ -21,13 +21,14 @@ use super::plugin_selector::PluginSelector;
21
21
  use super::plugin_tab::PluginTab;
22
22
  use crate::components::containers::sidebar_close_button::SidebarCloseButton;
23
23
  use crate::components::form::debug::DebugPanel;
24
- use crate::config::PluginUpdate;
24
+ use crate::config::{PluginStaticConfig, PluginUpdate};
25
25
  use crate::presentation::{ColumnLocator, OpenColumnSettings, Presentation};
26
26
  use crate::renderer::*;
27
27
  use crate::session::column_defaults_update::*;
28
28
  use crate::session::*;
29
29
  use crate::tasks::update_plugin_and_render;
30
30
  use crate::utils::*;
31
+ use crate::workspace::Workspace;
31
32
 
32
33
  #[derive(Clone, Properties)]
33
34
  pub struct SettingsPanelProps {
@@ -42,6 +43,15 @@ pub struct SettingsPanelProps {
42
43
  pub available_plugins: PtrEqRc<Vec<String>>,
43
44
  pub has_table: Option<TableLoadState>,
44
45
  pub named_column_count: usize,
46
+
47
+ /// The ACTIVE plugin's declared contract, threaded as a value prop so
48
+ /// that switching plugins re-renders the panes that read it. The
49
+ /// renderer handle cannot serve this: it is excluded from prop
50
+ /// equality (it is a handle, not a value), so a plugin swap that
51
+ /// leaves the view config untouched — Y Line back to Datagrid, both
52
+ /// of which name one column slot — would otherwise change nothing any
53
+ /// component compares.
54
+ pub plugin_static_config: Rc<PluginStaticConfig>,
45
55
  pub view_config: PtrEqRc<ViewConfig>,
46
56
 
47
57
  /// Snapshot of the active plugin's `plugin_config` bucket, threaded
@@ -90,6 +100,7 @@ pub struct SettingsPanelProps {
90
100
  pub session: Session,
91
101
  pub renderer: Renderer,
92
102
  pub presentation: Presentation,
103
+ pub workspace: Workspace,
93
104
  }
94
105
 
95
106
  impl PartialEq for SettingsPanelProps {
@@ -99,6 +110,7 @@ impl PartialEq for SettingsPanelProps {
99
110
  && self.available_plugins == rhs.available_plugins
100
111
  && self.has_table == rhs.has_table
101
112
  && self.named_column_count == rhs.named_column_count
113
+ && self.plugin_static_config == rhs.plugin_static_config
102
114
  && self.view_config == rhs.view_config
103
115
  && self.plugin_config == rhs.plugin_config
104
116
  && self.drag_column == rhs.drag_column
@@ -116,6 +128,11 @@ pub enum SelectedTab {
116
128
  Query,
117
129
  Plugin,
118
130
  Debug,
131
+
132
+ /// The embedded LLM agent's chat panel. The variant exists in every
133
+ /// build; its tab button and body render only under the `llm-agent`
134
+ /// feature, and only once `agentConfig()` has been called.
135
+ Chat,
119
136
  }
120
137
 
121
138
  #[function_component]
@@ -186,8 +203,15 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
186
203
  .unwrap();
187
204
 
188
205
  let group_rollups = plugin_config.get_group_rollups(&rollup_features);
206
+ let split_rollup_features = session_metadata
207
+ .get_features()
208
+ .map(|x| x.get_split_rollup_modes())
209
+ .unwrap();
210
+
211
+ let split_rollups = plugin_config.get_split_rollups(&split_rollup_features);
189
212
  let mut update = ViewConfigUpdate {
190
213
  group_rollup_mode: group_rollups.first().cloned(),
214
+ split_rollup_mode: split_rollups.first().cloned(),
191
215
  ..ViewConfigUpdate::default()
192
216
  };
193
217
 
@@ -237,6 +261,52 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
237
261
  }
238
262
  };
239
263
 
264
+ // The chat tab is zero-affordance until `agentConfig()` is called; the
265
+ // subscription re-renders this panel when that happens (and as the
266
+ // transcript updates while the chat body is mounted).
267
+ #[cfg(feature = "llm-agent")]
268
+ let (chat_tab_button, chat_body) = {
269
+ let update = use_force_update();
270
+ let agent = presentation.agent.clone();
271
+ use_effect_with((), move |_| {
272
+ let sub = agent
273
+ .on_update
274
+ .add_notify_listener(&Callback::from(move |_| update.force_update()));
275
+
276
+ move || drop(sub)
277
+ });
278
+
279
+ let button = if presentation.agent.is_configured() {
280
+ let on_select_column = props.on_select_column.clone();
281
+ let set_chat = {
282
+ let on_select_tab = props.on_select_tab.clone();
283
+ Callback::from(move |_: PointerEvent| {
284
+ on_select_tab.emit(SelectedTab::Chat);
285
+ on_select_column.emit(None)
286
+ })
287
+ };
288
+
289
+ html! {
290
+ <div
291
+ id="chat_tabbar_tab"
292
+ class={tab_class(selected, SelectedTab::Chat)}
293
+ onpointerdown={set_chat}
294
+ />
295
+ }
296
+ } else {
297
+ html! {}
298
+ };
299
+
300
+ let body = html! {
301
+ <crate::components::chat_panel::ChatPanel agent={presentation.agent.clone()} />
302
+ };
303
+
304
+ (button, body)
305
+ };
306
+
307
+ #[cfg(not(feature = "llm-agent"))]
308
+ let (chat_tab_button, chat_body) = (html! {}, html! {});
309
+
240
310
  let on_open_expr_panel = use_callback(props.on_select_column.clone(), |c, on_select| {
241
311
  on_select.emit(Some(c))
242
312
  });
@@ -270,6 +340,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
270
340
  class={tab_class(selected, SelectedTab::Debug)}
271
341
  onpointerdown={set_debug}
272
342
  />
343
+ { chat_tab_button }
273
344
  </div>
274
345
  if selected == SelectedTab::Query {
275
346
  <ColumnSelector
@@ -278,6 +349,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
278
349
  {selected_column}
279
350
  has_table={props.has_table.clone()}
280
351
  named_column_count={props.named_column_count}
352
+ plugin_static_config={props.plugin_static_config.clone()}
281
353
  view_config={props.view_config.clone()}
282
354
  drag_column={props.drag_column.clone()}
283
355
  metadata={props.metadata.clone()}
@@ -298,11 +370,14 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
298
370
  // initial_width={width}
299
371
  // on_auto_width={on_auto_width.clone()}
300
372
  />
373
+ } else if selected == SelectedTab::Chat {
374
+ { chat_body }
301
375
  } else {
302
376
  <DebugPanel
303
377
  {presentation}
304
378
  {renderer}
305
379
  {session}
380
+ workspace={props.workspace.clone()}
306
381
  initial_width={width}
307
382
  on_auto_width={on_auto_width.clone()}
308
383
  />
@@ -310,7 +385,10 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
310
385
  // Sibling sizer keeps the panel width pinned across tab
311
386
  // switches; lives outside the tab-body so it survives the
312
387
  // tab subtree's unmount.
313
- <div class="scroll-panel-auto-width" style={format!("width:{}px", width)} />
388
+ <div
389
+ class="scroll-panel-auto-width"
390
+ style={format!("width:{}px", width)}
391
+ />
314
392
  </div>
315
393
  }
316
394
  }
@@ -27,7 +27,7 @@ use super::wiring::{
27
27
  clear_active_callbacks, create_active_subscriptions, inject_active_callbacks,
28
28
  subscribe_panel_titles,
29
29
  };
30
- use crate::config::{TableUpdate, ViewerConfigUpdate};
30
+ use crate::config::{ViewerConfigInitial, ViewerConfigUpdate};
31
31
  use crate::queries::*;
32
32
  use crate::renderer::Renderer;
33
33
  use crate::session::*;
@@ -216,18 +216,18 @@ impl PerspectiveViewer {
216
216
 
217
217
  pub(super) fn on_new_panel(&mut self, ctx: &Context<Self>, id: String) -> bool {
218
218
  if let Some(panel) = ctx.props().workspace.panel(&PanelId::from(id)) {
219
- let table_name = panel.session.get_table().map(|t| t.get_name().to_owned());
219
+ let Some(table_name) = panel.session.get_table().map(|t| t.get_name().to_owned())
220
+ else {
221
+ tracing::warn!("Source panel has no `Table` to create a new panel from");
222
+ return false;
223
+ };
224
+
220
225
  let elem = ctx.props().elem.clone();
221
226
  let presentation = ctx.props().presentation.clone();
222
227
  let workspace = ctx.props().workspace.clone();
223
228
  let notify = ctx.link().callback(|_: ()| LayoutChanged);
224
229
  let activate = ctx.link().callback(|id| SetActivePanel(id, None));
225
230
  ApiFuture::spawn(async move {
226
- let update = ViewerConfigUpdate {
227
- table: table_name.map(TableUpdate::Update).unwrap_or_default(),
228
- ..Default::default()
229
- };
230
-
231
231
  let client = panel.session.get_client();
232
232
  let new_id = create_panel(
233
233
  &elem,
@@ -235,7 +235,7 @@ impl PerspectiveViewer {
235
235
  &workspace,
236
236
  &notify,
237
237
  None,
238
- update,
238
+ ViewerConfigInitial::new(table_name).into(),
239
239
  client,
240
240
  )
241
241
  .await?;
@@ -273,18 +273,13 @@ impl PerspectiveViewer {
273
273
  let notify = ctx.link().callback(|_: ()| LayoutChanged);
274
274
  let activate = ctx.link().callback(|id| SetActivePanel(id, None));
275
275
  ApiFuture::spawn(async move {
276
- let update = ViewerConfigUpdate {
277
- table: TableUpdate::Update(table),
278
- ..Default::default()
279
- };
280
-
281
276
  let new_id = create_panel(
282
277
  &elem,
283
278
  &presentation,
284
279
  &workspace,
285
280
  &notify,
286
281
  None,
287
- update,
282
+ ViewerConfigInitial::new(table).into(),
288
283
  Some(client),
289
284
  )
290
285
  .await?;
@@ -94,6 +94,7 @@ impl PerspectiveViewer {
94
94
  {available_plugins}
95
95
  {has_table}
96
96
  {named_column_count}
97
+ plugin_static_config={self.renderer_props.config.clone()}
97
98
  {view_config}
98
99
  plugin_config={self.renderer_props.plugin_config.clone()}
99
100
  {drag_column}
@@ -108,6 +109,7 @@ impl PerspectiveViewer {
108
109
  {presentation}
109
110
  {renderer}
110
111
  {session}
112
+ workspace={ctx.props().workspace.clone()}
111
113
  />
112
114
  }
113
115
  } else {