vedana-backoffice 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,204 @@
1
+ import reflex as rx
2
+
3
+ from vedana_backoffice.components.ui_chat import render_message_bubble
4
+ from vedana_backoffice.states.chat import ChatState
5
+ from vedana_backoffice.ui import app_header
6
+
7
+
8
+ def _message_row(msg: dict) -> rx.Component:
9
+ return render_message_bubble(
10
+ msg,
11
+ on_toggle_details=ChatState.toggle_details_by_id(message_id=msg["id"]), # type: ignore[call-arg,func-returns-value]
12
+ )
13
+
14
+
15
+ def page() -> rx.Component:
16
+ return rx.vstack(
17
+ app_header(),
18
+ rx.flex(
19
+ # Messages scroll region
20
+ rx.box(
21
+ rx.scroll_area(
22
+ rx.vstack(
23
+ rx.foreach(ChatState.messages, _message_row),
24
+ spacing="3",
25
+ width="100%",
26
+ ),
27
+ type="always",
28
+ scrollbars="vertical",
29
+ style={"height": "100%"},
30
+ ),
31
+ flex="1",
32
+ min_height="0",
33
+ width="100%",
34
+ ),
35
+ # Typing indicator
36
+ rx.cond(
37
+ ChatState.is_running,
38
+ rx.hstack(
39
+ rx.spinner(),
40
+ rx.text("Generating response..."),
41
+ spacing="2",
42
+ padding_y="0.5em",
43
+ width="100%",
44
+ ),
45
+ ),
46
+ # Sticky action + input bar
47
+ rx.box(
48
+ rx.vstack(
49
+ rx.hstack(
50
+ rx.button(
51
+ "Clear history",
52
+ variant="ghost",
53
+ color_scheme="gray",
54
+ size="1",
55
+ on_click=ChatState.reset_session,
56
+ disabled=rx.cond(
57
+ ChatState.chat_thread_id == "",
58
+ True, # no thread_id --> nothing to reset
59
+ False, # thread_id present --> can be reset
60
+ ),
61
+ ),
62
+ rx.dialog.root(
63
+ rx.dialog.trigger(
64
+ rx.button(
65
+ "Data model",
66
+ variant="ghost",
67
+ color_scheme="gray",
68
+ size="1",
69
+ on_click=ChatState.load_data_model_text,
70
+ ),
71
+ ),
72
+ rx.dialog.content(
73
+ rx.vstack(
74
+ rx.hstack(
75
+ rx.dialog.title("Data Model"),
76
+ rx.spacer(),
77
+ rx.button(
78
+ "Reload",
79
+ variant="soft",
80
+ color_scheme="blue",
81
+ size="1",
82
+ on_click=ChatState.reload_data_model,
83
+ loading=ChatState.is_refreshing_dm,
84
+ ),
85
+ rx.dialog.close(
86
+ rx.button("Close", variant="ghost", color_scheme="gray", size="1"),
87
+ ),
88
+ align="center",
89
+ width="100%",
90
+ ),
91
+ rx.scroll_area(
92
+ rx.markdown(ChatState.data_model_text), # type: ignore[operator]
93
+ type="always",
94
+ scrollbars="vertical",
95
+ style={"height": "70vh"},
96
+ ),
97
+ spacing="3",
98
+ width="100%",
99
+ ),
100
+ max_width="70vw",
101
+ ),
102
+ ),
103
+ rx.checkbox(
104
+ "Filter Data Model",
105
+ checked=ChatState.enable_dm_filtering,
106
+ on_change=ChatState.set_enable_dm_filtering,
107
+ size="1",
108
+ ),
109
+ rx.spacer(),
110
+ rx.hstack(
111
+ rx.cond(
112
+ ChatState.chat_thread_id != "",
113
+ rx.hstack(
114
+ rx.text("thread id: ", size="1", color="gray"),
115
+ rx.button(
116
+ ChatState.chat_thread_id,
117
+ variant="soft",
118
+ size="1",
119
+ on_click=ChatState.open_jims_thread,
120
+ ),
121
+ spacing="1",
122
+ ),
123
+ ),
124
+ rx.cond(
125
+ ChatState.total_conversation_cost > 0,
126
+ rx.text(
127
+ "total cost: " + ChatState.total_conversation_cost_str,
128
+ size="1",
129
+ color="gray",
130
+ ),
131
+ ),
132
+ spacing="2",
133
+ ),
134
+ align="end",
135
+ width="100%",
136
+ ),
137
+ rx.form.root(
138
+ rx.hstack(
139
+ rx.input(
140
+ placeholder="Type your message…",
141
+ value=ChatState.input_text,
142
+ on_change=ChatState.set_input,
143
+ width="100%",
144
+ ),
145
+ rx.select(
146
+ items=["openai", "openrouter"],
147
+ value=ChatState.provider,
148
+ on_change=ChatState.set_provider,
149
+ width="10em",
150
+ placeholder="Provider",
151
+ ),
152
+ rx.cond(
153
+ ChatState.provider == "openrouter",
154
+ rx.input(
155
+ placeholder=rx.cond(
156
+ ChatState.default_openrouter_key_present,
157
+ "(Optional) custom OPENROUTER_API_KEY",
158
+ "(Required) OPENROUTER_API_KEY",
159
+ ),
160
+ type="password",
161
+ value=ChatState.custom_openrouter_key,
162
+ on_change=ChatState.set_custom_openrouter_key,
163
+ width="36em",
164
+ required=rx.cond(ChatState.default_openrouter_key_present, False, True),
165
+ ),
166
+ ),
167
+ rx.select(
168
+ items=ChatState.available_models,
169
+ value=ChatState.model,
170
+ on_change=ChatState.set_model,
171
+ width="16em",
172
+ placeholder="Select model",
173
+ ),
174
+ rx.button("Send", type="submit", loading=ChatState.is_running),
175
+ spacing="2",
176
+ width="100%",
177
+ ),
178
+ on_submit=ChatState.send,
179
+ width="100%",
180
+ ),
181
+ spacing="2",
182
+ width="100%",
183
+ ),
184
+ position="sticky",
185
+ bottom="0",
186
+ padding_top="0.5em",
187
+ padding_bottom="0.5em",
188
+ style={"backgroundColor": "inherit"},
189
+ width="100%",
190
+ ),
191
+ direction="column",
192
+ gap="0.75em",
193
+ flex="1",
194
+ min_height="0",
195
+ width="100%",
196
+ ),
197
+ align="start",
198
+ spacing="2",
199
+ padding="1em",
200
+ height="100vh",
201
+ overflow="hidden",
202
+ on_mount=ChatState.mount,
203
+ width="100%",
204
+ )
@@ -0,0 +1,353 @@
1
+ import reflex as rx
2
+
3
+ from vedana_backoffice.components.etl_graph import etl_graph
4
+ from vedana_backoffice.states.etl import EtlState
5
+ from vedana_backoffice.ui import app_header
6
+
7
+
8
+ def _graph_card() -> rx.Component:
9
+ return rx.card(
10
+ rx.hstack(
11
+ rx.heading("ETL Pipeline", size="4"),
12
+ rx.spacer(),
13
+ rx.hstack(
14
+ rx.hstack(
15
+ rx.text("Data view", size="1", color="gray"),
16
+ rx.switch(checked=EtlState.data_view, on_change=EtlState.set_data_view),
17
+ spacing="2",
18
+ align="center",
19
+ ),
20
+ rx.text("Flow", size="1", color="gray"),
21
+ rx.select(
22
+ items=EtlState.available_flows,
23
+ value=EtlState.selected_flow,
24
+ on_change=EtlState.set_flow,
25
+ width="12em",
26
+ ),
27
+ rx.text("Stage", size="1", color="gray"),
28
+ rx.select(
29
+ items=EtlState.available_stages,
30
+ value=EtlState.selected_stage,
31
+ on_change=EtlState.set_stage,
32
+ width="12em",
33
+ ),
34
+ rx.button("Reset", variant="soft", size="1", on_click=EtlState.reset_filters),
35
+ rx.button("Run Selected", size="1", on_click=EtlState.run_selected, loading=EtlState.is_running),
36
+ rx.tooltip(
37
+ rx.button(
38
+ "↻",
39
+ variant="ghost",
40
+ color_scheme="gray",
41
+ size="1",
42
+ on_click=EtlState.load_pipeline_metadata,
43
+ ),
44
+ content="Reload metadata",
45
+ ),
46
+ spacing="3",
47
+ align="center",
48
+ ),
49
+ align="center",
50
+ width="100%",
51
+ ),
52
+ rx.scroll_area(
53
+ rx.box(
54
+ etl_graph(),
55
+ style={
56
+ "position": "relative",
57
+ "minWidth": EtlState.graph_width_css,
58
+ "minHeight": EtlState.graph_height_css,
59
+ },
60
+ ),
61
+ type="always",
62
+ scrollbars="both",
63
+ style={"height": "65vh", "width": "100%"},
64
+ ),
65
+ padding="1em",
66
+ width="100%",
67
+ )
68
+
69
+
70
+ def _pipeline_panel() -> rx.Component:
71
+ return rx.vstack(
72
+ _graph_card(),
73
+ rx.cond(EtlState.logs_open, _logs_bottom(), rx.box(width="100%")),
74
+ spacing="1",
75
+ width="100%",
76
+ )
77
+
78
+
79
+ def _pipeline_tabs() -> rx.Component:
80
+ return rx.tabs.root(
81
+ rx.tabs.list(
82
+ rx.foreach(
83
+ EtlState.available_pipelines,
84
+ lambda name: rx.tabs.trigger(
85
+ rx.cond(name, name, EtlState.default_pipeline_name),
86
+ value=name,
87
+ ),
88
+ ),
89
+ style={"gap": "0.75rem"},
90
+ ),
91
+ rx.foreach(
92
+ EtlState.available_pipelines,
93
+ lambda name: rx.tabs.content(
94
+ _pipeline_panel(),
95
+ value=name,
96
+ style={"width": "100%"},
97
+ ),
98
+ ),
99
+ value=EtlState.selected_pipeline,
100
+ on_change=EtlState.set_pipeline,
101
+ default_value=EtlState.default_pipeline_name,
102
+ style={"width": "100%"},
103
+ )
104
+
105
+
106
+ def _logs_bottom() -> rx.Component:
107
+ return rx.card(
108
+ rx.hstack(
109
+ rx.heading("Logs", size="3"),
110
+ rx.spacer(),
111
+ rx.button("Hide", variant="ghost", color_scheme="gray", size="1", on_click=EtlState.toggle_logs),
112
+ align="center",
113
+ width="100%",
114
+ ),
115
+ rx.scroll_area(
116
+ rx.vstack(rx.foreach(EtlState.logs, lambda m: rx.text(m))),
117
+ type="always",
118
+ scrollbars="vertical",
119
+ style={"height": "22vh"},
120
+ ),
121
+ padding="1em",
122
+ width="100%",
123
+ )
124
+
125
+
126
+ def _preview_styled_table() -> rx.Component:
127
+ """Table with row styling for changes view and expandable cells."""
128
+
129
+ def _expandable_cell(row: dict[str, rx.Var], col: rx.Var) -> rx.Component:
130
+ """Create an expandable/collapsible cell for long text content."""
131
+ row_id = row.get("row_id", "")
132
+ return rx.table.cell(
133
+ rx.box(
134
+ rx.cond(
135
+ row.get("expanded", False),
136
+ rx.text(
137
+ row.get(col, "—"), # type: ignore[call-overload]
138
+ size="1",
139
+ white_space="pre-wrap",
140
+ style={"wordBreak": "break-word"},
141
+ ),
142
+ rx.text(
143
+ row.get(col, "—"), # type: ignore[call-overload]
144
+ size="1",
145
+ style={
146
+ "display": "-webkit-box",
147
+ "WebkitLineClamp": "2",
148
+ "WebkitBoxOrient": "vertical",
149
+ "overflow": "hidden",
150
+ "textOverflow": "ellipsis",
151
+ "maxWidth": "400px",
152
+ "wordBreak": "break-word",
153
+ },
154
+ ),
155
+ ),
156
+ cursor="pointer",
157
+ on_click=EtlState.toggle_preview_row_expand(row_id=row_id), # type: ignore[arg-type,call-arg,func-returns-value]
158
+ style={"minWidth": "0", "width": "100%"},
159
+ ),
160
+ style={"minWidth": "0"},
161
+ )
162
+
163
+ def _make_row_renderer(row: dict[str, rx.Var]):
164
+ """Create a column renderer that captures the row context."""
165
+ return lambda col: _expandable_cell(row, col)
166
+
167
+ def _row(r: dict[str, rx.Var]) -> rx.Component:
168
+ return rx.table.row(
169
+ rx.foreach(EtlState.preview_columns, _make_row_renderer(r)),
170
+ style=r.get("row_style", {}),
171
+ )
172
+
173
+ return rx.table.root(
174
+ rx.table.header(
175
+ rx.table.row(
176
+ rx.foreach(
177
+ EtlState.preview_columns,
178
+ lambda c: rx.table.column_header_cell(c),
179
+ )
180
+ )
181
+ ),
182
+ rx.table.body(rx.foreach(EtlState.preview_rows, _row)),
183
+ variant="surface",
184
+ style={"width": "100%", "tableLayout": "auto"},
185
+ )
186
+
187
+
188
+ def _table_preview_dialog() -> rx.Component:
189
+ return rx.dialog.root(
190
+ rx.dialog.content(
191
+ rx.vstack(
192
+ rx.hstack(
193
+ rx.dialog.title(
194
+ rx.cond(
195
+ EtlState.preview_display_name,
196
+ EtlState.preview_display_name,
197
+ rx.cond(EtlState.preview_table_name, EtlState.preview_table_name, ""),
198
+ ),
199
+ size="4",
200
+ ),
201
+ rx.spacer(),
202
+ rx.hstack(
203
+ rx.text("Last run changes", size="1", color="gray"),
204
+ rx.switch(
205
+ checked=EtlState.preview_changes_only,
206
+ on_change=EtlState.toggle_preview_changes_only,
207
+ size="1",
208
+ ),
209
+ spacing="2",
210
+ align="center",
211
+ ),
212
+ rx.dialog.close(
213
+ rx.button("Close", variant="ghost", color_scheme="gray", size="1"),
214
+ ),
215
+ align="center",
216
+ width="100%",
217
+ ),
218
+ rx.cond(
219
+ EtlState.has_preview,
220
+ rx.vstack(
221
+ rx.scroll_area(
222
+ _preview_styled_table(),
223
+ type="always",
224
+ scrollbars="both",
225
+ style={"maxHeight": "68vh", "maxWidth": "calc(90vw - 3em)"},
226
+ ),
227
+ # Server-side pagination controls with legend
228
+ rx.hstack(
229
+ rx.text(EtlState.preview_rows_display, size="2", color="gray"),
230
+ # Color legend (only shown in changes view)
231
+ rx.cond(
232
+ EtlState.preview_changes_only,
233
+ rx.hstack(
234
+ rx.hstack(
235
+ rx.box(
236
+ style={
237
+ "width": "12px",
238
+ "height": "12px",
239
+ "backgroundColor": "rgba(34,197,94,0.12)",
240
+ "borderRadius": "2px",
241
+ }
242
+ ),
243
+ rx.text("Added", size="1", color="gray"),
244
+ spacing="1",
245
+ align="center",
246
+ ),
247
+ rx.hstack(
248
+ rx.box(
249
+ style={
250
+ "width": "12px",
251
+ "height": "12px",
252
+ "backgroundColor": "rgba(245,158,11,0.12)",
253
+ "borderRadius": "2px",
254
+ }
255
+ ),
256
+ rx.text("Updated", size="1", color="gray"),
257
+ spacing="1",
258
+ align="center",
259
+ ),
260
+ rx.hstack(
261
+ rx.box(
262
+ style={
263
+ "width": "12px",
264
+ "height": "12px",
265
+ "backgroundColor": "rgba(239,68,68,0.12)",
266
+ "borderRadius": "2px",
267
+ }
268
+ ),
269
+ rx.text("Deleted", size="1", color="gray"),
270
+ spacing="1",
271
+ align="center",
272
+ ),
273
+ spacing="3",
274
+ align="center",
275
+ ),
276
+ rx.box(),
277
+ ),
278
+ rx.spacer(),
279
+ rx.hstack(
280
+ rx.button(
281
+ "⏮",
282
+ variant="soft",
283
+ size="1",
284
+ on_click=EtlState.preview_first_page,
285
+ disabled=~EtlState.preview_has_prev,
286
+ ),
287
+ rx.button(
288
+ "← Prev",
289
+ variant="soft",
290
+ size="1",
291
+ on_click=EtlState.preview_prev_page,
292
+ disabled=~EtlState.preview_has_prev,
293
+ ),
294
+ rx.text(
295
+ EtlState.preview_page_display,
296
+ size="2",
297
+ style={"minWidth": "100px", "textAlign": "center"},
298
+ ),
299
+ rx.button(
300
+ "Next →",
301
+ variant="soft",
302
+ size="1",
303
+ on_click=EtlState.preview_next_page,
304
+ disabled=~EtlState.preview_has_next,
305
+ ),
306
+ rx.button(
307
+ "⏭",
308
+ variant="soft",
309
+ size="1",
310
+ on_click=EtlState.preview_last_page,
311
+ disabled=~EtlState.preview_has_next,
312
+ ),
313
+ spacing="2",
314
+ align="center",
315
+ ),
316
+ width="100%",
317
+ align="center",
318
+ padding_top="0.5em",
319
+ ),
320
+ width="100%",
321
+ spacing="2",
322
+ ),
323
+ rx.cond(
324
+ EtlState.preview_changes_only,
325
+ rx.box(rx.text("No changes in last run")),
326
+ rx.box(rx.text("No data")),
327
+ ),
328
+ ),
329
+ spacing="3",
330
+ width="100%",
331
+ ),
332
+ style={
333
+ "maxWidth": "90vw",
334
+ "maxHeight": "85vh",
335
+ "width": "fit-content",
336
+ "minWidth": "400px",
337
+ },
338
+ ),
339
+ open=EtlState.preview_open,
340
+ on_open_change=EtlState.set_preview_open,
341
+ )
342
+
343
+
344
+ def page() -> rx.Component:
345
+ return rx.vstack(
346
+ app_header(),
347
+ _pipeline_tabs(),
348
+ _table_preview_dialog(),
349
+ align="start",
350
+ spacing="1",
351
+ padding="1em",
352
+ width="100%",
353
+ )