pylogue 0.2.1__py3-none-any.whl → 0.3.29__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.
- pylogue/core.py +804 -0
- pylogue/embeds.py +32 -0
- pylogue/integrations/__init__.py +1 -0
- pylogue/integrations/pydantic_ai.py +417 -0
- pylogue/{_modidx.py → legacy/_modidx.py} +1 -0
- pylogue/legacy/cards.py +112 -0
- pylogue/{chat.py → legacy/chat.py} +54 -27
- pylogue/{chatapp.py → legacy/chatapp.py} +70 -26
- pylogue/legacy/design_system.py +117 -0
- pylogue/legacy/renderer.py +284 -0
- pylogue/shell.py +342 -0
- pylogue/static/pylogue-core.css +372 -0
- pylogue/static/pylogue-core.js +199 -0
- pylogue/static/pylogue-markdown.js +745 -0
- {pylogue-0.2.1.dist-info → pylogue-0.3.29.dist-info}/METADATA +10 -1
- pylogue-0.3.29.dist-info/RECORD +26 -0
- {pylogue-0.2.1.dist-info → pylogue-0.3.29.dist-info}/WHEEL +1 -1
- pylogue/cards.py +0 -157
- pylogue/renderer.py +0 -128
- pylogue-0.2.1.dist-info/RECORD +0 -17
- /pylogue/{__init__.py → legacy/__init__.py} +0 -0
- /pylogue/{__pre_init__.py → legacy/__pre_init__.py} +0 -0
- /pylogue/{health.py → legacy/health.py} +0 -0
- /pylogue/{service.py → legacy/service.py} +0 -0
- /pylogue/{session.py → legacy/session.py} +0 -0
- {pylogue-0.2.1.dist-info → pylogue-0.3.29.dist-info}/entry_points.txt +0 -0
- {pylogue-0.2.1.dist-info → pylogue-0.3.29.dist-info}/licenses/AUTHORS.md +0 -0
- {pylogue-0.2.1.dist-info → pylogue-0.3.29.dist-info}/licenses/LICENSE +0 -0
- {pylogue-0.2.1.dist-info → pylogue-0.3.29.dist-info}/top_level.txt +0 -0
pylogue/shell.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chat app wrapper with multiple local histories.
|
|
3
|
+
Run: python -m scripts.examples.chat_app_with_histories.main
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from uuid import uuid4
|
|
12
|
+
|
|
13
|
+
from fasthtml.common import *
|
|
14
|
+
from fastsql import Database
|
|
15
|
+
from monsterui.all import (
|
|
16
|
+
Button,
|
|
17
|
+
ButtonT,
|
|
18
|
+
Container,
|
|
19
|
+
ContainerT,
|
|
20
|
+
FastHTML as MUFastHTML,
|
|
21
|
+
TextPresets,
|
|
22
|
+
Theme,
|
|
23
|
+
UkIcon,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from pylogue.core import (
|
|
27
|
+
EchoResponder,
|
|
28
|
+
IMPORT_PREFIX,
|
|
29
|
+
_register_google_auth_routes,
|
|
30
|
+
_session_cookie_name,
|
|
31
|
+
google_oauth_config_from_env,
|
|
32
|
+
get_core_headers,
|
|
33
|
+
register_core_static,
|
|
34
|
+
register_ws_routes,
|
|
35
|
+
render_cards,
|
|
36
|
+
render_input,
|
|
37
|
+
)
|
|
38
|
+
from starlette.requests import Request
|
|
39
|
+
from starlette.responses import FileResponse
|
|
40
|
+
from starlette.responses import JSONResponse
|
|
41
|
+
from starlette.responses import RedirectResponse
|
|
42
|
+
|
|
43
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
44
|
+
CHAT_APP_DIR = PROJECT_ROOT / "scripts" / "examples" / "chat_app_with_histories"
|
|
45
|
+
STATIC_DIR = CHAT_APP_DIR / "static"
|
|
46
|
+
DB_PATH = CHAT_APP_DIR / "chat_app.db"
|
|
47
|
+
db = Database(f"sqlite:///{DB_PATH}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class Chat:
|
|
52
|
+
id: str
|
|
53
|
+
title: str
|
|
54
|
+
created_at: str
|
|
55
|
+
updated_at: str
|
|
56
|
+
payload: str = ""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
chats = db.create(Chat, pk="id")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _utc_iso() -> str:
|
|
63
|
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def app_factory(
|
|
67
|
+
responder=None,
|
|
68
|
+
responder_factory=None,
|
|
69
|
+
db_path: Path | str | None = None,
|
|
70
|
+
sidebar_title: str = "Pylogue",
|
|
71
|
+
hero_title: str = "Fast HTML + Pylogue Core",
|
|
72
|
+
hero_subtitle: str = (
|
|
73
|
+
"One UI wraps multiple Pylogue chat sessions. Pick a chat on the left, "
|
|
74
|
+
"start a new one, or return to previous conversations instantly."
|
|
75
|
+
),
|
|
76
|
+
) -> MUFastHTML:
|
|
77
|
+
resolved_db_path = Path(db_path) if db_path is not None else DB_PATH
|
|
78
|
+
local_db = Database(f"sqlite:///{resolved_db_path}")
|
|
79
|
+
if responder_factory is None:
|
|
80
|
+
responder = responder or EchoResponder()
|
|
81
|
+
headers = list(get_core_headers(include_markdown=True))
|
|
82
|
+
headers.extend(
|
|
83
|
+
[
|
|
84
|
+
Link(
|
|
85
|
+
rel="stylesheet",
|
|
86
|
+
href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700&display=swap",
|
|
87
|
+
),
|
|
88
|
+
Link(rel="stylesheet", href="/static/chat_app.css"),
|
|
89
|
+
Script(src="/static/chat_app.js", type="module"),
|
|
90
|
+
]
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
oauth_cfg = google_oauth_config_from_env()
|
|
94
|
+
auth_required = bool(oauth_cfg and oauth_cfg.auth_required)
|
|
95
|
+
session_secret = (
|
|
96
|
+
oauth_cfg.session_secret
|
|
97
|
+
if oauth_cfg and oauth_cfg.session_secret
|
|
98
|
+
else os.getenv("PYLOGUE_SESSION_SECRET")
|
|
99
|
+
)
|
|
100
|
+
app_kwargs = {"exts": "ws", "hdrs": tuple(headers), "pico": False}
|
|
101
|
+
app_kwargs["session_cookie"] = _session_cookie_name()
|
|
102
|
+
if session_secret:
|
|
103
|
+
app_kwargs["secret_key"] = session_secret
|
|
104
|
+
app = MUFastHTML(**app_kwargs)
|
|
105
|
+
register_core_static(app)
|
|
106
|
+
auth_paths = _register_google_auth_routes(app, oauth_cfg) if oauth_cfg else None
|
|
107
|
+
|
|
108
|
+
def _is_authorized(request: Request) -> bool:
|
|
109
|
+
if not auth_required:
|
|
110
|
+
return True
|
|
111
|
+
auth = request.session.get("auth")
|
|
112
|
+
return isinstance(auth, dict)
|
|
113
|
+
|
|
114
|
+
@app.route("/static/chat_app.css")
|
|
115
|
+
def _chat_app_css():
|
|
116
|
+
return FileResponse(STATIC_DIR / "chat_app.css")
|
|
117
|
+
|
|
118
|
+
@app.route("/static/chat_app.js")
|
|
119
|
+
def _chat_app_js():
|
|
120
|
+
return FileResponse(STATIC_DIR / "chat_app.js")
|
|
121
|
+
|
|
122
|
+
@app.route("/api/chats", methods=["GET"])
|
|
123
|
+
def list_chats(request: Request):
|
|
124
|
+
if not _is_authorized(request):
|
|
125
|
+
return JSONResponse({"error": "Unauthorized"}, status_code=401)
|
|
126
|
+
items = list(local_db.create(Chat, pk="id")())
|
|
127
|
+
items.sort(key=lambda c: c.updated_at or c.created_at, reverse=True)
|
|
128
|
+
return JSONResponse(
|
|
129
|
+
[
|
|
130
|
+
{
|
|
131
|
+
"id": c.id,
|
|
132
|
+
"title": c.title,
|
|
133
|
+
"created_at": c.created_at,
|
|
134
|
+
"updated_at": c.updated_at,
|
|
135
|
+
}
|
|
136
|
+
for c in items
|
|
137
|
+
]
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
@app.route("/api/chats", methods=["POST"])
|
|
141
|
+
async def create_chat(request: Request):
|
|
142
|
+
if not _is_authorized(request):
|
|
143
|
+
return JSONResponse({"error": "Unauthorized"}, status_code=401)
|
|
144
|
+
chats = local_db.create(Chat, pk="id")
|
|
145
|
+
data = await request.json()
|
|
146
|
+
chat_id = data.get("id") or str(uuid4())
|
|
147
|
+
title = data.get("title") or "New chat"
|
|
148
|
+
now = _utc_iso()
|
|
149
|
+
payload = data.get("payload")
|
|
150
|
+
payload_str = json.dumps(payload) if payload is not None else ""
|
|
151
|
+
chat = Chat(chat_id, title, now, now, payload_str)
|
|
152
|
+
try:
|
|
153
|
+
_ = chats[chat_id]
|
|
154
|
+
chats.update(chat)
|
|
155
|
+
except Exception:
|
|
156
|
+
chats.insert(chat)
|
|
157
|
+
return JSONResponse(
|
|
158
|
+
{
|
|
159
|
+
"id": chat.id,
|
|
160
|
+
"title": chat.title,
|
|
161
|
+
"created_at": chat.created_at,
|
|
162
|
+
"updated_at": chat.updated_at,
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
@app.route("/api/chats/{chat_id}", methods=["GET"])
|
|
167
|
+
def get_chat(request: Request, chat_id: str):
|
|
168
|
+
if not _is_authorized(request):
|
|
169
|
+
return JSONResponse({"error": "Unauthorized"}, status_code=401)
|
|
170
|
+
chats = local_db.create(Chat, pk="id")
|
|
171
|
+
try:
|
|
172
|
+
chat = chats[chat_id]
|
|
173
|
+
except Exception:
|
|
174
|
+
return JSONResponse({"cards": []})
|
|
175
|
+
payload = chat.payload or ""
|
|
176
|
+
if not payload:
|
|
177
|
+
return JSONResponse({"cards": []})
|
|
178
|
+
try:
|
|
179
|
+
data = json.loads(payload)
|
|
180
|
+
except json.JSONDecodeError:
|
|
181
|
+
data = {"cards": []}
|
|
182
|
+
return JSONResponse(data)
|
|
183
|
+
|
|
184
|
+
@app.route("/api/chats/{chat_id}", methods=["POST"])
|
|
185
|
+
async def save_chat(chat_id: str, request: Request):
|
|
186
|
+
if not _is_authorized(request):
|
|
187
|
+
return JSONResponse({"error": "Unauthorized"}, status_code=401)
|
|
188
|
+
chats = local_db.create(Chat, pk="id")
|
|
189
|
+
data = await request.json()
|
|
190
|
+
payload = data.get("payload") or {"cards": []}
|
|
191
|
+
title = data.get("title") or "New chat"
|
|
192
|
+
now = _utc_iso()
|
|
193
|
+
try:
|
|
194
|
+
existing = chats[chat_id]
|
|
195
|
+
created_at = existing.created_at
|
|
196
|
+
except Exception:
|
|
197
|
+
created_at = data.get("created_at") or now
|
|
198
|
+
chat = Chat(chat_id, title, created_at, now, json.dumps(payload))
|
|
199
|
+
try:
|
|
200
|
+
_ = chats[chat_id]
|
|
201
|
+
chats.update(chat)
|
|
202
|
+
except Exception:
|
|
203
|
+
chats.insert(chat)
|
|
204
|
+
return JSONResponse(
|
|
205
|
+
{
|
|
206
|
+
"id": chat.id,
|
|
207
|
+
"title": chat.title,
|
|
208
|
+
"created_at": chat.created_at,
|
|
209
|
+
"updated_at": chat.updated_at,
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
@app.route("/api/chats/{chat_id}", methods=["DELETE"])
|
|
214
|
+
def delete_chat(request: Request, chat_id: str):
|
|
215
|
+
if not _is_authorized(request):
|
|
216
|
+
return JSONResponse({"error": "Unauthorized"}, status_code=401)
|
|
217
|
+
chats = local_db.create(Chat, pk="id")
|
|
218
|
+
try:
|
|
219
|
+
chats.delete(chat_id)
|
|
220
|
+
except Exception:
|
|
221
|
+
pass
|
|
222
|
+
return JSONResponse({"deleted": True})
|
|
223
|
+
|
|
224
|
+
sessions: dict[int, dict] = {}
|
|
225
|
+
register_ws_routes(
|
|
226
|
+
app,
|
|
227
|
+
responder=responder,
|
|
228
|
+
responder_factory=responder_factory,
|
|
229
|
+
sessions=sessions,
|
|
230
|
+
auth_required=auth_required,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def _sidebar(request: Request):
|
|
234
|
+
show_logout = auth_required and _is_authorized(request)
|
|
235
|
+
logout_href = auth_paths["logout_path"] if auth_paths else "/logout"
|
|
236
|
+
return Div(
|
|
237
|
+
Div(
|
|
238
|
+
H1(sidebar_title, cls="text-xl font-semibold"),
|
|
239
|
+
A(
|
|
240
|
+
UkIcon("sign-out"),
|
|
241
|
+
Span("Logout"),
|
|
242
|
+
href=logout_href,
|
|
243
|
+
cls=(ButtonT.secondary, "text-xs"),
|
|
244
|
+
) if show_logout else None,
|
|
245
|
+
cls="sidebar-header",
|
|
246
|
+
),
|
|
247
|
+
Button(
|
|
248
|
+
UkIcon("plus"),
|
|
249
|
+
Span("New chat"),
|
|
250
|
+
cls=(ButtonT.secondary, "w-full justify-center"),
|
|
251
|
+
type="button",
|
|
252
|
+
id="new-chat-btn",
|
|
253
|
+
),
|
|
254
|
+
Div(id="chat-list", cls="chat-list"),
|
|
255
|
+
cls="sidebar",
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def _hero():
|
|
259
|
+
return Div(
|
|
260
|
+
Div(
|
|
261
|
+
H2(hero_title, cls="hero-title"),
|
|
262
|
+
P(hero_subtitle, cls="hero-sub"),
|
|
263
|
+
cls="space-y-2",
|
|
264
|
+
),
|
|
265
|
+
Div(
|
|
266
|
+
Button(
|
|
267
|
+
UkIcon("download"),
|
|
268
|
+
cls="uk-button uk-button-text copy-chat-btn",
|
|
269
|
+
type="button",
|
|
270
|
+
aria_label="Download conversation JSON",
|
|
271
|
+
title="Download conversation JSON",
|
|
272
|
+
),
|
|
273
|
+
Button(
|
|
274
|
+
UkIcon("upload"),
|
|
275
|
+
cls="uk-button uk-button-text copy-chat-btn upload-chat-btn",
|
|
276
|
+
type="button",
|
|
277
|
+
aria_label="Upload conversation JSON",
|
|
278
|
+
title="Upload conversation JSON",
|
|
279
|
+
),
|
|
280
|
+
Input(
|
|
281
|
+
type="file",
|
|
282
|
+
id="chat-upload",
|
|
283
|
+
accept="application/json",
|
|
284
|
+
cls="sr-only",
|
|
285
|
+
),
|
|
286
|
+
cls="flex gap-2 justify-end",
|
|
287
|
+
),
|
|
288
|
+
cls="hero",
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def _chat_panel():
|
|
292
|
+
return Div(
|
|
293
|
+
Div(render_cards([])),
|
|
294
|
+
Form(
|
|
295
|
+
render_input(),
|
|
296
|
+
Div(
|
|
297
|
+
Button("Send", cls=ButtonT.primary, type="submit", id="chat-send-btn"),
|
|
298
|
+
P("Cmd/Ctrl+Enter to send", cls="text-xs text-slate-400"),
|
|
299
|
+
cls="flex flex-col gap-2 items-stretch",
|
|
300
|
+
),
|
|
301
|
+
id="form",
|
|
302
|
+
hx_ext="ws",
|
|
303
|
+
ws_connect="/ws",
|
|
304
|
+
ws_send=True,
|
|
305
|
+
hx_target="#cards",
|
|
306
|
+
hx_swap="outerHTML",
|
|
307
|
+
cls="flex flex-col sm:flex-row gap-3 items-stretch pt-4",
|
|
308
|
+
),
|
|
309
|
+
cls="chat-panel space-y-4",
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def _main_panel():
|
|
313
|
+
return Div(
|
|
314
|
+
_hero(),
|
|
315
|
+
_chat_panel(),
|
|
316
|
+
cls="main-panel space-y-6",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def _shell(request: Request):
|
|
320
|
+
return Div(
|
|
321
|
+
_sidebar(request),
|
|
322
|
+
_main_panel(),
|
|
323
|
+
cls="app-shell",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
@app.route("/")
|
|
327
|
+
def home(request: Request):
|
|
328
|
+
if auth_required and not _is_authorized(request):
|
|
329
|
+
request.session["next"] = "/"
|
|
330
|
+
login_path = auth_paths["login_path"] if auth_paths else "/login"
|
|
331
|
+
return RedirectResponse(login_path, status_code=303)
|
|
332
|
+
return (
|
|
333
|
+
Title(hero_title),
|
|
334
|
+
Meta(name="viewport", content="width=device-width, initial-scale=1.0"),
|
|
335
|
+
Body(
|
|
336
|
+
_shell(request),
|
|
337
|
+
cls="min-h-screen",
|
|
338
|
+
data_import_prefix=IMPORT_PREFIX,
|
|
339
|
+
),
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return app
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
html, body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
background: #f8fafc;
|
|
4
|
+
color: #0f172a;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
html {
|
|
8
|
+
color-scheme: light;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
:root, .uk-theme-slate {
|
|
12
|
+
--background: 0 0% 98%;
|
|
13
|
+
--foreground: 222 47% 11%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.chat-panel {
|
|
17
|
+
background: #ffffff;
|
|
18
|
+
border: 1px solid #e2e8f0;
|
|
19
|
+
border-radius: 16px;
|
|
20
|
+
padding: 20px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.chat-row-user {
|
|
24
|
+
background: #e2e8f0;
|
|
25
|
+
border-left: 3px solid #cbd5f5;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.chat-row-assistant {
|
|
29
|
+
background: #f8fafc;
|
|
30
|
+
border-left: 3px solid #e2e8f0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.chat-row-block {
|
|
34
|
+
margin: 0;
|
|
35
|
+
padding: 14px 12px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.tool-status {
|
|
39
|
+
display: inline-block;
|
|
40
|
+
padding: 0.18rem 0.45rem;
|
|
41
|
+
border-radius: 999px;
|
|
42
|
+
font-size: 0.78rem;
|
|
43
|
+
font-weight: 600;
|
|
44
|
+
letter-spacing: 0.005em;
|
|
45
|
+
background: rgba(148, 163, 184, 0.15);
|
|
46
|
+
color: #0f172a;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.tool-status--running {
|
|
50
|
+
position: relative;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
color: #0f172a;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.tool-status--running::after {
|
|
56
|
+
content: "";
|
|
57
|
+
position: absolute;
|
|
58
|
+
top: 0;
|
|
59
|
+
left: -150%;
|
|
60
|
+
width: 150%;
|
|
61
|
+
height: 100%;
|
|
62
|
+
background: linear-gradient(
|
|
63
|
+
120deg,
|
|
64
|
+
transparent 0%,
|
|
65
|
+
rgba(34, 197, 94, 0.15),
|
|
66
|
+
transparent 80%
|
|
67
|
+
);
|
|
68
|
+
animation: tool-shimmer 1.4s linear infinite;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.tool-status--done {
|
|
72
|
+
background: rgba(255, 255, 255, 0.7) 40%;
|
|
73
|
+
color: #14532d;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@keyframes tool-shimmer {
|
|
77
|
+
0% { transform: translateX(0); }
|
|
78
|
+
100% { transform: translateX(100%); }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.marked a {
|
|
82
|
+
color: #2563eb;
|
|
83
|
+
text-decoration: underline;
|
|
84
|
+
text-underline-offset: 3px;
|
|
85
|
+
text-decoration-thickness: 1.5px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.marked a:hover {
|
|
89
|
+
color: #1d4ed8;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.marked table {
|
|
93
|
+
width: 100%;
|
|
94
|
+
border-collapse: collapse;
|
|
95
|
+
margin: 16px 0;
|
|
96
|
+
font-size: 0.95rem;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.marked thead th {
|
|
100
|
+
background: #f1f5f9;
|
|
101
|
+
color: #0f172a;
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
border: 1px solid #e2e8f0;
|
|
104
|
+
padding: 10px 12px;
|
|
105
|
+
text-align: left;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.marked tbody td {
|
|
109
|
+
border: 1px solid #e2e8f0;
|
|
110
|
+
padding: 10px 12px;
|
|
111
|
+
vertical-align: top;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.marked tbody tr:nth-child(odd) {
|
|
115
|
+
background: #f8fafc;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.marked tbody tr:hover {
|
|
119
|
+
background: #eef2ff;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.marked blockquote {
|
|
123
|
+
margin: 16px 0;
|
|
124
|
+
padding: 12px 14px;
|
|
125
|
+
border-left: 3px solid #e2e8f0;
|
|
126
|
+
background: #f8fafc;
|
|
127
|
+
color: #475569;
|
|
128
|
+
border-radius: 8px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.marked pre {
|
|
132
|
+
background: #f8fafc;
|
|
133
|
+
color: #0f172a;
|
|
134
|
+
padding: 12px 14px;
|
|
135
|
+
border-radius: 12px;
|
|
136
|
+
border: 1px solid #e2e8f0;
|
|
137
|
+
overflow: auto;
|
|
138
|
+
text-align: left;
|
|
139
|
+
position: relative;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.marked {
|
|
143
|
+
font-family: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.marked :not(pre) > code {
|
|
147
|
+
background: #f1f5f9;
|
|
148
|
+
color: #0f172a;
|
|
149
|
+
padding: 0.15rem 0.35rem;
|
|
150
|
+
border-radius: 6px;
|
|
151
|
+
font-size: 0.95em;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.marked pre code {
|
|
155
|
+
background: transparent;
|
|
156
|
+
padding: 0;
|
|
157
|
+
border-radius: 0;
|
|
158
|
+
text-align: left;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.marked pre .code-copy-btn {
|
|
162
|
+
position: absolute;
|
|
163
|
+
top: 8px;
|
|
164
|
+
right: 8px;
|
|
165
|
+
display: inline-flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
width: 28px;
|
|
169
|
+
height: 28px;
|
|
170
|
+
border: 1px solid #e2e8f0;
|
|
171
|
+
border-radius: 8px;
|
|
172
|
+
background: rgba(248, 250, 252, 0.9);
|
|
173
|
+
color: #64748b;
|
|
174
|
+
opacity: 0;
|
|
175
|
+
transform: translateY(-2px);
|
|
176
|
+
transition: opacity 0.15s ease, transform 0.15s ease, color 0.15s ease, border-color 0.15s ease;
|
|
177
|
+
cursor: pointer;
|
|
178
|
+
pointer-events: none;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.marked pre:hover .code-copy-btn,
|
|
182
|
+
.marked pre:focus-within .code-copy-btn {
|
|
183
|
+
opacity: 1;
|
|
184
|
+
transform: translateY(0);
|
|
185
|
+
pointer-events: auto;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.marked pre .code-copy-btn svg {
|
|
189
|
+
width: 16px;
|
|
190
|
+
height: 16px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.marked pre .code-copy-btn:hover {
|
|
194
|
+
color: #0f172a;
|
|
195
|
+
border-color: #cbd5f5;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.marked pre .code-copy-btn[data-copied="true"] {
|
|
199
|
+
color: #16a34a;
|
|
200
|
+
border-color: #86efac;
|
|
201
|
+
background: #f0fdf4;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.marked .mermaid-container {
|
|
205
|
+
position: relative;
|
|
206
|
+
border: 1px solid #e2e8f0;
|
|
207
|
+
border-radius: 12px;
|
|
208
|
+
background: #ffffff;
|
|
209
|
+
padding: 8px;
|
|
210
|
+
margin: 16px 0;
|
|
211
|
+
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.marked .mermaid-wrapper {
|
|
215
|
+
min-height: 0;
|
|
216
|
+
height: fit-content;
|
|
217
|
+
overflow: hidden;
|
|
218
|
+
position: relative;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.marked .mermaid-wrapper svg {
|
|
222
|
+
display: block;
|
|
223
|
+
width: 100%;
|
|
224
|
+
height: auto;
|
|
225
|
+
pointer-events: none;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.marked .mermaid-controls {
|
|
229
|
+
position: absolute;
|
|
230
|
+
top: 8px;
|
|
231
|
+
right: 8px;
|
|
232
|
+
display: inline-flex;
|
|
233
|
+
gap: 6px;
|
|
234
|
+
background: rgba(255, 255, 255, 0.95);
|
|
235
|
+
border: 1px solid #e2e8f0;
|
|
236
|
+
border-radius: 999px;
|
|
237
|
+
padding: 4px 6px;
|
|
238
|
+
z-index: 10;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.marked .mermaid-controls button {
|
|
242
|
+
border: 0;
|
|
243
|
+
background: transparent;
|
|
244
|
+
color: #475569;
|
|
245
|
+
font-size: 12px;
|
|
246
|
+
line-height: 1;
|
|
247
|
+
padding: 4px 6px;
|
|
248
|
+
border-radius: 999px;
|
|
249
|
+
cursor: pointer;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.marked .mermaid-controls button:hover {
|
|
253
|
+
background: #f1f5f9;
|
|
254
|
+
color: #0f172a;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.marked .mermaid-error {
|
|
258
|
+
border: 1px dashed #cbd5e1;
|
|
259
|
+
border-radius: 10px;
|
|
260
|
+
background: #f8fafc;
|
|
261
|
+
color: #475569;
|
|
262
|
+
font-size: 0.9rem;
|
|
263
|
+
padding: 10px 12px;
|
|
264
|
+
margin: 8px 0;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.marked ul,
|
|
268
|
+
.marked ol {
|
|
269
|
+
margin: 12px 0;
|
|
270
|
+
padding-left: 1.25rem;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.marked hr {
|
|
274
|
+
border: 0;
|
|
275
|
+
border-top: 1px solid #e2e8f0;
|
|
276
|
+
margin: 20px 0;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
#cards,
|
|
280
|
+
.chat-panel {
|
|
281
|
+
overflow-anchor: none;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.marked details.tool-call {
|
|
285
|
+
background: #f1f5f9;
|
|
286
|
+
border: 1px solid #e2e8f0;
|
|
287
|
+
border-radius: 12px;
|
|
288
|
+
padding: 6px 8px;
|
|
289
|
+
color: #475569;
|
|
290
|
+
margin: 14px 0;
|
|
291
|
+
font-size: 0.85rem;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.marked details.tool-call summary {
|
|
295
|
+
cursor: pointer;
|
|
296
|
+
color: #64748b;
|
|
297
|
+
font-weight: 600;
|
|
298
|
+
font-size: 0.85rem;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.marked details.tool-call pre {
|
|
302
|
+
background: #e2e8f0;
|
|
303
|
+
color: #334155;
|
|
304
|
+
border-color: #cbd5f5;
|
|
305
|
+
padding: 8px 10px;
|
|
306
|
+
font-size: 0.82rem;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.copy-btn {
|
|
310
|
+
border: 1px solid #e2e8f0;
|
|
311
|
+
border-radius: 6px;
|
|
312
|
+
width: 24px;
|
|
313
|
+
height: 24px;
|
|
314
|
+
padding: 0;
|
|
315
|
+
color: #94a3b8;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.copy-btn:hover {
|
|
319
|
+
color: #475569;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.copy-chat-btn {
|
|
323
|
+
border: 1px solid #e2e8f0;
|
|
324
|
+
border-radius: 8px;
|
|
325
|
+
padding: 4px 10px;
|
|
326
|
+
color: #475569;
|
|
327
|
+
gap: 6px;
|
|
328
|
+
display: inline-flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.copy-chat-btn:hover {
|
|
333
|
+
color: #1f2937;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.copy-chat-btn svg {
|
|
337
|
+
width: 14px;
|
|
338
|
+
height: 14px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.copy-btn svg {
|
|
342
|
+
width: 12px;
|
|
343
|
+
height: 12px;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.copy-btn[data-copied="true"] {
|
|
347
|
+
color: #16a34a;
|
|
348
|
+
border-color: #86efac;
|
|
349
|
+
background: #f0fdf4;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.copy-btn .copy-label {
|
|
353
|
+
display: none;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.copy-chat-btn[data-copied="true"] {
|
|
357
|
+
color: #16a34a;
|
|
358
|
+
border-color: #86efac;
|
|
359
|
+
background: #f0fdf4;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.sr-only {
|
|
363
|
+
position: absolute;
|
|
364
|
+
width: 1px;
|
|
365
|
+
height: 1px;
|
|
366
|
+
padding: 0;
|
|
367
|
+
margin: -1px;
|
|
368
|
+
overflow: hidden;
|
|
369
|
+
clip: rect(0, 0, 0, 0);
|
|
370
|
+
white-space: nowrap;
|
|
371
|
+
border: 0;
|
|
372
|
+
}
|