Spaces:
Runtime error
Runtime error
Commit ·
4fe75bc
1
Parent(s): 5a14532
final commit
Browse files- ticketiq/rxconfig.py +3 -7
- ticketiq/ticketiq/card.py +41 -33
- ticketiq/ticketiq/modal.py +26 -22
- ticketiq/ticketiq/pages/playground.py +32 -0
- ticketiq/ticketiq/pages/stream_page.py +50 -0
- ticketiq/ticketiq/state.py +100 -4
- ticketiq/ticketiq/stats.py +20 -20
- ticketiq/ticketiq/sth.json +21 -0
- ticketiq/ticketiq/ticketiq.py +79 -6
- ticketiq/ticketiq/ui.py +25 -15
ticketiq/rxconfig.py
CHANGED
|
@@ -2,11 +2,7 @@ import reflex as rx
|
|
| 2 |
|
| 3 |
config = rx.Config(
|
| 4 |
app_name="ticketiq",
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
frontend_path = "",
|
| 9 |
-
backend_port = 7860,
|
| 10 |
-
api_url="http://localhost:7860",
|
| 11 |
-
disable_plugins = ["SitemapPlugin"]
|
| 12 |
)
|
|
|
|
| 2 |
|
| 3 |
config = rx.Config(
|
| 4 |
app_name="ticketiq",
|
| 5 |
+
frontend_port=3000,
|
| 6 |
+
backend_port=8001,
|
| 7 |
+
api_url="http://localhost:8001",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
)
|
ticketiq/ticketiq/card.py
CHANGED
|
@@ -75,38 +75,41 @@ def ticket_card(ticket: dict, show_similar: bool = True) -> rx.Component:
|
|
| 75 |
margin_bottom="14px",
|
| 76 |
),
|
| 77 |
|
| 78 |
-
# ── solution toggle ──────────────
|
| 79 |
-
rx.
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
rx.
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
rx.
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
),
|
| 101 |
-
margin_top="10px", padding="14px 16px",
|
| 102 |
-
background="rgba(245,166,35,0.04)",
|
| 103 |
-
border=f"1px solid {BORDER}",
|
| 104 |
-
border_left=f"3px solid {ACCENT}",
|
| 105 |
-
border_radius="4px",
|
| 106 |
),
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
),
|
| 111 |
),
|
| 112 |
|
|
@@ -124,11 +127,16 @@ def ticket_card(ticket: dict, show_similar: bool = True) -> rx.Component:
|
|
| 124 |
),
|
| 125 |
|
| 126 |
# ── card shell ────────────────────────────────────────────────────
|
|
|
|
| 127 |
background=SURFACE,
|
| 128 |
-
border=f"
|
| 129 |
border_radius=RADIUS,
|
| 130 |
padding="20px",
|
| 131 |
width="100%",
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
)
|
|
|
|
| 75 |
margin_bottom="14px",
|
| 76 |
),
|
| 77 |
|
| 78 |
+
# ── solution toggle (only when solution is present) ──────────────
|
| 79 |
+
rx.cond(
|
| 80 |
+
ticket["solution"] != "",
|
| 81 |
+
rx.box(
|
| 82 |
+
rx.button(
|
| 83 |
+
rx.icon(rx.cond(is_open, "chevron-up", "chevron-down"), size=12),
|
| 84 |
+
rx.icon("zap", size=12),
|
| 85 |
+
rx.text("Solution", font_size="12px", font_family=SANS),
|
| 86 |
+
on_click=TicketState.toggle_expanded(tid),
|
| 87 |
+
display="flex", align_items="center", gap="5px",
|
| 88 |
+
background=rx.cond(is_open, ACCENT_BG, "transparent"),
|
| 89 |
+
color=rx.cond(is_open, ACCENT, MUTED),
|
| 90 |
+
border=rx.cond(is_open, f"1px solid {ACCENT}", f"1px solid {BORDER}"),
|
| 91 |
+
border_radius="4px", padding="5px 10px",
|
| 92 |
+
cursor="pointer", font_family=SANS,
|
| 93 |
+
transition="all 0.15s",
|
| 94 |
+
),
|
| 95 |
+
rx.cond(
|
| 96 |
+
is_open,
|
| 97 |
+
rx.box(
|
| 98 |
+
rx.text(
|
| 99 |
+
ticket["solution"],
|
| 100 |
+
font_size="13px", line_height="1.75",
|
| 101 |
+
color=TEXT, white_space="pre-wrap",
|
| 102 |
+
),
|
| 103 |
+
margin_top="10px", padding="14px 16px",
|
| 104 |
+
background="rgba(245,166,35,0.04)",
|
| 105 |
+
border=f"1px solid {BORDER}",
|
| 106 |
+
border_left=f"3px solid {ACCENT}",
|
| 107 |
+
border_radius="4px",
|
| 108 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
),
|
| 110 |
+
margin_bottom=rx.cond(
|
| 111 |
+
show_similar & TicketState.has_similar_tickets, "14px", "0"
|
| 112 |
+
),
|
| 113 |
),
|
| 114 |
),
|
| 115 |
|
|
|
|
| 127 |
),
|
| 128 |
|
| 129 |
# ── card shell ────────────────────────────────────────────────────
|
| 130 |
+
# ── card shell ────────────────────────────────────────────────────
|
| 131 |
background=SURFACE,
|
| 132 |
+
border=f"2px solid {rx.cond(rx.color_mode == 'dark', '#4a4a5e', '#c0c0cc')}",
|
| 133 |
border_radius=RADIUS,
|
| 134 |
padding="20px",
|
| 135 |
width="100%",
|
| 136 |
+
box_shadow="0 2px 8px rgba(0,0,0,0.15)",
|
| 137 |
+
_hover={
|
| 138 |
+
"border_color": ACCENT,
|
| 139 |
+
"box_shadow": f"0 4px 16px {ACCENT_BG}",
|
| 140 |
+
},
|
| 141 |
+
transition="all 0.2s ease",
|
| 142 |
)
|
ticketiq/ticketiq/modal.py
CHANGED
|
@@ -40,12 +40,12 @@ def ticket_modal() -> rx.Component:
|
|
| 40 |
return rx.cond(
|
| 41 |
TicketState.modal_open,
|
| 42 |
rx.box(
|
| 43 |
-
# ── backdrop ────────────────────────────────
|
| 44 |
rx.box(
|
| 45 |
on_click=TicketState.close_modal,
|
| 46 |
position="fixed", top="0", left="0",
|
| 47 |
width="100vw", height="100vh",
|
| 48 |
-
background=
|
| 49 |
z_index="40",
|
| 50 |
),
|
| 51 |
|
|
@@ -60,25 +60,25 @@ def ticket_modal() -> rx.Component:
|
|
| 60 |
),
|
| 61 |
rx.text(
|
| 62 |
"Ticket Detail",
|
| 63 |
-
font_size="
|
| 64 |
),
|
| 65 |
gap="2px", align="start",
|
| 66 |
),
|
| 67 |
rx.spacer(),
|
| 68 |
rx.icon_button(
|
| 69 |
-
rx.icon("x", size=
|
| 70 |
on_click=TicketState.close_modal,
|
| 71 |
background="transparent",
|
| 72 |
-
border=f"1px solid {
|
| 73 |
-
border_radius="
|
| 74 |
color=MUTED,
|
| 75 |
cursor="pointer",
|
| 76 |
size="2",
|
| 77 |
variant="ghost",
|
| 78 |
),
|
| 79 |
align="center",
|
| 80 |
-
padding="
|
| 81 |
-
border_bottom=f"1px solid {
|
| 82 |
),
|
| 83 |
|
| 84 |
# scrollable body
|
|
@@ -88,10 +88,10 @@ def ticket_modal() -> rx.Component:
|
|
| 88 |
section_label("DESCRIPTION"),
|
| 89 |
rx.text(
|
| 90 |
t["description"],
|
| 91 |
-
font_size="
|
| 92 |
color=TEXT, white_space="pre-wrap",
|
| 93 |
),
|
| 94 |
-
margin_bottom="
|
| 95 |
),
|
| 96 |
|
| 97 |
# label + department
|
|
@@ -99,17 +99,17 @@ def ticket_modal() -> rx.Component:
|
|
| 99 |
section_label("CATEGORY"),
|
| 100 |
mono(
|
| 101 |
t["label"],
|
| 102 |
-
font_size="
|
| 103 |
letter_spacing="0.02em",
|
| 104 |
),
|
| 105 |
rx.cond(
|
| 106 |
t["department"] != "Uncategorised",
|
| 107 |
rx.text(
|
| 108 |
t["department"],
|
| 109 |
-
font_size="
|
| 110 |
),
|
| 111 |
),
|
| 112 |
-
margin_bottom="
|
| 113 |
),
|
| 114 |
|
| 115 |
# badges + confidence
|
|
@@ -117,12 +117,12 @@ def ticket_modal() -> rx.Component:
|
|
| 117 |
priority_badge(t["priority"]),
|
| 118 |
source_badge(t["source"]),
|
| 119 |
gap="8px",
|
| 120 |
-
margin_bottom="
|
| 121 |
),
|
| 122 |
rx.box(
|
| 123 |
rx.text("Confidence", font_size="11px", color=MUTED, margin_bottom="4px"),
|
| 124 |
confidence_bar(t["confidence"]),
|
| 125 |
-
margin_bottom="
|
| 126 |
),
|
| 127 |
|
| 128 |
# solution
|
|
@@ -161,7 +161,7 @@ def ticket_modal() -> rx.Component:
|
|
| 161 |
border_left=f"3px solid {ACCENT}",
|
| 162 |
border_radius="4px",
|
| 163 |
),
|
| 164 |
-
margin_bottom="
|
| 165 |
),
|
| 166 |
|
| 167 |
# similar tickets
|
|
@@ -173,23 +173,27 @@ def ticket_modal() -> rx.Component:
|
|
| 173 |
),
|
| 174 |
),
|
| 175 |
|
| 176 |
-
padding="
|
| 177 |
overflow_y="auto",
|
| 178 |
flex="1",
|
| 179 |
),
|
| 180 |
|
| 181 |
-
# panel shell
|
| 182 |
position="fixed",
|
| 183 |
top="0", right="0",
|
| 184 |
-
width=
|
| 185 |
max_width="95vw",
|
| 186 |
height="100vh",
|
| 187 |
-
background=
|
| 188 |
-
border_left=f"
|
| 189 |
z_index="50",
|
| 190 |
display="flex",
|
| 191 |
flex_direction="column",
|
| 192 |
-
box_shadow=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
),
|
| 194 |
),
|
| 195 |
)
|
|
|
|
| 40 |
return rx.cond(
|
| 41 |
TicketState.modal_open,
|
| 42 |
rx.box(
|
| 43 |
+
# ── backdrop (darker overlay) ────────────────────────────────
|
| 44 |
rx.box(
|
| 45 |
on_click=TicketState.close_modal,
|
| 46 |
position="fixed", top="0", left="0",
|
| 47 |
width="100vw", height="100vh",
|
| 48 |
+
background="rgba(0,0,0,0.7)",
|
| 49 |
z_index="40",
|
| 50 |
),
|
| 51 |
|
|
|
|
| 60 |
),
|
| 61 |
rx.text(
|
| 62 |
"Ticket Detail",
|
| 63 |
+
font_size="16px", font_weight="600", color=TEXT,
|
| 64 |
),
|
| 65 |
gap="2px", align="start",
|
| 66 |
),
|
| 67 |
rx.spacer(),
|
| 68 |
rx.icon_button(
|
| 69 |
+
rx.icon("x", size=18),
|
| 70 |
on_click=TicketState.close_modal,
|
| 71 |
background="transparent",
|
| 72 |
+
border=f"1px solid {BORDER_HI}",
|
| 73 |
+
border_radius="8px",
|
| 74 |
color=MUTED,
|
| 75 |
cursor="pointer",
|
| 76 |
size="2",
|
| 77 |
variant="ghost",
|
| 78 |
),
|
| 79 |
align="center",
|
| 80 |
+
padding="24px",
|
| 81 |
+
border_bottom=f"1px solid {BORDER_HI}",
|
| 82 |
),
|
| 83 |
|
| 84 |
# scrollable body
|
|
|
|
| 88 |
section_label("DESCRIPTION"),
|
| 89 |
rx.text(
|
| 90 |
t["description"],
|
| 91 |
+
font_size="14px", line_height="1.7",
|
| 92 |
color=TEXT, white_space="pre-wrap",
|
| 93 |
),
|
| 94 |
+
margin_bottom="24px",
|
| 95 |
),
|
| 96 |
|
| 97 |
# label + department
|
|
|
|
| 99 |
section_label("CATEGORY"),
|
| 100 |
mono(
|
| 101 |
t["label"],
|
| 102 |
+
font_size="13px", color=ACCENT,
|
| 103 |
letter_spacing="0.02em",
|
| 104 |
),
|
| 105 |
rx.cond(
|
| 106 |
t["department"] != "Uncategorised",
|
| 107 |
rx.text(
|
| 108 |
t["department"],
|
| 109 |
+
font_size="13px", color=MUTED, margin_top="4px",
|
| 110 |
),
|
| 111 |
),
|
| 112 |
+
margin_bottom="24px",
|
| 113 |
),
|
| 114 |
|
| 115 |
# badges + confidence
|
|
|
|
| 117 |
priority_badge(t["priority"]),
|
| 118 |
source_badge(t["source"]),
|
| 119 |
gap="8px",
|
| 120 |
+
margin_bottom="16px",
|
| 121 |
),
|
| 122 |
rx.box(
|
| 123 |
rx.text("Confidence", font_size="11px", color=MUTED, margin_bottom="4px"),
|
| 124 |
confidence_bar(t["confidence"]),
|
| 125 |
+
margin_bottom="24px",
|
| 126 |
),
|
| 127 |
|
| 128 |
# solution
|
|
|
|
| 161 |
border_left=f"3px solid {ACCENT}",
|
| 162 |
border_radius="4px",
|
| 163 |
),
|
| 164 |
+
margin_bottom="24px",
|
| 165 |
),
|
| 166 |
|
| 167 |
# similar tickets
|
|
|
|
| 173 |
),
|
| 174 |
),
|
| 175 |
|
| 176 |
+
padding="24px",
|
| 177 |
overflow_y="auto",
|
| 178 |
flex="1",
|
| 179 |
),
|
| 180 |
|
| 181 |
+
# panel shell – now with high‑contrast background and bold left edge
|
| 182 |
position="fixed",
|
| 183 |
top="0", right="0",
|
| 184 |
+
width="540px",
|
| 185 |
max_width="95vw",
|
| 186 |
height="100vh",
|
| 187 |
+
background=rx.cond(rx.color_mode == "dark", "#262630", "#f5f6fa"),
|
| 188 |
+
border_left=f"3px solid {rx.cond(rx.color_mode == 'dark', '#5a5a70', '#a0a0b0')}",
|
| 189 |
z_index="50",
|
| 190 |
display="flex",
|
| 191 |
flex_direction="column",
|
| 192 |
+
box_shadow=rx.cond(
|
| 193 |
+
rx.color_mode == "dark",
|
| 194 |
+
"-12px 0 40px rgba(255,255,255,0.05), 0 0 0 1px rgba(255,255,255,0.05)",
|
| 195 |
+
"-12px 0 40px rgba(0,0,0,0.25), 0 0 0 1px rgba(0,0,0,0.1)",
|
| 196 |
+
),
|
| 197 |
),
|
| 198 |
),
|
| 199 |
)
|
ticketiq/ticketiq/pages/playground.py
CHANGED
|
@@ -4,6 +4,37 @@ from ..ui import (
|
|
| 4 |
mono, ACCENT, BORDER, MUTED, TEXT, SURFACE, SANS, RADIUS, section_label
|
| 5 |
)
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
def embedding_explorer() -> rx.Component:
|
| 8 |
return rx.card(
|
| 9 |
rx.vstack(
|
|
@@ -18,6 +49,7 @@ def embedding_explorer() -> rx.Component:
|
|
| 18 |
rx.button("Probe", on_click=PlaygroundState.probe),
|
| 19 |
rx.cond(
|
| 20 |
PlaygroundState.probe_results.length() > 0,
|
|
|
|
| 21 |
rx.table.root(
|
| 22 |
rx.table.header(
|
| 23 |
rx.table.row(
|
|
|
|
| 4 |
mono, ACCENT, BORDER, MUTED, TEXT, SURFACE, SANS, RADIUS, section_label
|
| 5 |
)
|
| 6 |
|
| 7 |
+
def network_graph_card() -> rx.Component:
|
| 8 |
+
return rx.box(
|
| 9 |
+
# Header + button (outside the iframe)
|
| 10 |
+
rx.vstack(
|
| 11 |
+
section_label("SIMILAR TICKETS NETWORK (DRAGGABLE)"),
|
| 12 |
+
rx.button("Show Network", on_click=PlaygroundState.show_network),
|
| 13 |
+
spacing="3",
|
| 14 |
+
margin_bottom="8px",
|
| 15 |
+
),
|
| 16 |
+
# Iframe in a dedicated container that forces 100% width
|
| 17 |
+
rx.cond(
|
| 18 |
+
PlaygroundState.network_url != "",
|
| 19 |
+
rx.box(
|
| 20 |
+
rx.html(
|
| 21 |
+
f'<iframe src="{PlaygroundState.network_url}" '
|
| 22 |
+
'style="width:100%; height:520px; border:none; display:block;"></iframe>'
|
| 23 |
+
),
|
| 24 |
+
width="100%",
|
| 25 |
+
# This ensures the box doesn't shrink
|
| 26 |
+
flex="1",
|
| 27 |
+
),
|
| 28 |
+
),
|
| 29 |
+
# Card shell
|
| 30 |
+
width="100%",
|
| 31 |
+
background=SURFACE,
|
| 32 |
+
border=f"1px solid {BORDER}",
|
| 33 |
+
border_radius=RADIUS,
|
| 34 |
+
padding="12px",
|
| 35 |
+
margin_top="16px",
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
def embedding_explorer() -> rx.Component:
|
| 39 |
return rx.card(
|
| 40 |
rx.vstack(
|
|
|
|
| 49 |
rx.button("Probe", on_click=PlaygroundState.probe),
|
| 50 |
rx.cond(
|
| 51 |
PlaygroundState.probe_results.length() > 0,
|
| 52 |
+
network_graph_card(),
|
| 53 |
rx.table.root(
|
| 54 |
rx.table.header(
|
| 55 |
rx.table.row(
|
ticketiq/ticketiq/pages/stream_page.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import reflex as rx
|
| 2 |
+
from ..state import StreamState
|
| 3 |
+
from ..ui import (
|
| 4 |
+
ACCENT, BORDER, MUTED, TEXT, SURFACE, SANS, RADIUS, LOW_C,
|
| 5 |
+
)
|
| 6 |
+
|
| 7 |
+
def stream_page() -> rx.Component:
|
| 8 |
+
return rx.box(
|
| 9 |
+
# Live indicator
|
| 10 |
+
rx.hstack(
|
| 11 |
+
rx.box(
|
| 12 |
+
width="8px", height="8px", border_radius="50%", background=LOW_C,
|
| 13 |
+
style={"animation": "pulse 1.5s infinite"},
|
| 14 |
+
),
|
| 15 |
+
rx.text("LIVE", font_size="24px", font_weight="bold", color=ACCENT),
|
| 16 |
+
rx.text("●", font_size="14px", color=LOW_C),
|
| 17 |
+
rx.text(
|
| 18 |
+
StreamState.live_tickets.length().to_string() + " tickets",
|
| 19 |
+
font_size="14px", color=MUTED,
|
| 20 |
+
),
|
| 21 |
+
gap="8px", align="center", margin_bottom="24px",
|
| 22 |
+
),
|
| 23 |
+
|
| 24 |
+
# Feed – rendered as HTML (no foreach)
|
| 25 |
+
rx.vstack(
|
| 26 |
+
rx.cond(
|
| 27 |
+
StreamState.live_tickets.length() == 0,
|
| 28 |
+
rx.text("Waiting for tickets…", font_size="14px", color=MUTED, padding="40px"),
|
| 29 |
+
rx.html(StreamState.feed_html),
|
| 30 |
+
),
|
| 31 |
+
width="100%",
|
| 32 |
+
),
|
| 33 |
+
|
| 34 |
+
# Animations
|
| 35 |
+
rx.el.style("""
|
| 36 |
+
@keyframes slideIn {
|
| 37 |
+
from { opacity: 0; transform: translateY(-20px); }
|
| 38 |
+
to { opacity: 1; transform: translateY(0); }
|
| 39 |
+
}
|
| 40 |
+
@keyframes pulse {
|
| 41 |
+
0%, 100% { opacity: 1; }
|
| 42 |
+
50% { opacity: 0.4; }
|
| 43 |
+
}
|
| 44 |
+
"""),
|
| 45 |
+
|
| 46 |
+
max_width="900px", margin="0 auto",
|
| 47 |
+
padding="30px 24px", width="100%",
|
| 48 |
+
on_mount=StreamState.start_stream,
|
| 49 |
+
on_unmount=StreamState.stop_stream,
|
| 50 |
+
)
|
ticketiq/ticketiq/state.py
CHANGED
|
@@ -1,8 +1,17 @@
|
|
| 1 |
-
import httpx
|
| 2 |
import reflex as rx
|
|
|
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
TIMEOUT = 60.0
|
| 7 |
PAGE_SIZE = 12
|
| 8 |
|
|
@@ -434,6 +443,7 @@ class PlaygroundState(rx.State):
|
|
| 434 |
batch_text: str = ""
|
| 435 |
batch_results: list[dict] = []
|
| 436 |
|
|
|
|
| 437 |
def set_probe_text(self, value: str):
|
| 438 |
self.probe_text = value
|
| 439 |
|
|
@@ -483,4 +493,90 @@ class PlaygroundState(rx.State):
|
|
| 483 |
json={"texts": lines}
|
| 484 |
)
|
| 485 |
data = resp.json()
|
| 486 |
-
self.batch_results = data["results"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import httpx, asyncio
|
| 2 |
import reflex as rx
|
| 3 |
+
import time, json
|
| 4 |
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
from .ui import (
|
| 8 |
+
mono, ACCENT, CHART_BLUE,
|
| 9 |
+
ACCENT, BORDER, MUTED, TEXT, SURFACE, SANS, MONO, RADIUS,
|
| 10 |
+
HIGH, MED, LOW_C, HIGH_BG, MED_BG, LOW_BG,
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
API_BASE = "http://localhost:8000/api/v1/tickets"
|
| 14 |
+
API_BASE_V1 = "http://localhost:8000/api/v1"
|
| 15 |
TIMEOUT = 60.0
|
| 16 |
PAGE_SIZE = 12
|
| 17 |
|
|
|
|
| 443 |
batch_text: str = ""
|
| 444 |
batch_results: list[dict] = []
|
| 445 |
|
| 446 |
+
|
| 447 |
def set_probe_text(self, value: str):
|
| 448 |
self.probe_text = value
|
| 449 |
|
|
|
|
| 493 |
json={"texts": lines}
|
| 494 |
)
|
| 495 |
data = resp.json()
|
| 496 |
+
self.batch_results = data["results"]
|
| 497 |
+
|
| 498 |
+
network_url: str = ""
|
| 499 |
+
|
| 500 |
+
async def show_network(self):
|
| 501 |
+
if not self.probe_text.strip():
|
| 502 |
+
return
|
| 503 |
+
async with httpx.AsyncClient(timeout=120) as client:
|
| 504 |
+
resp = await client.post(
|
| 505 |
+
f"{API_BASE_V1}/dev/probe-tickets-graph",
|
| 506 |
+
json={"text": self.probe_text.strip()}
|
| 507 |
+
)
|
| 508 |
+
data = resp.json()
|
| 509 |
+
self.network_url = data["url"]
|
| 510 |
+
|
| 511 |
+
|
| 512 |
+
class ThemeState(rx.State):
|
| 513 |
+
theme: str = "dark"
|
| 514 |
+
|
| 515 |
+
def toggle(self):
|
| 516 |
+
self.theme = "light" if self.theme == "dark" else "dark"
|
| 517 |
+
|
| 518 |
+
@rx.var
|
| 519 |
+
def opposite(self) -> str:
|
| 520 |
+
return "light" if self.theme == "dark" else "dark"
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
class StreamState(rx.State):
|
| 524 |
+
live_tickets: list[dict] = []
|
| 525 |
+
last_id: int = 0
|
| 526 |
+
streaming: bool = False
|
| 527 |
+
|
| 528 |
+
async def start_stream(self):
|
| 529 |
+
self.streaming = True
|
| 530 |
+
self.last_id = 0
|
| 531 |
+
self.live_tickets = []
|
| 532 |
+
return StreamState.poll
|
| 533 |
+
|
| 534 |
+
async def poll(self):
|
| 535 |
+
if not self.streaming:
|
| 536 |
+
return
|
| 537 |
+
try:
|
| 538 |
+
async with httpx.AsyncClient(timeout=10) as client:
|
| 539 |
+
r = await client.get(
|
| 540 |
+
f"{API_BASE_V1}/stream/latest",
|
| 541 |
+
params={"since_id": self.last_id, "limit": 20},
|
| 542 |
+
)
|
| 543 |
+
data = r.json()
|
| 544 |
+
new_tickets = data.get("tickets", [])
|
| 545 |
+
if new_tickets:
|
| 546 |
+
self.live_tickets = new_tickets + self.live_tickets
|
| 547 |
+
self.last_id = new_tickets[0]["id"]
|
| 548 |
+
self.live_tickets = self.live_tickets[:50]
|
| 549 |
+
except Exception:
|
| 550 |
+
pass
|
| 551 |
+
await asyncio.sleep(3)
|
| 552 |
+
return StreamState.poll
|
| 553 |
+
|
| 554 |
+
async def stop_stream(self):
|
| 555 |
+
self.streaming = False
|
| 556 |
+
self.live_tickets = []
|
| 557 |
+
self.last_id = 0
|
| 558 |
+
|
| 559 |
+
@rx.var
|
| 560 |
+
def feed_html(self) -> str:
|
| 561 |
+
"""Render the live ticket feed as an HTML string."""
|
| 562 |
+
if not self.live_tickets:
|
| 563 |
+
return ""
|
| 564 |
+
rows = []
|
| 565 |
+
for t in self.live_tickets:
|
| 566 |
+
p = t.get("priority", "low").lower()
|
| 567 |
+
color = {"high": "#ef4444", "medium": "#f59e0b", "low": "#22c55e"}.get(p, "#888")
|
| 568 |
+
bg = {"high": "rgba(239,68,68,0.10)", "medium": "rgba(245,158,11,0.10)", "low": "rgba(34,197,94,0.10)"}.get(p, "transparent")
|
| 569 |
+
rows.append(f"""<div style="background:{bg}; border-left:3px solid {color};
|
| 570 |
+
padding:10px 14px; margin-bottom:6px; border-radius:4px;
|
| 571 |
+
display:flex; align-items:flex-start; gap:10px; animation:slideIn 0.4s ease-out;">
|
| 572 |
+
<div style="width:10px;height:10px;border-radius:50%;background:{color};flex-shrink:0;margin-top:4px;"></div>
|
| 573 |
+
<div style="font-family:monospace;font-size:11px;font-weight:bold;color:{color};min-width:50px;">
|
| 574 |
+
{t.get('priority','?').upper()}</div>
|
| 575 |
+
<div style="display:flex;flex-direction:column;gap:2px;">
|
| 576 |
+
<div style="font-size:13px;color:#d0d0d8;line-height:1.4;">{t.get('description','')}</div>
|
| 577 |
+
<div style="font-size:11px;color:#9090a8;">
|
| 578 |
+
{t.get('label','')} · {t.get('department','')} · conf: {t.get('confidence',0):.2f} · {t.get('source','')}
|
| 579 |
+
</div>
|
| 580 |
+
</div>
|
| 581 |
+
</div>""")
|
| 582 |
+
return "".join(rows)
|
ticketiq/ticketiq/stats.py
CHANGED
|
@@ -7,11 +7,10 @@ from .ui import (
|
|
| 7 |
HIGH, HIGH_BG, LOW_C, LOW_BG, MED, MED_BG, SANS, MONO, RADIUS,
|
| 8 |
)
|
| 9 |
|
| 10 |
-
#
|
| 11 |
PIE_COLORS = [ACCENT, "#60a5fa", LOW_C, "#a78bfa", HIGH, "#f472b6", "#34d399"]
|
| 12 |
|
| 13 |
-
|
| 14 |
-
# ── Stat card ─────────────────────────────────────────────────────────────────
|
| 15 |
|
| 16 |
def stat_card(label: str, value: rx.Var, sub: str = "") -> rx.Component:
|
| 17 |
return rx.box(
|
|
@@ -29,7 +28,7 @@ def stat_card(label: str, value: rx.Var, sub: str = "") -> rx.Component:
|
|
| 29 |
)
|
| 30 |
|
| 31 |
|
| 32 |
-
# ── Priority donut ──────────────────────────────────────────────
|
| 33 |
|
| 34 |
def priority_donut() -> rx.Component:
|
| 35 |
stats = TicketState.stats
|
|
@@ -86,7 +85,7 @@ def priority_donut() -> rx.Component:
|
|
| 86 |
)
|
| 87 |
|
| 88 |
|
| 89 |
-
# ── Source donut ────────────────────────────────────────────────
|
| 90 |
|
| 91 |
def source_donut() -> rx.Component:
|
| 92 |
stats = TicketState.stats
|
|
@@ -136,7 +135,7 @@ def source_donut() -> rx.Component:
|
|
| 136 |
)
|
| 137 |
|
| 138 |
|
| 139 |
-
# ── Top labels bar chart ────────────────────────────────
|
| 140 |
|
| 141 |
def top_labels_chart() -> rx.Component:
|
| 142 |
return rx.box(
|
|
@@ -144,18 +143,18 @@ def top_labels_chart() -> rx.Component:
|
|
| 144 |
rx.recharts.bar_chart(
|
| 145 |
rx.recharts.bar(
|
| 146 |
data_key="count",
|
| 147 |
-
fill="#f59e0b",
|
| 148 |
radius=[0, 4, 4, 0],
|
| 149 |
),
|
| 150 |
rx.recharts.x_axis(
|
| 151 |
type_="number",
|
| 152 |
-
tick={"fontSize": 10, "fill":
|
| 153 |
),
|
| 154 |
rx.recharts.y_axis(
|
| 155 |
data_key="label",
|
| 156 |
type_="category",
|
| 157 |
-
tick={"fontSize": 10, "fill":
|
| 158 |
-
width=200,
|
| 159 |
),
|
| 160 |
rx.recharts.cartesian_grid(
|
| 161 |
stroke_dasharray="3 3",
|
|
@@ -170,7 +169,7 @@ def top_labels_chart() -> rx.Component:
|
|
| 170 |
"fontSize": "12px",
|
| 171 |
"color": TEXT,
|
| 172 |
},
|
| 173 |
-
cursor={"fill": "rgba(245,158,11,0.06)"},
|
| 174 |
),
|
| 175 |
layout="vertical",
|
| 176 |
data=TicketState.stats_top_labels,
|
|
@@ -186,7 +185,7 @@ def top_labels_chart() -> rx.Component:
|
|
| 186 |
)
|
| 187 |
|
| 188 |
|
| 189 |
-
# ── Department bar chart ────────────────────────────────
|
| 190 |
|
| 191 |
def dept_chart() -> rx.Component:
|
| 192 |
return rx.box(
|
|
@@ -194,17 +193,17 @@ def dept_chart() -> rx.Component:
|
|
| 194 |
rx.recharts.bar_chart(
|
| 195 |
rx.recharts.bar(
|
| 196 |
data_key="count",
|
| 197 |
-
fill="#60a5fa",
|
| 198 |
radius=[0, 4, 4, 0],
|
| 199 |
),
|
| 200 |
rx.recharts.x_axis(
|
| 201 |
type_="number",
|
| 202 |
-
tick={"fontSize": 10, "fill":
|
| 203 |
),
|
| 204 |
rx.recharts.y_axis(
|
| 205 |
data_key="department",
|
| 206 |
type_="category",
|
| 207 |
-
tick={"fontSize": 10, "fill":
|
| 208 |
width=180,
|
| 209 |
),
|
| 210 |
rx.recharts.cartesian_grid(
|
|
@@ -220,9 +219,9 @@ def dept_chart() -> rx.Component:
|
|
| 220 |
"fontSize": "12px",
|
| 221 |
"color": TEXT,
|
| 222 |
},
|
| 223 |
-
cursor={"fill": "rgba(96,165,250,0.06)"},
|
| 224 |
),
|
| 225 |
-
layout="vertical",
|
| 226 |
data=TicketState.stats_department_breakdown,
|
| 227 |
width="100%",
|
| 228 |
height=400,
|
|
@@ -235,6 +234,7 @@ def dept_chart() -> rx.Component:
|
|
| 235 |
width="100%",
|
| 236 |
)
|
| 237 |
|
|
|
|
| 238 |
def uncategorised_card() -> rx.Component:
|
| 239 |
return rx.box(
|
| 240 |
rx.text("Uncategorised", font_size="12px", color=MUTED),
|
|
@@ -253,7 +253,8 @@ def uncategorised_card() -> rx.Component:
|
|
| 253 |
min_width="120px",
|
| 254 |
)
|
| 255 |
|
| 256 |
-
|
|
|
|
| 257 |
|
| 258 |
def stats_page() -> rx.Component:
|
| 259 |
s = TicketState.stats
|
|
@@ -347,5 +348,4 @@ def stats_page() -> rx.Component:
|
|
| 347 |
max_width="900px", margin="0 auto",
|
| 348 |
padding="40px 24px", width="100%",
|
| 349 |
on_mount=TicketState.load_stats,
|
| 350 |
-
)
|
| 351 |
-
|
|
|
|
| 7 |
HIGH, HIGH_BG, LOW_C, LOW_BG, MED, MED_BG, SANS, MONO, RADIUS,
|
| 8 |
)
|
| 9 |
|
| 10 |
+
# PIE_COLORS – using CSS variables works fine here because they're passed directly as fill values
|
| 11 |
PIE_COLORS = [ACCENT, "#60a5fa", LOW_C, "#a78bfa", HIGH, "#f472b6", "#34d399"]
|
| 12 |
|
| 13 |
+
# ── Stat card (unchanged) ────────────────────────────────────────────────────
|
|
|
|
| 14 |
|
| 15 |
def stat_card(label: str, value: rx.Var, sub: str = "") -> rx.Component:
|
| 16 |
return rx.box(
|
|
|
|
| 28 |
)
|
| 29 |
|
| 30 |
|
| 31 |
+
# ── Priority donut (unchanged) ──────────────────────────────────────────────
|
| 32 |
|
| 33 |
def priority_donut() -> rx.Component:
|
| 34 |
stats = TicketState.stats
|
|
|
|
| 85 |
)
|
| 86 |
|
| 87 |
|
| 88 |
+
# ── Source donut (unchanged) ────────────────────────────────────────────────
|
| 89 |
|
| 90 |
def source_donut() -> rx.Component:
|
| 91 |
stats = TicketState.stats
|
|
|
|
| 135 |
)
|
| 136 |
|
| 137 |
|
| 138 |
+
# ── Top labels bar chart – FIXED TICK COLORS ────────────────────────────────
|
| 139 |
|
| 140 |
def top_labels_chart() -> rx.Component:
|
| 141 |
return rx.box(
|
|
|
|
| 143 |
rx.recharts.bar_chart(
|
| 144 |
rx.recharts.bar(
|
| 145 |
data_key="count",
|
| 146 |
+
fill=rx.cond(rx.color_mode == "dark", "#f59e0b", "#d97706"),
|
| 147 |
radius=[0, 4, 4, 0],
|
| 148 |
),
|
| 149 |
rx.recharts.x_axis(
|
| 150 |
type_="number",
|
| 151 |
+
tick={"fontSize": 10, "fill": rx.cond(rx.color_mode == "dark", "#ccc", "#555"), "fontFamily": MONO},
|
| 152 |
),
|
| 153 |
rx.recharts.y_axis(
|
| 154 |
data_key="label",
|
| 155 |
type_="category",
|
| 156 |
+
tick={"fontSize": 10, "fill": rx.cond(rx.color_mode == "dark", "#ccc", "#555"), "fontFamily": MONO},
|
| 157 |
+
width=200,
|
| 158 |
),
|
| 159 |
rx.recharts.cartesian_grid(
|
| 160 |
stroke_dasharray="3 3",
|
|
|
|
| 169 |
"fontSize": "12px",
|
| 170 |
"color": TEXT,
|
| 171 |
},
|
| 172 |
+
cursor={"fill": rx.cond(rx.color_mode == "dark", "rgba(245,158,11,0.06)", "rgba(217,119,6,0.06)")},
|
| 173 |
),
|
| 174 |
layout="vertical",
|
| 175 |
data=TicketState.stats_top_labels,
|
|
|
|
| 185 |
)
|
| 186 |
|
| 187 |
|
| 188 |
+
# ── Department bar chart – FIXED TICK COLORS ────────────────────────────────
|
| 189 |
|
| 190 |
def dept_chart() -> rx.Component:
|
| 191 |
return rx.box(
|
|
|
|
| 193 |
rx.recharts.bar_chart(
|
| 194 |
rx.recharts.bar(
|
| 195 |
data_key="count",
|
| 196 |
+
fill=rx.cond(rx.color_mode == "dark", "#60a5fa", "#2563eb"),
|
| 197 |
radius=[0, 4, 4, 0],
|
| 198 |
),
|
| 199 |
rx.recharts.x_axis(
|
| 200 |
type_="number",
|
| 201 |
+
tick={"fontSize": 10, "fill": rx.cond(rx.color_mode == "dark", "#ccc", "#555"), "fontFamily": MONO},
|
| 202 |
),
|
| 203 |
rx.recharts.y_axis(
|
| 204 |
data_key="department",
|
| 205 |
type_="category",
|
| 206 |
+
tick={"fontSize": 10, "fill": rx.cond(rx.color_mode == "dark", "#ccc", "#555"), "fontFamily": MONO},
|
| 207 |
width=180,
|
| 208 |
),
|
| 209 |
rx.recharts.cartesian_grid(
|
|
|
|
| 219 |
"fontSize": "12px",
|
| 220 |
"color": TEXT,
|
| 221 |
},
|
| 222 |
+
cursor={"fill": rx.cond(rx.color_mode == "dark", "rgba(96,165,250,0.06)", "rgba(37,99,235,0.06)")},
|
| 223 |
),
|
| 224 |
+
layout="vertical",
|
| 225 |
data=TicketState.stats_department_breakdown,
|
| 226 |
width="100%",
|
| 227 |
height=400,
|
|
|
|
| 234 |
width="100%",
|
| 235 |
)
|
| 236 |
|
| 237 |
+
|
| 238 |
def uncategorised_card() -> rx.Component:
|
| 239 |
return rx.box(
|
| 240 |
rx.text("Uncategorised", font_size="12px", color=MUTED),
|
|
|
|
| 253 |
min_width="120px",
|
| 254 |
)
|
| 255 |
|
| 256 |
+
|
| 257 |
+
# ── Stats page (unchanged) ──────────────────────────────────────────────────
|
| 258 |
|
| 259 |
def stats_page() -> rx.Component:
|
| 260 |
s = TicketState.stats
|
|
|
|
| 348 |
max_width="900px", margin="0 auto",
|
| 349 |
padding="40px 24px", width="100%",
|
| 350 |
on_mount=TicketState.load_stats,
|
| 351 |
+
)
|
|
|
ticketiq/ticketiq/sth.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
test_tickets = {
|
| 2 |
+
"The `cert-manager` Order resource for `api.fintech.com` is stuck in 'pending' because the ACME HTTP01 challenge is returning a 404 from the ingress controller after a recent nginx config change.",
|
| 3 |
+
|
| 4 |
+
"Our `grafana` dashboard for payment latency shows a gap between 02:00 and 04:00 UTC because the `prometheus` retention policy was shortened to 30 days without updating the dashboard query range.",
|
| 5 |
+
|
| 6 |
+
"The `mlflow` experiment tracking server is returning '500 Internal Server Error' when logging parameters larger than 64KB because the `nginx` reverse proxy has `client_max_body_size` set to 64k.",
|
| 7 |
+
|
| 8 |
+
"A `terraform apply` on the staging environment is trying to destroy and recreate the `aws_elasticache_replication_group` because the `snapshot_window` parameter was removed from the module and the state file sees a drift.",
|
| 9 |
+
|
| 10 |
+
"The `sonarqube` scanner in the CI pipeline fails with 'Project not found' because the project key was renamed from `fintech-payment` to `fintech.payment` in the UI but the `sonar-project.properties` file was not updated.",
|
| 11 |
+
|
| 12 |
+
"Our `karpenter` provisioner is scaling down GPU nodes too aggressively during off-peak hours, causing model inference pods to be evicted mid-request and resulting in 503 errors for the risk scoring API.",
|
| 13 |
+
|
| 14 |
+
"The `datadog` agent on `us-east-1` EKS nodes is emitting duplicate `kubernetes_state.*` metrics because the `kube-state-metrics` service is being scraped by both the cluster agent and the node agent simultaneously.",
|
| 15 |
+
|
| 16 |
+
"The `vault` audit log is full of 'permission denied' errors for the `payment-service` role because the policy attached to the role was changed to require `sudo` for `transit/decrypt` operations after a security audit.",
|
| 17 |
+
|
| 18 |
+
"The `next.js` build in the CI pipeline fails with 'Error: EMFILE: too many open files' because the build process opens more than 1024 file handles simultaneously and the default `ulimit` on the runner is too low.",
|
| 19 |
+
|
| 20 |
+
"The `timescaledb` continuous aggregate for `hourly_trade_volume` stopped refreshing after the source hypertable was renamed from `trades` to `trade_events` and the materialization job is referencing the old name.",
|
| 21 |
+
}
|
ticketiq/ticketiq/ticketiq.py
CHANGED
|
@@ -10,11 +10,21 @@ from ticketiq.search import search_page
|
|
| 10 |
from ticketiq.stats import stats_page
|
| 11 |
from .pages.trends import trends_page
|
| 12 |
from .pages.playground import playground_page
|
|
|
|
| 13 |
from ticketiq.ui import (
|
| 14 |
BG, SURFACE, BORDER, BORDER_HI, ACCENT, ACCENT_BG,
|
| 15 |
MUTED, TEXT, SANS, MONO, RADIUS,
|
| 16 |
)
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
GOOGLE_FONTS = (
|
| 19 |
"https://fonts.googleapis.com/css2?"
|
| 20 |
"family=JetBrains+Mono:wght@400;500;700"
|
|
@@ -29,6 +39,7 @@ NAV = [
|
|
| 29 |
("stats", "bar-chart-2", "Stats"),
|
| 30 |
("trends", "trending-up", "Trends"),
|
| 31 |
("playground", "flask-conical", "Playground"),
|
|
|
|
| 32 |
|
| 33 |
]
|
| 34 |
|
|
@@ -68,7 +79,7 @@ def sidebar() -> rx.Component:
|
|
| 68 |
# logo
|
| 69 |
rx.hstack(
|
| 70 |
rx.box(
|
| 71 |
-
rx.icon("cpu", size=16, color=
|
| 72 |
width="30px", height="30px",
|
| 73 |
background=ACCENT, border_radius="6px",
|
| 74 |
display="flex", align_items="center", justify_content="center",
|
|
@@ -92,8 +103,20 @@ def sidebar() -> rx.Component:
|
|
| 92 |
padding="0 10px",
|
| 93 |
),
|
| 94 |
|
| 95 |
-
# footer
|
| 96 |
rx.box(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
rx.text(
|
| 98 |
"BGE-M3 · HDBSCAN · XGBoost · Groq",
|
| 99 |
font_size="10px", color=MUTED, font_family=MONO,
|
|
@@ -128,6 +151,7 @@ def page_body() -> rx.Component:
|
|
| 128 |
("stats", stats_page()),
|
| 129 |
("trends", trends_page()),
|
| 130 |
("playground", playground_page()),
|
|
|
|
| 131 |
submit_page(),
|
| 132 |
)
|
| 133 |
|
|
@@ -136,9 +160,60 @@ def page_body() -> rx.Component:
|
|
| 136 |
|
| 137 |
def index() -> rx.Component:
|
| 138 |
return rx.box(
|
| 139 |
-
#
|
| 140 |
rx.script(src=GOOGLE_FONTS),
|
| 141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
rx.hstack(
|
| 143 |
sidebar(),
|
| 144 |
rx.box(
|
|
@@ -152,7 +227,7 @@ def index() -> rx.Component:
|
|
| 152 |
width="100%",
|
| 153 |
),
|
| 154 |
|
| 155 |
-
#
|
| 156 |
font_family=SANS,
|
| 157 |
background=BG,
|
| 158 |
color=TEXT,
|
|
@@ -166,8 +241,6 @@ def index() -> rx.Component:
|
|
| 166 |
),
|
| 167 |
},
|
| 168 |
)
|
| 169 |
-
|
| 170 |
-
|
| 171 |
# ── App ───────────────────────────────────────────────────────────────────────
|
| 172 |
|
| 173 |
app = rx.App(
|
|
|
|
| 10 |
from ticketiq.stats import stats_page
|
| 11 |
from .pages.trends import trends_page
|
| 12 |
from .pages.playground import playground_page
|
| 13 |
+
from .pages.stream_page import stream_page
|
| 14 |
from ticketiq.ui import (
|
| 15 |
BG, SURFACE, BORDER, BORDER_HI, ACCENT, ACCENT_BG,
|
| 16 |
MUTED, TEXT, SANS, MONO, RADIUS,
|
| 17 |
)
|
| 18 |
|
| 19 |
+
|
| 20 |
+
app = rx.App(
|
| 21 |
+
style={"font_family": "Inter, sans-serif"},
|
| 22 |
+
theme=rx.theme(appearance="light", accent_color="blue"),
|
| 23 |
+
stylesheets=[
|
| 24 |
+
"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=DM+Sans:wght@300;400;500;600&display=swap",
|
| 25 |
+
],
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
GOOGLE_FONTS = (
|
| 29 |
"https://fonts.googleapis.com/css2?"
|
| 30 |
"family=JetBrains+Mono:wght@400;500;700"
|
|
|
|
| 39 |
("stats", "bar-chart-2", "Stats"),
|
| 40 |
("trends", "trending-up", "Trends"),
|
| 41 |
("playground", "flask-conical", "Playground"),
|
| 42 |
+
("stream", "radio", "Live"),
|
| 43 |
|
| 44 |
]
|
| 45 |
|
|
|
|
| 79 |
# logo
|
| 80 |
rx.hstack(
|
| 81 |
rx.box(
|
| 82 |
+
rx.icon("cpu", size=16, color=TEXT),
|
| 83 |
width="30px", height="30px",
|
| 84 |
background=ACCENT, border_radius="6px",
|
| 85 |
display="flex", align_items="center", justify_content="center",
|
|
|
|
| 103 |
padding="0 10px",
|
| 104 |
),
|
| 105 |
|
| 106 |
+
# footer (with toggle button)
|
| 107 |
rx.box(
|
| 108 |
+
rx.icon_button(
|
| 109 |
+
rx.cond(rx.color_mode == "dark", rx.icon("sun", size=16), rx.icon("moon", size=16)),
|
| 110 |
+
on_click=rx.toggle_color_mode,
|
| 111 |
+
background="transparent",
|
| 112 |
+
border=f"1px solid {BORDER}",
|
| 113 |
+
border_radius="6px",
|
| 114 |
+
color=MUTED,
|
| 115 |
+
cursor="pointer",
|
| 116 |
+
size="2",
|
| 117 |
+
variant="ghost",
|
| 118 |
+
margin_bottom="10px",
|
| 119 |
+
),
|
| 120 |
rx.text(
|
| 121 |
"BGE-M3 · HDBSCAN · XGBoost · Groq",
|
| 122 |
font_size="10px", color=MUTED, font_family=MONO,
|
|
|
|
| 151 |
("stats", stats_page()),
|
| 152 |
("trends", trends_page()),
|
| 153 |
("playground", playground_page()),
|
| 154 |
+
("stream", stream_page()),
|
| 155 |
submit_page(),
|
| 156 |
)
|
| 157 |
|
|
|
|
| 160 |
|
| 161 |
def index() -> rx.Component:
|
| 162 |
return rx.box(
|
| 163 |
+
# Inject Google Fonts
|
| 164 |
rx.script(src=GOOGLE_FONTS),
|
| 165 |
|
| 166 |
+
# Embedded theme styles (dark / light + chart colours)
|
| 167 |
+
rx.el.style("""
|
| 168 |
+
html[data-theme='dark'] {
|
| 169 |
+
--bg: #1a1a20;
|
| 170 |
+
--surface: #242430;
|
| 171 |
+
--border: #343440;
|
| 172 |
+
--border-hi: #4a4a5a;
|
| 173 |
+
--text: #e0e0e8;
|
| 174 |
+
--muted: #7a7a90;
|
| 175 |
+
--accent: #818cf8;
|
| 176 |
+
--accent-bg: rgba(129,140,248,0.12);
|
| 177 |
+
--high: #f87171;
|
| 178 |
+
--high-bg: rgba(248,113,113,0.12);
|
| 179 |
+
--med: #fbbf24;
|
| 180 |
+
--med-bg: rgba(251,191,36,0.12);
|
| 181 |
+
--low: #34d399;
|
| 182 |
+
--low-bg: rgba(52,211,153,0.12);
|
| 183 |
+
--chart-blue: #60a5fa;
|
| 184 |
+
--chart-orange: #f59e0b;
|
| 185 |
+
--chart-purple: #a78bfa;
|
| 186 |
+
--chart-pink: #f472b6;
|
| 187 |
+
--chart-green: #34d399;
|
| 188 |
+
--chart-blue-bg: rgba(96,165,250,0.06);
|
| 189 |
+
--chart-orange-bg: rgba(245,158,11,0.06);
|
| 190 |
+
}
|
| 191 |
+
html[data-theme='light'] {
|
| 192 |
+
--bg: #f8f9fa;
|
| 193 |
+
--surface: #ffffff;
|
| 194 |
+
--border: #dee2e6;
|
| 195 |
+
--border-hi: #adb5bd;
|
| 196 |
+
--text: #212529;
|
| 197 |
+
--muted: #6c757d;
|
| 198 |
+
--accent: #0d6efd;
|
| 199 |
+
--accent-bg: rgba(13,110,253,0.10);
|
| 200 |
+
--high: #dc3545;
|
| 201 |
+
--high-bg: rgba(220,53,69,0.08);
|
| 202 |
+
--med: #fd7e14;
|
| 203 |
+
--med-bg: rgba(253,126,20,0.08);
|
| 204 |
+
--low: #198754;
|
| 205 |
+
--low-bg: rgba(25,135,84,0.08);
|
| 206 |
+
--chart-blue: #60a5fa;
|
| 207 |
+
--chart-orange: #f59e0b;
|
| 208 |
+
--chart-purple: #a78bfa;
|
| 209 |
+
--chart-pink: #f472b6;
|
| 210 |
+
--chart-green: #34d399;
|
| 211 |
+
--chart-blue-bg: rgba(96,165,250,0.06);
|
| 212 |
+
--chart-orange-bg: rgba(245,158,11,0.06);
|
| 213 |
+
}
|
| 214 |
+
"""),
|
| 215 |
+
|
| 216 |
+
# Sidebar + main content
|
| 217 |
rx.hstack(
|
| 218 |
sidebar(),
|
| 219 |
rx.box(
|
|
|
|
| 227 |
width="100%",
|
| 228 |
),
|
| 229 |
|
| 230 |
+
# Global styles
|
| 231 |
font_family=SANS,
|
| 232 |
background=BG,
|
| 233 |
color=TEXT,
|
|
|
|
| 241 |
),
|
| 242 |
},
|
| 243 |
)
|
|
|
|
|
|
|
| 244 |
# ── App ───────────────────────────────────────────────────────────────────────
|
| 245 |
|
| 246 |
app = rx.App(
|
ticketiq/ticketiq/ui.py
CHANGED
|
@@ -2,21 +2,31 @@
|
|
| 2 |
import reflex as rx
|
| 3 |
|
| 4 |
# ── palette ───────────────────────────────────────────────────────────────────
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
MONO = "'JetBrains Mono', monospace"
|
| 22 |
SANS = "'DM Sans', sans-serif"
|
|
|
|
| 2 |
import reflex as rx
|
| 3 |
|
| 4 |
# ── palette ───────────────────────────────────────────────────────────────────
|
| 5 |
+
import reflex as rx
|
| 6 |
+
|
| 7 |
+
# ── palette ───────────────────────────────────────────────────────────────────
|
| 8 |
+
BG = "var(--bg)"
|
| 9 |
+
SURFACE = "var(--surface)"
|
| 10 |
+
BORDER = "var(--border)"
|
| 11 |
+
BORDER_HI = "var(--border-hi)"
|
| 12 |
+
TEXT = "var(--text)"
|
| 13 |
+
MUTED = "var(--muted)"
|
| 14 |
+
ACCENT = "var(--accent)"
|
| 15 |
+
ACCENT_BG = "var(--accent-bg)"
|
| 16 |
+
HIGH = "var(--high)"
|
| 17 |
+
HIGH_BG = "var(--high-bg)"
|
| 18 |
+
MED = "var(--med)"
|
| 19 |
+
MED_BG = "var(--med-bg)"
|
| 20 |
+
LOW_C = "var(--low)"
|
| 21 |
+
LOW_BG = "var(--low-bg)"
|
| 22 |
+
|
| 23 |
+
CHART_BLUE = "var(--chart-blue)"
|
| 24 |
+
CHART_ORANGE = "var(--chart-orange)"
|
| 25 |
+
CHART_PURPLE = "var(--chart-purple)"
|
| 26 |
+
CHART_PINK = "var(--chart-pink)"
|
| 27 |
+
CHART_GREEN = "var(--chart-green)"
|
| 28 |
+
CHART_BLUE_BG = "var(--chart-blue-bg)"
|
| 29 |
+
CHART_ORANGE_BG = "var(--chart-orange-bg)"
|
| 30 |
|
| 31 |
MONO = "'JetBrains Mono', monospace"
|
| 32 |
SANS = "'DM Sans', sans-serif"
|