agent response improvements
frdel committed
Jun 16, 2025 at 20:14 UTC
73e6855345fbbb4e714de5348d6fb79b2d190d80
11 files changed
+814
-537
agent.py
+78
-35
@@ -1,5 +1,6 @@
1
import asyncio
2
import nest_asyncio
3
+
4
nest_asyncio.apply()
5
6
from collections import OrderedDict
@@ -79,7 +80,7 @@ class AgentContext:
80
if not AgentContext._contexts:
81
return None
82
return list(AgentContext._contexts.values())[0]
82
-
83
+
84
@staticmethod
85
def all():
86
return list(AgentContext._contexts.values())
@@ -97,7 +98,8 @@ class AgentContext:
98
"name": self.name,
99
"created_at": (
100
Localization.get().serialize_datetime(self.created_at)
100
- if self.created_at else Localization.get().serialize_datetime(datetime.fromtimestamp(0))
101
+ if self.created_at
102
+ else Localization.get().serialize_datetime(datetime.fromtimestamp(0))
103
),
104
"no": self.no,
105
"log_guid": self.log.guid,
@@ -106,7 +108,8 @@ class AgentContext:
108
"paused": self.paused,
109
"last_message": (
110
Localization.get().serialize_datetime(self.last_message)
109
- if self.last_message else Localization.get().serialize_datetime(datetime.fromtimestamp(0))
111
+ if self.last_message
112
+ else Localization.get().serialize_datetime(datetime.fromtimestamp(0))
113
),
114
"type": self.type.value,
115
}
@@ -124,7 +127,11 @@ class AgentContext:
127
) -> list[Log.LogItem]:
128
items: list[Log.LogItem] = []
129
for context in AgentContext.all():
127
- items.append(context.log.log(type, heading, content, kvps, temp, update_progress, id, **kwargs))
130
+ items.append(
131
+ context.log.log(
132
+ type, heading, content, kvps, temp, update_progress, id, **kwargs
133
+ )
134
+ )
135
return items
136
137
def kill_process(self):
@@ -253,6 +260,8 @@ class LoopData:
260
self.extras_temporary: OrderedDict[str, history.MessageContent] = OrderedDict()
261
self.extras_persistent: OrderedDict[str, history.MessageContent] = OrderedDict()
262
self.last_response = ""
263
+ self.params_temporary: dict = {}
264
+ self.params_persistent: dict = {}
265
266
# override values with kwargs
267
for key, value in kwargs.items():
@@ -313,9 +322,12 @@ class Agent:
322
323
self.context.streaming_agent = self # mark self as current streamer
324
self.loop_data.iteration += 1
325
+ self.loop_data.params_temporary = {} # clear temporary params
326
327
# call message_loop_start extensions
318
- await self.call_extensions("message_loop_start", loop_data=self.loop_data)
328
+ await self.call_extensions(
329
+ "message_loop_start", loop_data=self.loop_data
330
+ )
331
332
try:
333
# prepare LLM chain (model, system, history)
@@ -328,15 +340,18 @@ class Agent:
340
padding=True,
341
background_color="white",
342
).print(f"{self.agent_name}: Generating")
331
- log = self.context.log.log(
332
- type="agent", heading=f"{self.agent_name}: Generating"
343
+ # create log message right away, more responsive
344
+ self.loop_data.params_temporary["log_item_generating"] = (
345
+ self.context.log.log(
346
+ type="agent", heading=f"{self.agent_name}: Generating"
347
+ )
348
)
349
350
async def stream_callback(chunk: str, full: str):
351
# output the agent response stream
352
if chunk:
353
printer.stream(chunk)
339
- self.log_from_stream(full, log)
354
+ await self.handle_response_stream(full)
355
356
agent_response = await self.call_chat_model(
357
prompt, callback=stream_callback
@@ -414,10 +429,14 @@ class Agent:
429
# for extra in loop_data.extras_temporary.values():
430
# extras += history.Message(False, content=extra).output()
431
extras = history.Message(
417
- False,
418
- content=self.read_prompt("agent.context.extras.md", extras=dirty_json.stringify(
419
- {**loop_data.extras_persistent, **loop_data.extras_temporary}
420
- ))).output()
432
+ False,
433
+ content=self.read_prompt(
434
+ "agent.context.extras.md",
435
+ extras=dirty_json.stringify(
436
+ {**loop_data.extras_persistent, **loop_data.extras_temporary}
437
+ ),
438
+ ),
439
+ ).output()
440
loop_data.extras_temporary.clear()
441
442
# convert history + extras to LLM format
@@ -527,14 +546,14 @@ class Agent:
546
"fw.intervention.md",
547
message=message.message,
548
attachments=message.attachments,
530
- system_message=message.system_message
549
+ system_message=message.system_message,
550
)
551
else:
552
content = self.parse_prompt(
553
"fw.user_message.md",
554
message=message.message,
555
attachments=message.attachments,
537
- system_message=message.system_message
556
+ system_message=message.system_message,
557
)
558
559
# remove empty parts from template
@@ -705,34 +724,39 @@ class Agent:
724
if tool_request is not None:
725
raw_tool_name = tool_request.get("tool_name", "") # Get the raw tool name
726
tool_args = tool_request.get("tool_args", {})
708
-
727
+
728
tool_name = raw_tool_name # Initialize tool_name with raw_tool_name
729
tool_method = None # Initialize tool_method
730
731
# Split raw_tool_name into tool_name and tool_method if applicable
732
if ":" in raw_tool_name:
733
tool_name, tool_method = raw_tool_name.split(":", 1)
715
-
734
+
735
tool = None # Initialize tool to None
736
737
# Try getting tool from MCP first
738
try:
720
- import python.helpers.mcp_handler as mcp_helper
721
- mcp_tool_candidate = mcp_helper.MCPConfig.get_instance().get_tool(self, tool_name)
739
+ import python.helpers.mcp_handler as mcp_helper
740
+
741
+ mcp_tool_candidate = mcp_helper.MCPConfig.get_instance().get_tool(
742
+ self, tool_name
743
+ )
744
if mcp_tool_candidate:
745
tool = mcp_tool_candidate
746
except ImportError:
725
- PrintStyle(background_color="black", font_color="yellow", padding=True).print(
726
- "MCP helper module not found. Skipping MCP tool lookup."
727
- )
747
+ PrintStyle(
748
+ background_color="black", font_color="yellow", padding=True
749
+ ).print("MCP helper module not found. Skipping MCP tool lookup.")
750
except Exception as e:
729
- PrintStyle(background_color="black", font_color="red", padding=True).print(
730
- f"Failed to get MCP tool '{tool_name}': {e}"
731
- )
751
+ PrintStyle(
752
+ background_color="black", font_color="red", padding=True
753
+ ).print(f"Failed to get MCP tool '{tool_name}': {e}")
754
755
# Fallback to local get_tool if MCP tool was not found or MCP lookup failed
756
if not tool:
735
- tool = self.get_tool(name=tool_name, method=tool_method, args=tool_args, message=msg)
757
+ tool = self.get_tool(
758
+ name=tool_name, method=tool_method, args=tool_args, message=msg
759
+ )
760
761
if tool:
762
await self.handle_intervention()
@@ -745,7 +769,9 @@ class Agent:
769
if response.break_loop:
770
return response.message
771
else:
748
- error_detail = f"Tool '{raw_tool_name}' not found or could not be initialized."
772
+ error_detail = (
773
+ f"Tool '{raw_tool_name}' not found or could not be initialized."
774
+ )
775
self.hist_add_warning(error_detail)
776
PrintStyle(font_color="red", padding=True).print(error_detail)
777
self.context.log.log(
@@ -756,21 +782,29 @@ class Agent:
782
self.hist_add_warning(warning_msg_misformat)
783
PrintStyle(font_color="red", padding=True).print(warning_msg_misformat)
784
self.context.log.log(
759
- type="error", content=f"{self.agent_name}: Message misformat, no valid tool request found."
785
+ type="error",
786
+ content=f"{self.agent_name}: Message misformat, no valid tool request found.",
787
)
788
762
- def log_from_stream(self, stream: str, logItem: Log.LogItem):
789
+ async def handle_response_stream(self, stream: str):
790
try:
791
if len(stream) < 25:
792
return # no reason to try
793
response = DirtyJson.parse_string(stream)
794
if isinstance(response, dict):
768
- # log if result is a dictionary already
769
- logItem.update(content=stream, kvps=response)
795
+ await self.call_extensions(
796
+ "response_stream",
797
+ loop_data=self.loop_data,
798
+ text=stream,
799
+ parsed=response,
800
+ )
801
+
802
except Exception as e:
803
pass
804
773
- def get_tool(self, name: str, method: str | None, args: dict, message: str, **kwargs):
805
+ def get_tool(
806
+ self, name: str, method: str | None, args: dict, message: str, **kwargs
807
+ ):
808
from python.tools.unknown import Unknown
809
from python.helpers.tool import Tool
810
@@ -778,13 +812,22 @@ class Agent:
812
"python/tools", name + ".py", Tool
813
)
814
tool_class = classes[0] if classes else Unknown
781
- return tool_class(agent=self, name=name, method=method, args=args, message=message, **kwargs)
815
+ return tool_class(
816
+ agent=self, name=name, method=method, args=args, message=message, **kwargs
817
+ )
818
819
async def call_extensions(self, folder: str, **kwargs) -> Any:
820
from python.helpers.extension import Extension
821
786
- classes = extract_tools.load_classes_from_folder(
787
- "python/extensions/" + folder, "*", Extension
788
- )
822
+ cache = {} # some extensions can be called very often, like response_stream
823
+
824
+ if folder in cache:
825
+ classes = cache[folder]
826
+ else:
827
+ classes = extract_tools.load_classes_from_folder(
828
+ "python/extensions/" + folder, "*", Extension
829
+ )
830
+ cache[folder] = classes
831
+
832
for cls in classes:
833
await cls(agent=self).execute(**kwargs)
prompts/default/agent.system.tool.response.md
+5
-1
@@ -3,7 +3,11 @@ final answer to user
3
ends task processing use only when done or no task active
4
put result in text arg
5
always write full file paths
6
-always use markdown for formatting including headers bold text and lists to improve readability
6
+always use markdown formatting headers bold text lists emojis improve readability
7
+prefer using tables
8
+output full file paths not only names they are clickable
9
+images shown with 
10
+math with latex notation delimiters <$LATEX>...</$LATEX>
11
usage:
12
~~~json
13
{
prompts/research_agent/agent.system.main.communication.md
+1
-1
@@ -42,7 +42,7 @@ No other text is allowed!
42
Only one JSON object allowed per reply
43
44
### rules
45
-Math requires latex notation $...$ delimiters
45
+Math requires latex notation <$LATEX>...<$LATEX> delimiters
46
Code inside markdown must be enclosed in "~~~" and not "```"
47
dont use **
48
python/extensions/response_stream/.gitkeep
python/extensions/response_stream/_10_log_from_stream.py
new
+28
@@ -0,0 +1,28 @@
1
+from python.helpers import persist_chat, tokens
2
+from python.helpers.extension import Extension
3
+from agent import LoopData
4
+import asyncio
5
+from python.helpers.log import LogItem
6
+from python.helpers import log
7
+
8
+
9
+class LogFromStream(Extension):
10
+
11
+ async def execute(
12
+ self,
13
+ loop_data: LoopData = LoopData(),
14
+ text: str = "",
15
+ parsed: dict = {},
16
+ **kwargs
17
+ ):
18
+
19
+ # create log message and store it in loop data temporary params
20
+ if "log_item_generating" not in loop_data.params_temporary:
21
+ loop_data.params_temporary["log_item_generating"] = self.agent.context.log.log(
22
+ type="agent",
23
+ heading=f"{self.agent.agent_name}: Generating",
24
+ )
25
+
26
+ # update log message
27
+ log_item = loop_data.params_temporary["log_item_generating"]
28
+ log_item.update(content=text, kvps=parsed)
\ No newline at end of file
python/extensions/response_stream/_20_live_response.py
new
+41
@@ -0,0 +1,41 @@
1
+from python.helpers import persist_chat, tokens
2
+from python.helpers.extension import Extension
3
+from agent import LoopData
4
+import asyncio
5
+from python.helpers.log import LogItem
6
+from python.helpers import log
7
+
8
+
9
+class LiveResponse(Extension):
10
+
11
+ async def execute(
12
+ self,
13
+ loop_data: LoopData = LoopData(),
14
+ text: str = "",
15
+ parsed: dict = {},
16
+ **kwargs,
17
+ ):
18
+ try:
19
+ if (
20
+ not "tool_name" in parsed
21
+ or parsed["tool_name"] != "response"
22
+ or "tool_args" not in parsed
23
+ or "text" not in parsed["tool_args"]
24
+ or not parsed["tool_args"]["text"]
25
+ ):
26
+ return # not a response
27
+
28
+ # create log message and store it in loop data temporary params
29
+ if "log_item_response" not in loop_data.params_temporary:
30
+ loop_data.params_temporary["log_item_response"] = (
31
+ self.agent.context.log.log(
32
+ type="response",
33
+ heading=f"{self.agent.agent_name}: Responding",
34
+ )
35
+ )
36
+
37
+ # update log message
38
+ log_item = loop_data.params_temporary["log_item_response"]
39
+ log_item.update(content=parsed["tool_args"]["text"])
40
+ except Exception as e:
41
+ pass
python/tools/response.py
+6
-4
@@ -1,13 +1,15 @@
1
from python.helpers.tool import Tool, Response
2
3
+
4
class ResponseTool(Tool):
5
5
- async def execute(self,**kwargs):
6
+ async def execute(self, **kwargs):
7
return Response(message=self.args["text"], break_loop=True)
8
9
async def before_execution(self, **kwargs):
9
- self.log = self.agent.context.log.log(type="response", heading=f"{self.agent.agent_name}: Responding", content=self.args.get("text", ""))
10
+ # self.log = self.agent.context.log.log(type="response", heading=f"{self.agent.agent_name}: Responding", content=self.args.get("text", ""))
11
+ # don't log here anymore, we have the live_response extension now
12
+ pass
13
11
-
14
async def after_execution(self, response, **kwargs):
13
- pass # do not add anything to the history or output
\ No newline at end of file
15
+ pass # do not add anything to the history or output
webui/css/modals.css
+3
-3
@@ -58,7 +58,7 @@
58
}
59
60
.modal-header h2 {
61
- font-size: var(--font-size-large);
61
+ /* font-size: var(--font-size-large); */
62
margin: 0;
63
}
64
@@ -340,9 +340,9 @@
340
}
341
342
/* Typography */
343
-h2 {
343
+/* h2 {
344
color: var(--color-primary);
345
-}
345
+} */
346
347
/* Responsive Design */
348
@media (max-width: 768px) {
webui/index.css
+547
-451
@@ -1,7 +1,9 @@
1
-@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Rubik:ital,wght@0,300..900;1,300..900&display=swap');
1
+@import url("https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Rubik:ital,wght@0,300..900;1,300..900&display=swap");
2
3
/* Add box-sizing globally for better cross-browser consistency */
4
-*, *::before, *::after {
4
+*,
5
+*::before,
6
+*::after {
7
box-sizing: border-box;
8
}
9
@@ -53,6 +55,7 @@
55
56
/* Font sizes */
57
--font-size-small: 0.8rem;
58
+ --font-size-smaller: 0.9rem;
59
--font-size-normal: 1rem;
60
--font-size-large: 1.2rem;
61
@@ -60,7 +63,8 @@
63
--border-radius: 1.125rem;
64
--transition-speed: 0.3s;
65
--svg-filter: brightness(0) saturate(100%) var(--color-primary-filter);
63
- --color-primary-filter: invert(73%) sepia(17%) saturate(360%) hue-rotate(177deg) brightness(87%) contrast(85%);
66
+ --color-primary-filter: invert(73%) sepia(17%) saturate(360%)
67
+ hue-rotate(177deg) brightness(87%) contrast(85%);
68
}
69
70
/* Reset and Base Styles */
@@ -78,7 +82,7 @@ html {
82
overflow: hidden;
83
position: fixed;
84
-webkit-font-smoothing: antialiased; /* Improve font rendering */
81
- -moz-osx-font-smoothing: grayscale; /* Improve font rendering */
85
+ -moz-osx-font-smoothing: grayscale; /* Improve font rendering */
86
}
87
88
body {
@@ -209,39 +213,39 @@ body,
213
214
/* Chats container */
215
.chat-container {
212
- display: flex;
213
- align-items: center;
214
- position: relative;
215
- width: 100%;
216
- min-height: 30px;
217
- gap: 0;
216
+ display: flex;
217
+ align-items: center;
218
+ position: relative;
219
+ width: 100%;
220
+ min-height: 30px;
221
+ gap: 0;
222
}
223
224
/* Update the chat-list-button padding to accommodate the vertical layout */
225
.chat-list-button {
222
- display: block;
223
- width: 100%;
224
- padding: 8px 5px;
225
- cursor: pointer;
226
- overflow: hidden;
227
- position: relative;
228
- border-radius: 4px;
229
- transition: background-color 0.2s ease-in-out;
226
+ display: block;
227
+ width: 100%;
228
+ padding: 8px 5px;
229
+ cursor: pointer;
230
+ overflow: hidden;
231
+ position: relative;
232
+ border-radius: 4px;
233
+ transition: background-color 0.2s ease-in-out;
234
}
235
236
/* Add some more padding to the list items to accommodate the vertical layout */
237
.chat-list-button.has-task-container {
234
- padding-top: 6px;
235
- padding-bottom: 6px;
238
+ padding-top: 6px;
239
+ padding-bottom: 6px;
240
}
241
242
/* Subtle background on hover for the entire row */
243
.chat-list-button:hover {
240
- background-color: rgba(255, 255, 255, 0.03);
244
+ background-color: rgba(255, 255, 255, 0.03);
245
}
246
247
.light-mode .chat-list-button:hover {
244
- background-color: rgba(0, 0, 0, 0.02);
248
+ background-color: rgba(0, 0, 0, 0.02);
249
}
250
251
.chat-name {
@@ -405,12 +409,12 @@ body,
409
}
410
411
/* Typography */
408
-h2,
412
+/* h2,
413
h3 {
414
color: var(--color-primary);
411
-}
415
+} */
416
413
-h2 {
417
+/* h2 {
418
margin-bottom: var(--spacing-sm);
419
margin-top: var(--spacing-lg);
420
}
@@ -421,7 +425,7 @@ h3 {
425
426
h4 {
427
margin: auto 0;
424
-}
428
+} */
429
430
#a0version {
431
color: var(--color-text);
@@ -431,7 +435,7 @@ h4 {
435
}
436
437
pre {
434
- font-family: 'Roboto Mono', monospace;
438
+ font-family: "Roboto Mono", monospace;
439
font-optical-sizing: auto;
440
-webkit-font-optical-sizing: auto;
441
font-size: 0.75rem;
@@ -519,6 +523,10 @@ pre {
523
z-index: 1001;
524
}
525
526
+#progress-bar-box h4 {
527
+ margin: 0;
528
+}
529
+
530
#progress-bar-h {
531
color: var(--color-primary);
532
display: flex;
@@ -526,7 +534,7 @@ pre {
534
justify-content: flex-start;
535
height: 1.2em;
536
text-overflow: ellipsis;
529
- white-space: nowrap; /* Added for text overflow */
537
+ white-space: nowrap; /* Added for text overflow */
538
overflow: hidden;
539
font-weight: normal;
540
}
@@ -544,11 +552,11 @@ pre {
552
553
.shiny-text {
554
background: linear-gradient(
547
- to right,
548
- var(--color-primary-dark) 20%,
549
- var(--color-text) 40%,
550
- var(--color-text) 60%,
551
- var(--color-primary-dark) 60%
555
+ to right,
556
+ var(--color-primary-dark) 20%,
557
+ var(--color-text) 40%,
558
+ var(--color-text) 60%,
559
+ var(--color-primary-dark) 60%
560
);
561
background-size: 200% auto;
562
color: transparent;
@@ -566,17 +574,27 @@ pre {
574
animation: fadeIn 0.5s;
575
-webkit-animation: fadeIn 0.5s;
576
margin-bottom: var(--spacing-sm);
577
+ width: 100%;
578
+ max-width: 100%;
579
}
580
581
.message {
582
background-color: var(--color-message-bg);
583
border-radius: var(--border-radius);
584
padding: 0.9rem var(--spacing-md) 0.7rem var(--spacing-md);
585
+ overflow-x: auto;
586
+ width: fit-content;
587
+ max-width: 100%;
588
+ box-sizing: border-box;
589
+ display: block;
590
}
591
592
.user-container {
593
align-self: flex-end;
594
margin: var(--spacing-sm) var(--spacing-md);
595
+ display: flex;
596
+ justify-content: flex-end;
597
+ width: 100%;
598
}
599
600
.ai-container {
@@ -602,7 +620,7 @@ pre {
620
621
.message-user > div {
622
padding-top: var(--spacing-xs);
605
- font-family: 'Roboto Mono', monospace;
623
+ font-family: "Roboto Mono", monospace;
624
font-optical-sizing: auto;
625
-webkit-font-optical-sizing: auto;
626
font-size: var(--font-size-small);
@@ -724,6 +742,10 @@ pre {
742
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
743
}
744
745
+.msg-heading {
746
+ margin: 0;
747
+}
748
+
749
/* Message Actions */
750
.message-actions {
751
color: var(--color-text);
@@ -838,10 +860,10 @@ pre {
860
border-radius: 8px;
861
color: var(--color-text);
862
flex-grow: 1;
841
- font-family: 'Roboto Mono', monospace;
863
+ font-family: "Roboto Mono", monospace;
864
font-optical-sizing: auto;
865
-webkit-font-optical-sizing: auto;
844
- font-size: 0.90rem;
866
+ font-size: 0.9rem;
867
max-height: 7rem;
868
min-height: 3.05rem;
869
width: 100%;
@@ -883,8 +905,8 @@ pre {
905
}
906
907
#expand-button svg {
886
- width: 1.3rem;
887
- height: 1.3rem;
908
+ width: 1.3rem;
909
+ height: 1.3rem;
910
}
911
912
#chat-input::-webkit-scrollbar {
@@ -960,7 +982,7 @@ pre {
982
opacity: 0.8;
983
text-wrap: nowrap;
984
width: calc(50% - var(--spacing-xs));
963
- float:left;
985
+ float: left;
986
margin: 0 var(--spacing-xs) var(--spacing-xs) 0;
987
padding: var(--spacing-sm) 0.75rem;
988
max-height: 2.3rem;
@@ -1250,7 +1272,7 @@ pre {
1272
}
1273
1274
.text-button:active {
1253
- opacity: 0.5;
1275
+ opacity: 0.5;
1276
}
1277
1278
.text-button svg {
@@ -1266,9 +1288,9 @@ pre {
1288
/* Chat buttons (Send and Mic) */
1289
1290
#chat-buttons-wrapper {
1269
- gap: var(--spacing-xs);
1270
- padding-left: var(--spacing-xs);
1271
- }
1291
+ gap: var(--spacing-xs);
1292
+ padding-left: var(--spacing-xs);
1293
+}
1294
1295
.chat-button {
1296
border: none;
@@ -1379,7 +1401,7 @@ pre {
1401
1402
.remove-image {
1403
-webkit-transform: scale(1.1);
1382
- transform: scale(1.1);
1404
+ transform: scale(1.1);
1405
}
1406
1407
/* Toggle Switch */
@@ -1429,8 +1451,8 @@ input:checked + .slider {
1451
}
1452
1453
input:checked + .slider:before {
1432
- -webkit-transform: translateX(1.0rem);
1433
- transform: translateX(1.0rem);
1454
+ -webkit-transform: translateX(1rem);
1455
+ transform: translateX(1rem);
1456
}
1457
1458
#chat-buttons-wrapper {
@@ -1538,7 +1560,7 @@ input:checked + .slider:before {
1560
border: 1px solid rgba(255, 255, 255, 0.15);
1561
}
1562
1541
-.image-viewer-img{
1563
+.image-viewer-img {
1564
width: 100%;
1565
}
1566
@@ -1614,22 +1636,23 @@ input:checked + .slider:before {
1636
}
1637
}
1638
1617
-@-webkit-keyframes fadeIn { /* Safari and older Chrome */
1639
+@-webkit-keyframes fadeIn {
1640
+ /* Safari and older Chrome */
1641
from {
1642
opacity: 0;
1643
-webkit-transform: translateY(var(--spacing-sm));
1621
- transform: translateY(var(--spacing-sm));
1644
+ transform: translateY(var(--spacing-sm));
1645
}
1646
to {
1647
opacity: 1;
1648
-webkit-transform: translateY(0);
1626
- transform: translateY(0);
1649
+ transform: translateY(0);
1650
}
1651
}
1652
1653
@keyframes shine {
1654
to {
1632
- background-position: -200% center;
1655
+ background-position: -200% center;
1656
}
1657
}
1658
@@ -1672,8 +1695,8 @@ input:checked + .slider:before {
1695
/* New styles for mobile messages */
1696
1697
.message-followup {
1675
- margin-left: var(--spacing-md);
1676
- margin-bottom: var(--spacing-md);
1698
+ margin-left: var(--spacing-md);
1699
+ margin-bottom: var(--spacing-md);
1700
}
1701
1702
.msg-kvps {
@@ -1706,7 +1729,7 @@ input:checked + .slider:before {
1729
}
1730
1731
.kvps-val {
1709
- margin: 0 0 0.4rem 0;
1732
+ margin: 0 0 0.4rem 0;
1733
}
1734
}
1735
@@ -1813,7 +1836,7 @@ input:checked + .slider:before {
1836
.msg-content span::after,
1837
.kvps-val::after,
1838
.message-text::after {
1816
- content: 'Copied!';
1839
+ content: "Copied!";
1840
position: absolute;
1841
opacity: 0;
1842
font-family: "Rubik", Arial, Helvetica, sans-serif;
@@ -1880,20 +1903,20 @@ input:checked + .slider:before {
1903
1904
/* Link styling */
1905
a {
1883
- color: inherit;
1884
- /* text-decoration: none; */
1906
+ color: inherit;
1907
+ /* text-decoration: none; */
1908
}
1909
1910
a:visited {
1888
- color: inherit;
1911
+ color: inherit;
1912
}
1913
1914
a:hover {
1892
- color: inherit;
1915
+ color: inherit;
1916
}
1917
1918
a:active {
1896
- color: inherit;
1919
+ color: inherit;
1920
}
1921
1922
/* Light mode class */
@@ -1912,7 +1935,7 @@ a:active {
1935
}
1936
1937
.light-mode .msg-kvps tr {
1915
- border-bottom: 1px solid rgb(192 192 192 / 50%)
1938
+ border-bottom: 1px solid rgb(192 192 192 / 50%);
1939
}
1940
1941
.light-mode .message-default {
@@ -2034,7 +2057,7 @@ a:active {
2057
2058
.light-mode #logo-container img {
2059
-webkit-filter: invert(100%) grayscale(100%);
2037
- filter: invert(100%) grayscale(100%);
2060
+ filter: invert(100%) grayscale(100%);
2061
}
2062
2063
.light-mode .extension {
@@ -2055,16 +2078,17 @@ a:active {
2078
}
2079
}
2080
2058
-@-webkit-keyframes fadeIn { /* Safari and older Chrome */
2081
+@-webkit-keyframes fadeIn {
2082
+ /* Safari and older Chrome */
2083
from {
2084
opacity: 0;
2085
-webkit-transform: translateY(var(--spacing-sm));
2062
- transform: translateY(var(--spacing-sm));
2086
+ transform: translateY(var(--spacing-sm));
2087
}
2088
to {
2089
opacity: 1;
2090
-webkit-transform: translateY(0);
2067
- transform: translateY(0);
2091
+ transform: translateY(0);
2092
}
2093
}
2094
@@ -2096,77 +2120,77 @@ a:active {
2120
}
2121
2122
.dragdrop-overlay {
2099
- position: fixed;
2100
- top: 0;
2101
- left: 0;
2102
- width: 100%;
2103
- height: 100%;
2104
- background: rgba(0, 0, 0, 0.85);
2105
- z-index: 9999;
2106
- display: flex;
2107
- justify-content: center;
2108
- align-items: center;
2109
- flex-direction: column;
2110
- gap: 1rem;
2123
+ position: fixed;
2124
+ top: 0;
2125
+ left: 0;
2126
+ width: 100%;
2127
+ height: 100%;
2128
+ background: rgba(0, 0, 0, 0.85);
2129
+ z-index: 9999;
2130
+ display: flex;
2131
+ justify-content: center;
2132
+ align-items: center;
2133
+ flex-direction: column;
2134
+ gap: 1rem;
2135
}
2136
2137
.light-mode .dragdrop-overlay {
2114
- background: rgba(255, 255, 255, 0.92);
2138
+ background: rgba(255, 255, 255, 0.92);
2139
}
2140
2141
/* Alpine transition classes */
2142
.transition {
2119
- transition-property: all;
2120
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2143
+ transition-property: all;
2144
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2145
}
2146
2147
.duration-300 {
2124
- transition-duration: 300ms;
2148
+ transition-duration: 300ms;
2149
}
2150
2151
.opacity-0 {
2128
- opacity: 0;
2152
+ opacity: 0;
2153
}
2154
2155
.opacity-100 {
2132
- opacity: 1;
2156
+ opacity: 1;
2157
}
2158
2159
/* Rest of dragdrop styles remain the same */
2160
.dragdrop-icon {
2137
- width: 128px;
2138
- height: 128px;
2139
- opacity: 0.85;
2140
- filter: invert(1);
2161
+ width: 128px;
2162
+ height: 128px;
2163
+ opacity: 0.85;
2164
+ filter: invert(1);
2165
}
2166
2167
.light-mode .dragdrop-icon {
2144
- filter: none;
2168
+ filter: none;
2169
}
2170
2171
.dragdrop-text {
2148
- color: var(--color-text);
2149
- font-size: 1.2rem;
2150
- opacity: 0.85;
2151
- text-align: center;
2152
- max-width: 80%;
2153
- line-height: 1.5;
2172
+ color: var(--color-text);
2173
+ font-size: 1.2rem;
2174
+ opacity: 0.85;
2175
+ text-align: center;
2176
+ max-width: 80%;
2177
+ line-height: 1.5;
2178
}
2179
2180
.dragdrop-subtext {
2157
- color: var(--color-text);
2158
- font-size: 0.9rem;
2159
- opacity: 0.6;
2181
+ color: var(--color-text);
2182
+ font-size: 0.9rem;
2183
+ opacity: 0.6;
2184
}
2185
2162
-.path-link{
2186
+.path-link {
2187
margin-left: 0.1em;
2188
margin-right: 0.1em;
2189
}
2190
2191
/* Alpine cloak to prevent FOUC */
2192
[x-cloak] {
2169
- display: none !important;
2193
+ display: none !important;
2194
}
2195
2196
/* Remove unnecessary specific media query that was causing issues */
@@ -2179,89 +2203,88 @@ a:active {
2203
2204
/* Add to the existing .chat-actions class or create it */
2205
.chat-actions {
2182
- display: flex;
2183
- gap: 5px;
2184
- position: absolute;
2185
- right: 5px;
2186
- top: 50%;
2187
- transform: translateY(-50%);
2188
- z-index: 2; /* Ensure buttons are above the edit field */
2189
- min-width: 70px; /* Ensure minimum width for the buttons */
2190
- justify-content: flex-end;
2206
+ display: flex;
2207
+ gap: 5px;
2208
+ position: absolute;
2209
+ right: 5px;
2210
+ top: 50%;
2211
+ transform: translateY(-50%);
2212
+ z-index: 2; /* Ensure buttons are above the edit field */
2213
+ min-width: 70px; /* Ensure minimum width for the buttons */
2214
+ justify-content: flex-end;
2215
}
2216
2193
-
2217
/* Tasks list container - similar to chats list */
2218
.tasks-list-container {
2196
- max-height: 300px;
2197
- overflow-y: auto;
2198
- margin-top: 10px;
2199
- padding-right: 5px;
2200
- border-radius: 5px;
2201
- position: relative;
2202
- scrollbar-width: thin;
2203
- -ms-overflow-style: auto;
2219
+ max-height: 300px;
2220
+ overflow-y: auto;
2221
+ margin-top: 10px;
2222
+ padding-right: 5px;
2223
+ border-radius: 5px;
2224
+ position: relative;
2225
+ scrollbar-width: thin;
2226
+ -ms-overflow-style: auto;
2227
}
2228
2229
.tasks-list-container::-webkit-scrollbar {
2207
- width: 5px;
2230
+ width: 5px;
2231
}
2232
2233
.tasks-list-container::-webkit-scrollbar-track {
2211
- background: rgba(0, 0, 0, 0.2);
2212
- border-radius: 6px;
2234
+ background: rgba(0, 0, 0, 0.2);
2235
+ border-radius: 6px;
2236
}
2237
2238
.tasks-list-container::-webkit-scrollbar-thumb {
2216
- background-color: var(--color-border);
2217
- border-radius: 6px;
2239
+ background-color: var(--color-border);
2240
+ border-radius: 6px;
2241
}
2242
2243
.tasks-list-container::-webkit-scrollbar-thumb:hover {
2221
- background-color: var(--color-border);
2244
+ background-color: var(--color-border);
2245
}
2246
2247
.task-name {
2225
- display: block;
2226
- width: 100%;
2227
- overflow: hidden;
2228
- text-overflow: ellipsis;
2229
- white-space: nowrap;
2230
- padding: 3px 0;
2231
- margin-left: 10px;
2232
- cursor: pointer;
2233
- border-radius: 4px;
2234
- transition: background-color 0.2s;
2235
- font-size: var(--font-size-small);
2236
- margin-bottom: 2px;
2248
+ display: block;
2249
+ width: 100%;
2250
+ overflow: hidden;
2251
+ text-overflow: ellipsis;
2252
+ white-space: nowrap;
2253
+ padding: 3px 0;
2254
+ margin-left: 10px;
2255
+ cursor: pointer;
2256
+ border-radius: 4px;
2257
+ transition: background-color 0.2s;
2258
+ font-size: var(--font-size-small);
2259
+ margin-bottom: 2px;
2260
}
2261
2262
.task-info-line {
2240
- display: flex;
2241
- justify-content: space-between;
2242
- align-items: center;
2243
- width: 100%;
2244
- margin-top: 2px;
2245
- margin-left: 5px;
2263
+ display: flex;
2264
+ justify-content: space-between;
2265
+ align-items: center;
2266
+ width: 100%;
2267
+ margin-top: 2px;
2268
+ margin-left: 5px;
2269
}
2270
2271
.task-name:hover {
2249
- background-color: rgba(255, 255, 255, 0.1);
2250
- text-decoration: none;
2272
+ background-color: rgba(255, 255, 255, 0.1);
2273
+ text-decoration: none;
2274
}
2275
2276
.light-mode .task-name:hover {
2254
- background-color: rgba(0, 0, 0, 0.05);
2277
+ background-color: rgba(0, 0, 0, 0.05);
2278
}
2279
2280
/* Dark mode overrides */
2281
.light-mode .tab.active {
2259
- color: var(--highlight-pink);
2282
+ color: var(--highlight-pink);
2283
}
2284
2285
.light-mode .tab.active::after {
2263
- background-color: var(--highlight-pink);
2264
- /* box-shadow: 0 0 8px var(--highlight-pink); */
2286
+ background-color: var(--highlight-pink);
2287
+ /* box-shadow: 0 0 8px var(--highlight-pink); */
2288
}
2289
/* Tabs styling */
2290
.tabs-container {
@@ -2311,10 +2334,8 @@ a:active {
2334
}
2335
2336
.light-mode .tab.active {
2314
- box-shadow:
2315
- 0 -4px 8px -2px var(--color-border),
2316
- 4px 0 8px -2px var(--color-border),
2317
- -4px 0 8px -2px var(--color-border);
2337
+ box-shadow: 0 -4px 8px -2px var(--color-border),
2338
+ 4px 0 8px -2px var(--color-border), -4px 0 8px -2px var(--color-border);
2339
}
2340
2341
.light-mode .tab:not(.active) {
@@ -2396,575 +2417,650 @@ a:active {
2417
2418
/* Add specific styling for selected chat items */
2419
.chat-list-button.font-bold {
2399
- position: relative;
2400
- background-color: var(--color-border) 0.05;
2420
+ position: relative;
2421
+ background-color: var(--color-border) 0.05;
2422
}
2423
2424
.chat-list-button.font-bold::before {
2404
- content: '';
2405
- position: absolute;
2406
- left: 0;
2407
- top: 0;
2408
- height: 100%;
2409
- width: 3px;
2410
- background-color: var(--color-border);
2411
- border-top-left-radius: 3px;
2412
- border-bottom-left-radius: 3px;
2425
+ content: "";
2426
+ position: absolute;
2427
+ left: 0;
2428
+ top: 0;
2429
+ height: 100%;
2430
+ width: 3px;
2431
+ background-color: var(--color-border);
2432
+ border-top-left-radius: 3px;
2433
+ border-bottom-left-radius: 3px;
2434
}
2435
2436
.light-mode .chat-list-button.font-bold {
2416
- background-color: var(--color-border) 0.05;
2437
+ background-color: var(--color-border) 0.05;
2438
}
2439
2440
.light-mode .chat-list-button.font-bold::before {
2420
- background-color: var(--color-border);
2441
+ background-color: var(--color-border);
2442
}
2443
2444
/* Make sure the chat container has proper spacing */
2424
-.chat-container, .task-container {
2425
- display: flex;
2426
- align-items: center;
2427
- width: 100%;
2428
- justify-content: space-between;
2445
+.chat-container,
2446
+.task-container {
2447
+ display: flex;
2448
+ align-items: center;
2449
+ width: 100%;
2450
+ justify-content: space-between;
2451
}
2452
2453
/* Settings Modal Styles */
2454
.settings-modal {
2433
- position: fixed;
2434
- top: 0;
2435
- left: 0;
2436
- right: 0;
2437
- bottom: 0;
2438
- z-index: 1000;
2439
- background-color: rgba(0, 0, 0, 0.75);
2440
- display: flex;
2441
- justify-content: center;
2442
- align-items: center;
2443
- overflow: auto;
2444
- padding: 24px;
2455
+ position: fixed;
2456
+ top: 0;
2457
+ left: 0;
2458
+ right: 0;
2459
+ bottom: 0;
2460
+ z-index: 1000;
2461
+ background-color: rgba(0, 0, 0, 0.75);
2462
+ display: flex;
2463
+ justify-content: center;
2464
+ align-items: center;
2465
+ overflow: auto;
2466
+ padding: 24px;
2467
}
2468
2469
.settings-modal-close {
2448
- position: absolute;
2449
- top: 8px;
2450
- right: 16px;
2451
- border: none;
2452
- background: transparent;
2453
- font-size: 24px;
2454
- cursor: pointer;
2455
- color: var(--color-text-secondary);
2470
+ position: absolute;
2471
+ top: 8px;
2472
+ right: 16px;
2473
+ border: none;
2474
+ background: transparent;
2475
+ font-size: 24px;
2476
+ cursor: pointer;
2477
+ color: var(--color-text-secondary);
2478
}
2479
2480
.settings-modal-title {
2459
- margin-top: 0;
2460
- margin-bottom: 24px;
2461
- font-size: 1.8rem;
2462
- text-align: center;
2463
- border-bottom: 1px solid var(--color-border);
2464
- padding-bottom: 12px;
2481
+ margin-top: 0;
2482
+ margin-bottom: 24px;
2483
+ font-size: 1.8rem;
2484
+ text-align: center;
2485
+ border-bottom: 1px solid var(--color-border);
2486
+ padding-bottom: 12px;
2487
}
2488
2489
.settings-modal-content {
2468
- background-color: var(--color-panel);
2469
- color: var(--color-text);
2470
- width: 100%;
2471
- max-width: 800px;
2472
- height: auto;
2473
- max-height: 90vh;
2474
- border-radius: 8px;
2475
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
2476
- padding: 24px;
2477
- position: relative;
2478
- overflow: auto;
2490
+ background-color: var(--color-panel);
2491
+ color: var(--color-text);
2492
+ width: 100%;
2493
+ max-width: 800px;
2494
+ height: auto;
2495
+ max-height: 90vh;
2496
+ border-radius: 8px;
2497
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
2498
+ padding: 24px;
2499
+ position: relative;
2500
+ overflow: auto;
2501
}
2502
2503
/* Settings Sections */
2504
nav ul {
2483
- display: grid;
2484
- grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
2485
- gap: 1rem;
2486
- padding: 0;
2487
- margin: 0 0 1rem 0;
2488
- list-style: none;
2505
+ display: grid;
2506
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
2507
+ gap: 1rem;
2508
+ padding: 0;
2509
+ margin: 0 0 1rem 0;
2510
+ list-style: none;
2511
}
2512
2513
/* Add specific styling for the nav element itself to ensure spacing */
2514
nav {
2493
-margin-bottom: 1rem;
2515
+ margin-bottom: 1rem;
2516
}
2517
2518
nav ul li a {
2497
- display: flex;
2498
- flex-direction: column;
2499
- align-items: center;
2500
- padding: 1rem;
2501
- border-radius: 0.5rem;
2502
- background-color: var(--color-bg-secondary);
2503
- text-decoration: none;
2504
- color: var(--color-text);
2505
- transition: background-color 0.2s ease;
2519
+ display: flex;
2520
+ flex-direction: column;
2521
+ align-items: center;
2522
+ padding: 1rem;
2523
+ border-radius: 0.5rem;
2524
+ background-color: var(--color-bg-secondary);
2525
+ text-decoration: none;
2526
+ color: var(--color-text);
2527
+ transition: background-color 0.2s ease;
2528
}
2529
2530
nav ul li a:hover {
2509
- background-color: var(--color-bg-tertiary);
2531
+ background-color: var(--color-bg-tertiary);
2532
}
2533
2534
nav ul li a img {
2513
- width: 50px;
2514
- height: 50px;
2515
- margin-bottom: 0.5rem;
2516
- filter: var(--svg-filter);
2535
+ width: 50px;
2536
+ height: 50px;
2537
+ margin-bottom: 0.5rem;
2538
+ filter: var(--svg-filter);
2539
}
2540
2541
.section {
2520
- margin-bottom: 3rem;
2521
- animation: fadeIn 0.3s ease;
2522
- width: 100%;
2542
+ margin-bottom: 3rem;
2543
+ animation: fadeIn 0.3s ease;
2544
+ width: 100%;
2545
}
2546
2547
.section-title {
2526
- margin-top: 0;
2527
- margin-bottom: 0.5rem;
2528
- font-size: 1.5rem;
2529
- font-weight: 500;
2530
- color: var(--color-primary);
2531
- border-bottom: 1px solid var(--color-border);
2532
- padding-bottom: 0.5rem;
2548
+ margin-top: 0;
2549
+ margin-bottom: 0.5rem;
2550
+ font-size: 1.5rem;
2551
+ font-weight: 500;
2552
+ color: var(--color-primary);
2553
+ border-bottom: 1px solid var(--color-border);
2554
+ padding-bottom: 0.5rem;
2555
}
2556
2557
.section-description {
2536
- margin-bottom: 1.5rem;
2537
- color: var(--color-text-secondary);
2558
+ margin-bottom: 1.5rem;
2559
+ color: var(--color-text-secondary);
2560
}
2561
2562
.scheduler-header {
2541
- display: flex;
2542
- justify-content: space-between;
2543
- align-items: center;
2544
- margin-bottom: 20px;
2563
+ display: flex;
2564
+ justify-content: space-between;
2565
+ align-items: center;
2566
+ margin-bottom: 20px;
2567
}
2568
2569
.scheduler-header h2 {
2548
- margin: 0;
2549
- font-size: 1.2rem;
2550
- font-weight: 500;
2570
+ margin: 0;
2571
+ font-size: 1.2rem;
2572
+ font-weight: 500;
2573
}
2574
2575
.scheduler-filters {
2554
- display: flex;
2555
- gap: 20px;
2556
- margin-bottom: 20px;
2557
- flex-wrap: wrap;
2576
+ display: flex;
2577
+ gap: 20px;
2578
+ margin-bottom: 20px;
2579
+ flex-wrap: wrap;
2580
}
2581
2582
.scheduler-filter-group {
2561
- display: flex;
2562
- align-items: center;
2563
- gap: 8px;
2583
+ display: flex;
2584
+ align-items: center;
2585
+ gap: 8px;
2586
}
2587
2588
.scheduler-filter-label {
2567
- font-weight: 500;
2568
- color: var(--color-text-secondary);
2589
+ font-weight: 500;
2590
+ color: var(--color-text-secondary);
2591
}
2592
2593
.scheduler-filter-select {
2572
- padding: 6px 8px;
2573
- border-radius: 4px;
2574
- border: 1px solid var(--color-border);
2575
- background-color: var(--color-bg-secondary);
2576
- color: var(--color-text);
2594
+ padding: 6px 8px;
2595
+ border-radius: 4px;
2596
+ border: 1px solid var(--color-border);
2597
+ background-color: var(--color-bg-secondary);
2598
+ color: var(--color-text);
2599
}
2600
2601
.scheduler-task-list {
2580
- width: 100%;
2581
- border-collapse: collapse;
2582
- margin: 1rem 0;
2583
- font-size: 0.9rem;
2584
- overflow-x: auto;
2585
- table-layout: fixed;
2602
+ width: 100%;
2603
+ border-collapse: collapse;
2604
+ margin: 1rem 0;
2605
+ font-size: 0.9rem;
2606
+ overflow-x: auto;
2607
+ table-layout: fixed;
2608
}
2609
2610
.scheduler-task-list th,
2611
.scheduler-task-list td {
2590
- padding: 0.75rem;
2591
- text-align: left;
2592
- border-bottom: 1px solid var(--color-border);
2593
- vertical-align: middle;
2594
- overflow: hidden;
2595
- text-overflow: ellipsis;
2596
- white-space: nowrap;
2612
+ padding: 0.75rem;
2613
+ text-align: left;
2614
+ border-bottom: 1px solid var(--color-border);
2615
+ vertical-align: middle;
2616
+ overflow: hidden;
2617
+ text-overflow: ellipsis;
2618
+ white-space: nowrap;
2619
}
2620
2621
.scheduler-task-list th:nth-child(1), /* Name */
2622
.scheduler-task-list td:nth-child(1) {
2601
- width: 25%;
2623
+ width: 25%;
2624
}
2625
2626
.scheduler-task-list th:nth-child(2), /* State */
2627
.scheduler-task-list td:nth-child(2) {
2606
- width: 10%;
2628
+ width: 10%;
2629
}
2630
2631
.scheduler-task-list th:nth-child(3), /* Type */
2632
.scheduler-task-list td:nth-child(3) {
2611
- width: 10%;
2633
+ width: 10%;
2634
}
2635
2636
.scheduler-task-list th:nth-child(4), /* Schedule */
2637
.scheduler-task-list td:nth-child(4) {
2616
- width: 20%;
2638
+ width: 20%;
2639
}
2640
2641
.scheduler-task-list th:nth-child(5), /* Last Run */
2642
.scheduler-task-list td:nth-child(5) {
2621
- width: 20%;
2643
+ width: 20%;
2644
}
2645
2646
.scheduler-task-list th:nth-child(6), /* Actions */
2647
.scheduler-task-list td:nth-child(6) {
2626
- width: 15%;
2627
- text-align: right;
2648
+ width: 15%;
2649
+ text-align: right;
2650
}
2651
2652
.scheduler-task-list th {
2631
- background-color: var(--color-bg-secondary);
2632
- font-weight: 500;
2633
- cursor: pointer;
2634
- user-select: none;
2653
+ background-color: var(--color-bg-secondary);
2654
+ font-weight: 500;
2655
+ cursor: pointer;
2656
+ user-select: none;
2657
}
2658
2659
.scheduler-task-list th:hover {
2638
- background-color: var(--color-bg-tertiary);
2660
+ background-color: var(--color-bg-tertiary);
2661
}
2662
2663
.scheduler-task-actions {
2642
- display: flex;
2643
- gap: 8px;
2664
+ display: flex;
2665
+ gap: 8px;
2666
}
2667
2668
.scheduler-task-action {
2647
- padding: 4px;
2648
- background: none;
2649
- border: none;
2650
- color: var(--color-text-secondary);
2651
- cursor: pointer;
2652
- border-radius: 4px;
2669
+ padding: 4px;
2670
+ background: none;
2671
+ border: none;
2672
+ color: var(--color-text-secondary);
2673
+ cursor: pointer;
2674
+ border-radius: 4px;
2675
}
2676
2677
.scheduler-task-action:hover {
2656
- background-color: var(--color-bg-tertiary);
2657
- color: var(--color-text);
2678
+ background-color: var(--color-bg-tertiary);
2679
+ color: var(--color-text);
2680
}
2681
2682
.scheduler-status-badge {
2661
- display: inline-block;
2662
- padding: 4px 8px;
2663
- border-radius: 4px;
2664
- font-size: 12px;
2665
- font-weight: 500;
2666
- text-transform: capitalize;
2667
- white-space: nowrap;
2683
+ display: inline-block;
2684
+ padding: 4px 8px;
2685
+ border-radius: 4px;
2686
+ font-size: 12px;
2687
+ font-weight: 500;
2688
+ text-transform: capitalize;
2689
+ white-space: nowrap;
2690
}
2691
2692
.scheduler-status-idle {
2671
- background-color: rgba(0, 100, 0, 0.2);
2672
- color: #2a9d8f; /* Dark green that works with both light and dark themes */
2673
- border: 1px solid rgba(42, 157, 143, 0.3);
2693
+ background-color: rgba(0, 100, 0, 0.2);
2694
+ color: #2a9d8f; /* Dark green that works with both light and dark themes */
2695
+ border: 1px solid rgba(42, 157, 143, 0.3);
2696
}
2697
2698
.scheduler-status-running {
2677
- background-color: rgba(0, 60, 120, 0.2);
2678
- color: #4361ee; /* Dark blue that works with both light and dark themes */
2679
- border: 1px solid rgba(67, 97, 238, 0.3);
2699
+ background-color: rgba(0, 60, 120, 0.2);
2700
+ color: #4361ee; /* Dark blue that works with both light and dark themes */
2701
+ border: 1px solid rgba(67, 97, 238, 0.3);
2702
}
2703
2704
.scheduler-status-disabled {
2683
- background-color: rgba(70, 70, 70, 0.2);
2684
- color: #6c757d; /* Dark grey that works with both light and dark themes */
2685
- border: 1px solid rgba(108, 117, 125, 0.3);
2705
+ background-color: rgba(70, 70, 70, 0.2);
2706
+ color: #6c757d; /* Dark grey that works with both light and dark themes */
2707
+ border: 1px solid rgba(108, 117, 125, 0.3);
2708
}
2709
2710
.scheduler-status-error {
2689
- background-color: rgba(120, 0, 0, 0.2);
2690
- color: #e63946; /* Dark red that works with both light and dark themes */
2691
- border: 1px solid rgba(230, 57, 70, 0.3);
2711
+ background-color: rgba(120, 0, 0, 0.2);
2712
+ color: #e63946; /* Dark red that works with both light and dark themes */
2713
+ border: 1px solid rgba(230, 57, 70, 0.3);
2714
}
2715
2716
/* Light mode adjustments */
2717
.light-mode .scheduler-status-idle {
2696
- background-color: rgba(42, 157, 143, 0.1);
2697
- color: #1a6f65; /* Darker green for light mode */
2718
+ background-color: rgba(42, 157, 143, 0.1);
2719
+ color: #1a6f65; /* Darker green for light mode */
2720
}
2721
2722
.light-mode .scheduler-status-running {
2701
- background-color: rgba(67, 97, 238, 0.1);
2702
- color: #2540b3; /* Darker blue for light mode */
2723
+ background-color: rgba(67, 97, 238, 0.1);
2724
+ color: #2540b3; /* Darker blue for light mode */
2725
}
2726
2727
.light-mode .scheduler-status-disabled {
2706
- background-color: rgba(108, 117, 125, 0.1);
2707
- color: #495057; /* Darker grey for light mode */
2728
+ background-color: rgba(108, 117, 125, 0.1);
2729
+ color: #495057; /* Darker grey for light mode */
2730
}
2731
2732
.light-mode .scheduler-status-error {
2711
- background-color: rgba(230, 57, 70, 0.1);
2712
- color: #c5283d; /* Darker red for light mode */
2733
+ background-color: rgba(230, 57, 70, 0.1);
2734
+ color: #c5283d; /* Darker red for light mode */
2735
}
2736
2737
.scheduler-empty {
2716
- text-align: center;
2717
- padding: 40px 0;
2718
- color: var(--color-text-secondary);
2738
+ text-align: center;
2739
+ padding: 40px 0;
2740
+ color: var(--color-text-secondary);
2741
}
2742
2743
.scheduler-empty-icon {
2722
- font-size: 32px;
2723
- margin-bottom: 10px;
2744
+ font-size: 32px;
2745
+ margin-bottom: 10px;
2746
}
2747
2748
.scheduler-empty-text {
2727
- margin-bottom: 20px;
2749
+ margin-bottom: 20px;
2750
}
2751
2752
.scheduler-loading {
2731
- text-align: center;
2732
- padding: 40px 0;
2733
- color: var(--color-text-secondary);
2753
+ text-align: center;
2754
+ padding: 40px 0;
2755
+ color: var(--color-text-secondary);
2756
}
2757
2758
.scheduler-task-details {
2737
- padding: 16px;
2738
- background-color: var(--color-bg-secondary);
2739
- border-radius: 4px;
2759
+ padding: 16px;
2760
+ background-color: var(--color-bg-secondary);
2761
+ border-radius: 4px;
2762
}
2763
2764
.scheduler-details-grid {
2743
- display: grid;
2744
- grid-template-columns: 120px 1fr;
2745
- gap: 8px 16px;
2746
- margin-bottom: 16px;
2765
+ display: grid;
2766
+ grid-template-columns: 120px 1fr;
2767
+ gap: 8px 16px;
2768
+ margin-bottom: 16px;
2769
}
2770
2771
.scheduler-details-label {
2750
- font-weight: 500;
2751
- color: var(--color-text-secondary);
2752
- display: flex;
2753
- align-items: center;
2772
+ font-weight: 500;
2773
+ color: var(--color-text-secondary);
2774
+ display: flex;
2775
+ align-items: center;
2776
}
2777
2778
.scheduler-details-value {
2757
- color: var(--color-text);
2758
- word-break: break-word;
2779
+ color: var(--color-text);
2780
+ word-break: break-word;
2781
}
2782
2783
.scheduler-details-actions {
2762
- display: flex;
2763
- justify-content: flex-end;
2784
+ display: flex;
2785
+ justify-content: flex-end;
2786
}
2787
2788
.scheduler-form {
2767
- background-color: var(--color-bg-secondary);
2768
- border-radius: 4px;
2769
- padding: 20px;
2770
- margin-bottom: 20px;
2789
+ background-color: var(--color-bg-secondary);
2790
+ border-radius: 4px;
2791
+ padding: 20px;
2792
+ margin-bottom: 20px;
2793
}
2794
2795
.scheduler-form-title {
2774
- font-size: 1.2rem;
2775
- font-weight: 500;
2776
- margin-bottom: 20px;
2777
- padding-bottom: 10px;
2778
- border-bottom: 1px solid var(--color-border);
2796
+ font-size: 1.2rem;
2797
+ font-weight: 500;
2798
+ margin-bottom: 20px;
2799
+ padding-bottom: 10px;
2800
+ border-bottom: 1px solid var(--color-border);
2801
}
2802
2803
.scheduler-form-grid {
2782
- display: grid;
2783
- grid-template-columns: 1fr 1fr;
2784
- gap: 16px;
2785
- margin-bottom: 20px;
2804
+ display: grid;
2805
+ grid-template-columns: 1fr 1fr;
2806
+ gap: 16px;
2807
+ margin-bottom: 20px;
2808
}
2809
2810
.scheduler-form-field {
2789
- display: flex;
2790
- flex-direction: column;
2791
- gap: 6px;
2811
+ display: flex;
2812
+ flex-direction: column;
2813
+ gap: 6px;
2814
}
2815
2816
.full-width {
2795
- grid-column: 1 / -1;
2817
+ grid-column: 1 / -1;
2818
}
2819
2820
.scheduler-form-label {
2799
- font-weight: 500;
2821
+ font-weight: 500;
2822
}
2823
2824
.scheduler-form-help {
2803
- font-size: 12px;
2804
- color: var(--color-text-secondary);
2825
+ font-size: 12px;
2826
+ color: var(--color-text-secondary);
2827
}
2828
2829
.scheduler-form-actions {
2808
- display: flex;
2809
- justify-content: flex-end;
2810
- gap: 12px;
2830
+ display: flex;
2831
+ justify-content: flex-end;
2832
+ gap: 12px;
2833
}
2834
2835
.scheduler-schedule-builder {
2814
- display: grid;
2815
- grid-template-columns: repeat(5, 1fr);
2816
- gap: 12px;
2836
+ display: grid;
2837
+ grid-template-columns: repeat(5, 1fr);
2838
+ gap: 12px;
2839
}
2840
2841
.scheduler-schedule-field {
2820
- display: flex;
2821
- flex-direction: column;
2822
- gap: 4px;
2823
- max-width: 70px; /* Limit width of schedule fields */
2842
+ display: flex;
2843
+ flex-direction: column;
2844
+ gap: 4px;
2845
+ max-width: 70px; /* Limit width of schedule fields */
2846
}
2847
2848
.scheduler-schedule-field input {
2827
- width: 100%;
2828
- min-width: 0; /* Allow shrinking below content size */
2829
- font-size: 0.9rem; /* Slightly reduce font size for better fit */
2849
+ width: 100%;
2850
+ min-width: 0; /* Allow shrinking below content size */
2851
+ font-size: 0.9rem; /* Slightly reduce font size for better fit */
2852
}
2853
2854
.scheduler-schedule-label {
2833
- font-size: 12px;
2834
- color: var(--color-text-secondary);
2855
+ font-size: 12px;
2856
+ color: var(--color-text-secondary);
2857
}
2858
2859
.input-group {
2838
- display: flex;
2839
- gap: 8px;
2860
+ display: flex;
2861
+ gap: 8px;
2862
}
2863
2864
/* Sort indicators */
2865
.scheduler-sort-indicator {
2844
- display: inline-block;
2845
- margin-left: 4px;
2846
- transition: transform 0.2s ease;
2866
+ display: inline-block;
2867
+ margin-left: 4px;
2868
+ transition: transform 0.2s ease;
2869
}
2870
2871
.scheduler-sort-desc {
2850
- transform: rotate(180deg);
2872
+ transform: rotate(180deg);
2873
}
2874
2875
/* Responsive adjustments */
2876
@media (max-width: 768px) {
2855
- .scheduler-form-grid {
2856
- grid-template-columns: 1fr;
2857
- }
2877
+ .scheduler-form-grid {
2878
+ grid-template-columns: 1fr;
2879
+ }
2880
2859
- .scheduler-schedule-builder {
2860
- grid-template-columns: 1fr 1fr;
2861
- }
2881
+ .scheduler-schedule-builder {
2882
+ grid-template-columns: 1fr 1fr;
2883
+ }
2884
2863
- .scheduler-filters {
2864
- flex-direction: column;
2865
- gap: 12px;
2866
- }
2885
+ .scheduler-filters {
2886
+ flex-direction: column;
2887
+ gap: 12px;
2888
+ }
2889
2868
- .scheduler-task-actions {
2869
- flex-wrap: wrap;
2870
- }
2890
+ .scheduler-task-actions {
2891
+ flex-wrap: wrap;
2892
+ }
2893
}
2894
2895
@media (max-width: 480px) {
2874
- nav ul li a {
2875
- flex-direction: row;
2876
- justify-content: flex-start;
2877
- gap: 1rem;
2878
- padding: 0.75rem 1rem;
2879
- }
2896
+ nav ul li a {
2897
+ flex-direction: row;
2898
+ justify-content: flex-start;
2899
+ gap: 1rem;
2900
+ padding: 0.75rem 1rem;
2901
+ }
2902
2881
- nav ul li a img {
2882
- margin-bottom: 0;
2883
- width: 30px;
2884
- height: 30px;
2885
- }
2903
+ nav ul li a img {
2904
+ margin-bottom: 0;
2905
+ width: 30px;
2906
+ height: 30px;
2907
+ }
2908
}
2909
2910
/* Add row hover effect for task list rows matching left panel hover */
2911
.scheduler-task-list tbody tr {
2890
- cursor: pointer;
2891
- transition: background-color 0.2s ease;
2912
+ cursor: pointer;
2913
+ transition: background-color 0.2s ease;
2914
}
2915
2916
.scheduler-task-list tbody tr:hover {
2895
- background-color: rgba(255, 255, 255, 0.03);
2917
+ background-color: rgba(255, 255, 255, 0.03);
2918
}
2919
2920
.light-mode .scheduler-task-list tbody tr:hover {
2899
- background-color: rgba(0, 0, 0, 0.02);
2921
+ background-color: rgba(0, 0, 0, 0.02);
2922
}
2923
2924
.scheduler-task-list th {
2903
- background-color: var(--color-bg-secondary);
2904
- font-weight: 500;
2905
- cursor: pointer;
2906
- user-select: none;
2925
+ background-color: var(--color-bg-secondary);
2926
+ font-weight: 500;
2927
+ cursor: pointer;
2928
+ user-select: none;
2929
}
2930
2931
.scheduler-task-list th:hover {
2910
- background-color: var(--color-bg-tertiary);
2932
+ background-color: var(--color-bg-tertiary);
2933
}
2934
2935
/* Task detail view styling */
2936
.scheduler-detail-view {
2915
- background-color: var(--color-bg-secondary);
2916
- border-radius: 4px;
2917
- padding: 20px;
2918
- margin-bottom: 20px;
2919
- animation: fadeIn 0.3s ease;
2937
+ background-color: var(--color-bg-secondary);
2938
+ border-radius: 4px;
2939
+ padding: 20px;
2940
+ margin-bottom: 20px;
2941
+ animation: fadeIn 0.3s ease;
2942
}
2943
2944
.scheduler-detail-header {
2923
- display: flex;
2924
- justify-content: flex-start;
2925
- align-items: center;
2926
- margin-bottom: 20px;
2927
- padding-bottom: 10px;
2928
- border-bottom: 1px solid var(--color-border);
2929
- flex-wrap: wrap;
2930
- gap: 10px;
2945
+ display: flex;
2946
+ justify-content: flex-start;
2947
+ align-items: center;
2948
+ margin-bottom: 20px;
2949
+ padding-bottom: 10px;
2950
+ border-bottom: 1px solid var(--color-border);
2951
+ flex-wrap: wrap;
2952
+ gap: 10px;
2953
}
2954
2955
.scheduler-detail-header .scheduler-detail-title {
2934
- font-size: 1.4rem;
2935
- font-weight: 500;
2936
- margin: 0;
2937
- margin-right: auto;
2956
+ font-size: 1.4rem;
2957
+ font-weight: 500;
2958
+ margin: 0;
2959
+ margin-right: auto;
2960
}
2961
2962
.scheduler-detail-header .scheduler-status-badge {
2941
- margin-right: 10px;
2963
+ margin-right: 10px;
2964
}
2965
2966
.scheduler-detail-content {
2945
- margin-bottom: 20px;
2967
+ margin-bottom: 20px;
2968
}
2969
2970
/* Task Scheduler Styles */
2971
2972
.scheduler-no-schedule {
2951
- color: var(--color-text-secondary);
2952
- opacity: 0.7;
2953
- font-style: italic;
2973
+ color: var(--color-text-secondary);
2974
+ opacity: 0.7;
2975
+ font-style: italic;
2976
}
2977
2978
.task-container-vertical {
2957
- display: flex;
2958
- flex-direction: column;
2959
- width: 100%;
2960
- gap: 6px;
2979
+ display: flex;
2980
+ flex-direction: column;
2981
+ width: 100%;
2982
+ gap: 6px;
2983
}
2984
2985
/* Smaller status badge for task list */
2986
.scheduler-status-badge-small {
2965
- font-size: 10px;
2966
- padding: 2px 6px;
2967
- margin-right: 5px;
2968
- min-width: 40px;
2969
- text-align: center;
2987
+ font-size: 10px;
2988
+ padding: 2px 6px;
2989
+ margin-right: 5px;
2990
+ min-width: 40px;
2991
+ text-align: center;
2992
+}
2993
+
2994
+/* Markdown in messages */
2995
+.msg-content {
2996
+ font-size: var(--font-size-small);
2997
+}
2998
+
2999
+.message-agent-response .msg-content {
3000
+ font-size: var(--font-size-smaller);
3001
+}
3002
+
3003
+.msg-content h1 {
3004
+ font-size: 1.25em;
3005
+}
3006
+
3007
+.msg-content h2 {
3008
+ font-size: 1.2em;
3009
+}
3010
+
3011
+.msg-content h3 {
3012
+ font-size: 1.15em;
3013
+}
3014
+
3015
+.msg-content h4 {
3016
+ font-size: 1.1em;
3017
+}
3018
+
3019
+.msg-content h5 {
3020
+ font-size: 1.05em;
3021
+}
3022
+
3023
+.msg-content h6 {
3024
+ font-size: 1em;
3025
+}
3026
+
3027
+.msg-content table {
3028
+ width: 100%;
3029
+ border-collapse: collapse;
3030
+ margin: 1em 0;
3031
+ font-size: 0.98em;
3032
+ background: transparent;
3033
+ border-radius: 8px;
3034
+ overflow: hidden;
3035
+}
3036
+
3037
+.msg-content th,
3038
+.msg-content td {
3039
+ padding: 0.55em 1em;
3040
+ border: 1px solid rgba(142, 142, 142, 0.1);
3041
+ text-align: left;
3042
+ background: transparent;
3043
+}
3044
+
3045
+.msg-content th {
3046
+ font-weight: 600;
3047
+ background: rgba(142, 142, 142, 0.1);
3048
+}
3049
+
3050
+.msg-content tr:nth-child(even) {
3051
+ background: rgba(142, 142, 142, 0.1);
3052
+}
3053
+
3054
+.msg-content tr:nth-child(odd) {
3055
+ background: transparent;
3056
+}
3057
+
3058
+.msg-content table {
3059
+ box-shadow: none;
3060
+}
3061
+
3062
+.msg-content pre:has(code) {
3063
+ padding: 0.5em;
3064
+ border: 1px solid rgba(142, 142, 142, 0.1);
3065
+ border-radius: 0.3em;
3066
}
webui/index.html
+2
@@ -118,6 +118,8 @@
118
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js"
119
crossorigin="anonymous"></script>
120
121
+ <script src="https://rawgit.com/showdownjs/showdown/develop/dist/showdown.min.js"></script>
122
+
123
<!-- Non-module scripts after Alpine.js -->
124
<script type="text/javascript" src="js/settings.js"></script>
125
<script type="text/javascript" src="js/file_browser.js"></script>
webui/js/messages.js
+103
-42
@@ -2,6 +2,9 @@
2
import { openImageModal } from "./image_modal.js";
3
import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
4
5
+const katexDelimiterLeft = "<$LATEX>";
6
+const katexDelimiterRight = "</$LATEX>";
7
+
8
function createCopyButton() {
9
const button = document.createElement("button");
10
button.className = "copy-button";
@@ -92,6 +95,7 @@ export function _drawMessage(
95
96
if (heading) {
97
const headingElement = document.createElement("h4");
98
+ headingElement.classList.add("msg-heading");
99
headingElement.textContent = heading;
100
messageDiv.appendChild(headingElement);
101
}
@@ -102,47 +106,62 @@ export function _drawMessage(
106
if (markdown) {
107
const contentDiv = document.createElement("div");
108
contentDiv.classList.add("msg-content", ...contentClasses);
105
-
109
+
110
const spanElement = document.createElement("span"); // Wrapper span
107
- let processedContent = convertPathsToLinks(content);
111
+ let processedContent = content;
112
processedContent = convertImageTags(processedContent);
109
- spanElement.innerHTML = marked.parse(processedContent, { breaks: true });
113
+ processedContent = convertImgFilePaths(processedContent);
114
+ processedContent = marked.parse(processedContent, { breaks: true });
115
+ processedContent = convertPathsToLinks(processedContent);
116
+ spanElement.innerHTML = processedContent;
117
contentDiv.appendChild(spanElement);
111
-
118
+
119
addCopyButtonToElement(contentDiv);
120
messageDiv.appendChild(contentDiv);
121
122
// KaTeX rendering for markdown
123
if (window.renderMathInElement && latex) {
124
renderMathInElement(contentDiv, {
118
- delimiters: [{ left: "$", right: "$", display: true }],
125
+ delimiters: [
126
+ {
127
+ left: katexDelimiterLeft,
128
+ right: katexDelimiterRight,
129
+ display: true,
130
+ },
131
+ ],
132
throwOnError: false,
133
});
134
}
135
} else {
123
- const preElement = document.createElement("pre");
124
- preElement.classList.add("msg-content", ...contentClasses);
125
- preElement.style.whiteSpace = "pre-wrap";
126
- preElement.style.wordBreak = "break-word";
136
+ const preElement = document.createElement("pre");
137
+ preElement.classList.add("msg-content", ...contentClasses);
138
+ preElement.style.whiteSpace = "pre-wrap";
139
+ preElement.style.wordBreak = "break-word";
140
128
- const spanElement = document.createElement("span");
129
- spanElement.innerHTML = convertHTML(content);
141
+ const spanElement = document.createElement("span");
142
+ spanElement.innerHTML = convertHTML(content);
143
131
- // Add click handler for small screens
132
- spanElement.addEventListener("click", () => {
133
- copyText(spanElement.textContent, spanElement);
134
- });
144
+ // Add click handler for small screens
145
+ spanElement.addEventListener("click", () => {
146
+ copyText(spanElement.textContent, spanElement);
147
+ });
148
136
- preElement.appendChild(spanElement);
137
- addCopyButtonToElement(preElement);
138
- messageDiv.appendChild(preElement);
149
+ preElement.appendChild(spanElement);
150
+ addCopyButtonToElement(preElement);
151
+ messageDiv.appendChild(preElement);
152
140
- // Render LaTeX math within the span
141
- if (window.renderMathInElement && latex) {
142
- renderMathInElement(spanElement, {
143
- delimiters: [{ left: "$", right: "$", display: true }],
144
- throwOnError: false,
145
- });
153
+ // Render LaTeX math within the span
154
+ if (window.renderMathInElement && latex) {
155
+ renderMathInElement(spanElement, {
156
+ delimiters: [
157
+ {
158
+ left: katexDelimiterLeft,
159
+ right: katexDelimiterRight,
160
+ display: true,
161
+ },
162
+ ],
163
+ throwOnError: false,
164
+ });
165
}
166
}
167
}
@@ -268,6 +287,7 @@ export function drawMessageUser(
287
messageDiv.classList.add("message", "message-user");
288
289
const headingElement = document.createElement("h4");
290
+ headingElement.classList.add("msg-heading");
291
headingElement.textContent = "User message";
292
messageDiv.appendChild(headingElement);
293
@@ -589,7 +609,7 @@ function drawKvps(container, kvps, latex) {
609
610
if (window.renderMathInElement && latex) {
611
renderMathInElement(span, {
592
- delimiters: [{ left: "$", right: "$", display: true }],
612
+ delimiters: [{ left: katexDelimiterLeft, right: katexDelimiterRight, display: true }],
613
throwOnError: false,
614
});
615
}
@@ -651,11 +671,15 @@ function convertHTML(str) {
671
if (typeof str !== "string") str = JSON.stringify(str, null, 2);
672
673
let result = escapeHTML(str);
654
- result = convertPathsToLinks(result);
674
result = convertImageTags(result);
675
+ result = convertPathsToLinks(result);
676
return result;
677
}
678
679
+function convertImgFilePaths(str) {
680
+ return str.replace("img://", "/image_get?path=");
681
+}
682
+
683
function escapeHTML(str) {
684
const escapeChars = {
685
"&": "&",
@@ -668,25 +692,62 @@ function escapeHTML(str) {
692
}
693
694
function convertPathsToLinks(str) {
671
- function generateLinks(match, ...args) {
672
- const parts = match.split("/");
673
-
674
- if (!parts[0]) parts.shift();
675
- let conc = "";
676
- let html = "";
677
- for (let part of parts) {
678
- conc += "/" + part;
679
- html += `/<a href="#" class="path-link" onclick="openFileLink('${conc}');">${part}</a>`;
680
- }
681
- return html;
695
+ function generateLinks(match) {
696
+ const parts = match.split("/");
697
+ if (!parts[0]) parts.shift(); // drop empty element left of first “/”
698
+ let conc = "";
699
+ let html = "";
700
+ for (const part of parts) {
701
+ conc += "/" + part;
702
+ html += `/<a href="#" class="path-link" onclick="openFileLink('${conc}');">${part}</a>`;
703
+ }
704
+ return html;
705
}
706
684
- const prefix = `(?:^|[ \`'"\\n]|'|")`; // Use a non-capturing group for OR logic
685
- const folder = `[a-zA-Z0-9_\\/.\\-]`; // Characters allowed in folder chain
686
- const file = `[a-zA-Z0-9_\\-\\/]`; // Characters allowed in file names
707
+ const prefix = `(?:^|[> \`'"\\n]|'|")`;
708
+ const folder = `[a-zA-Z0-9_\\/.\\-]`;
709
+ const file = `[a-zA-Z0-9_\\-\\/]`;
710
const suffix = `(?<!\\.)`;
711
+ const pathRegex = new RegExp(`(?<=${prefix})\\/${folder}*${file}${suffix}`, "g");
712
689
- const regex = new RegExp(`(?<=${prefix})\\/${folder}*${file}${suffix}`, "g");
713
+ // skip paths inside html tags, like <img src="/path/to/image">
714
+ const tagRegex = /(<(?:[^<>"']+|"[^"]*"|'[^']*')*>)/g;
715
691
- return str.replace(regex, generateLinks);
716
+ return str
717
+ .split(tagRegex) // keep tags & text separate
718
+ .map(chunk => {
719
+ // if it *starts* with '<', it’s a tag -> leave untouched
720
+ if (chunk.startsWith("<")) return chunk;
721
+ // otherwise run your link-generation
722
+ return chunk.replace(pathRegex, generateLinks);
723
+ })
724
+ .join("");
725
}
726
+
727
+// function convertPathsToLinksInHtml(htmlString) {
728
+// // 1. Parse the input safely
729
+// const wrapper = document.createElement("div");
730
+// wrapper.innerHTML = htmlString;
731
+
732
+// // 2. Depth-first walk
733
+// function walk(node) {
734
+// // Skip <script> and <style> blocks entirely
735
+// if (node.nodeName === "SCRIPT" || node.nodeName === "STYLE") return;
736
+
737
+// if (node.nodeType === Node.TEXT_NODE) {
738
+// const original = node.nodeValue;
739
+// const replaced = convertPathsToLinks(original);
740
+// if (replaced !== original) {
741
+// // Turn the replacement HTML string into real nodes
742
+// const frag = document.createRange().createContextualFragment(replaced);
743
+// node.replaceWith(frag);
744
+// }
745
+// } else {
746
+// // Recurse into children
747
+// for (const child of Array.from(node.childNodes)) walk(child);
748
+// }
749
+// }
750
+
751
+// walk(wrapper);
752
+// return wrapper.innerHTML;
753
+// }