@lazyingart/agintiflow 0.8.9 → 0.8.10

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a resumable Playwright website-control agent with OpenAI-compatible tool calling.",
6
6
  "license": "Apache-2.0",
package/public/app.js CHANGED
@@ -1014,6 +1014,201 @@ function escapeHtml(value) {
1014
1014
  );
1015
1015
  }
1016
1016
 
1017
+ function safeLinkHref(value) {
1018
+ const raw = String(value || "").trim();
1019
+ try {
1020
+ const parsed = new URL(raw, window.location.origin);
1021
+ return ["http:", "https:", "mailto:"].includes(parsed.protocol) ? parsed.href : "";
1022
+ } catch {
1023
+ return "";
1024
+ }
1025
+ }
1026
+
1027
+ function renderInlineMarkdown(value) {
1028
+ const placeholders = [];
1029
+ const protect = (html) => {
1030
+ const index = placeholders.push(html) - 1;
1031
+ return `\u0000${index}\u0000`;
1032
+ };
1033
+
1034
+ let text = String(value || "");
1035
+ text = text.replace(/`([^`\n]+)`/g, (_match, code) => protect(`<code>${escapeHtml(code)}</code>`));
1036
+ text = text.replace(/\[([^\]\n]+)]\(([^)\s]+)\)/g, (_match, label, href) => {
1037
+ const safeHref = safeLinkHref(href);
1038
+ if (!safeHref) return escapeHtml(label);
1039
+ return protect(
1040
+ `<a href="${escapeHtml(safeHref)}" target="_blank" rel="noopener noreferrer">${escapeHtml(label)}</a>`
1041
+ );
1042
+ });
1043
+
1044
+ let html = escapeHtml(text);
1045
+ html = html
1046
+ .replace(/\*\*([^*\n]+)\*\*/g, "<strong>$1</strong>")
1047
+ .replace(/__([^_\n]+)__/g, "<strong>$1</strong>")
1048
+ .replace(/\*([^*\n]+)\*/g, "<em>$1</em>")
1049
+ .replace(/~~([^~\n]+)~~/g, "<del>$1</del>");
1050
+
1051
+ return html.replace(/\u0000(\d+)\u0000/g, (_match, index) => placeholders[Number(index)] || "");
1052
+ }
1053
+
1054
+ function splitTableRow(line) {
1055
+ return String(line || "")
1056
+ .trim()
1057
+ .replace(/^\|/, "")
1058
+ .replace(/\|$/, "")
1059
+ .split("|")
1060
+ .map((cell) => cell.trim());
1061
+ }
1062
+
1063
+ function isMarkdownTableSeparator(line) {
1064
+ return /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(String(line || ""));
1065
+ }
1066
+
1067
+ function renderMarkdownTable(headerLine, rows) {
1068
+ const headers = splitTableRow(headerLine);
1069
+ const bodyRows = rows.map(splitTableRow);
1070
+ return `
1071
+ <div class="markdown-table-wrap">
1072
+ <table>
1073
+ <thead>
1074
+ <tr>${headers.map((cell) => `<th>${renderInlineMarkdown(cell)}</th>`).join("")}</tr>
1075
+ </thead>
1076
+ <tbody>
1077
+ ${bodyRows
1078
+ .map((row) => `<tr>${headers.map((_header, index) => `<td>${renderInlineMarkdown(row[index] || "")}</td>`).join("")}</tr>`)
1079
+ .join("")}
1080
+ </tbody>
1081
+ </table>
1082
+ </div>
1083
+ `;
1084
+ }
1085
+
1086
+ function isMarkdownBlockStart(line, nextLine = "") {
1087
+ const trimmed = String(line || "").trim();
1088
+ return (
1089
+ /^```/.test(trimmed) ||
1090
+ /^#{1,6}\s+/.test(trimmed) ||
1091
+ /^[-*_]{3,}$/.test(trimmed) ||
1092
+ /^>\s?/.test(trimmed) ||
1093
+ /^[-*+]\s+/.test(trimmed) ||
1094
+ /^\d+\.\s+/.test(trimmed) ||
1095
+ (trimmed.includes("|") && isMarkdownTableSeparator(nextLine))
1096
+ );
1097
+ }
1098
+
1099
+ function renderMarkdown(value) {
1100
+ const lines = String(value || "").replace(/\r\n?/g, "\n").split("\n");
1101
+ const html = [];
1102
+ let paragraph = [];
1103
+ let listType = "";
1104
+ let listItems = [];
1105
+
1106
+ const flushParagraph = () => {
1107
+ if (!paragraph.length) return;
1108
+ html.push(`<p>${renderInlineMarkdown(paragraph.join(" ").trim())}</p>`);
1109
+ paragraph = [];
1110
+ };
1111
+
1112
+ const flushList = () => {
1113
+ if (!listItems.length) return;
1114
+ const tag = listType === "ol" ? "ol" : "ul";
1115
+ html.push(`<${tag}>${listItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join("")}</${tag}>`);
1116
+ listType = "";
1117
+ listItems = [];
1118
+ };
1119
+
1120
+ for (let index = 0; index < lines.length; index += 1) {
1121
+ const line = lines[index];
1122
+ const trimmed = line.trim();
1123
+
1124
+ if (!trimmed) {
1125
+ flushParagraph();
1126
+ flushList();
1127
+ continue;
1128
+ }
1129
+
1130
+ const fence = trimmed.match(/^```([A-Za-z0-9_-]+)?\s*$/);
1131
+ if (fence) {
1132
+ flushParagraph();
1133
+ flushList();
1134
+ const language = fence[1] || "";
1135
+ const code = [];
1136
+ index += 1;
1137
+ while (index < lines.length && !/^```\s*$/.test(lines[index].trim())) {
1138
+ code.push(lines[index]);
1139
+ index += 1;
1140
+ }
1141
+ html.push(
1142
+ `<pre class="markdown-code"><code data-language="${escapeHtml(language)}">${escapeHtml(code.join("\n"))}</code></pre>`
1143
+ );
1144
+ continue;
1145
+ }
1146
+
1147
+ const heading = trimmed.match(/^(#{1,6})\s+(.+?)\s*#*$/);
1148
+ if (heading) {
1149
+ flushParagraph();
1150
+ flushList();
1151
+ const level = heading[1].length;
1152
+ html.push(`<h${level}>${renderInlineMarkdown(heading[2])}</h${level}>`);
1153
+ continue;
1154
+ }
1155
+
1156
+ if (/^[-*_]{3,}$/.test(trimmed)) {
1157
+ flushParagraph();
1158
+ flushList();
1159
+ html.push("<hr>");
1160
+ continue;
1161
+ }
1162
+
1163
+ if (trimmed.includes("|") && isMarkdownTableSeparator(lines[index + 1])) {
1164
+ flushParagraph();
1165
+ flushList();
1166
+ const header = line;
1167
+ const rows = [];
1168
+ index += 2;
1169
+ while (index < lines.length && lines[index].trim().includes("|")) {
1170
+ rows.push(lines[index]);
1171
+ index += 1;
1172
+ }
1173
+ index -= 1;
1174
+ html.push(renderMarkdownTable(header, rows));
1175
+ continue;
1176
+ }
1177
+
1178
+ if (/^>\s?/.test(trimmed)) {
1179
+ flushParagraph();
1180
+ flushList();
1181
+ const quote = [];
1182
+ while (index < lines.length && /^>\s?/.test(lines[index].trim())) {
1183
+ quote.push(lines[index].trim().replace(/^>\s?/, ""));
1184
+ index += 1;
1185
+ }
1186
+ index -= 1;
1187
+ html.push(`<blockquote>${renderMarkdown(quote.join("\n"))}</blockquote>`);
1188
+ continue;
1189
+ }
1190
+
1191
+ const unordered = trimmed.match(/^[-*+]\s+(.+)$/);
1192
+ const ordered = trimmed.match(/^\d+\.\s+(.+)$/);
1193
+ if (unordered || ordered) {
1194
+ flushParagraph();
1195
+ const nextType = ordered ? "ol" : "ul";
1196
+ if (listType && listType !== nextType) flushList();
1197
+ listType = nextType;
1198
+ listItems.push((unordered || ordered)[1]);
1199
+ continue;
1200
+ }
1201
+
1202
+ if (listItems.length) flushList();
1203
+ paragraph.push(trimmed);
1204
+ if (index + 1 >= lines.length || isMarkdownBlockStart(lines[index + 1], lines[index + 2])) flushParagraph();
1205
+ }
1206
+
1207
+ flushParagraph();
1208
+ flushList();
1209
+ return html.join("");
1210
+ }
1211
+
1017
1212
  function renderChat(chatEntries) {
1018
1213
  lastChatEntries = chatEntries || [];
1019
1214
 
@@ -1026,7 +1221,10 @@ function renderChat(chatEntries) {
1026
1221
  .map((entry) => {
1027
1222
  const role = entry.role === "assistant" ? "assistant" : "user";
1028
1223
  const label = role === "assistant" ? t("assistantLabel") : t("youLabel");
1029
- const content = escapeHtml(entry.content).replace(/\n/g, "<br>");
1224
+ const content =
1225
+ role === "assistant"
1226
+ ? `<div class="markdown-body">${renderMarkdown(entry.content)}</div>`
1227
+ : escapeHtml(entry.content).replace(/\n/g, "<br>");
1030
1228
  return `
1031
1229
  <article class="chat-item ${role}">
1032
1230
  <div class="chat-meta">${label}${entry.at ? ` · ${new Date(entry.at).toLocaleString()}` : ""}</div>
@@ -1200,6 +1398,11 @@ function renderArtifactContent(content) {
1200
1398
  }
1201
1399
 
1202
1400
  const text = typeof content.text === "string" ? content.text : "";
1401
+ if (content.kind === "markdown" || /markdown/i.test(content.mime || "")) {
1402
+ artifactViewerBodyEl.innerHTML = `<div class="artifact-markdown markdown-body">${renderMarkdown(text)}</div>`;
1403
+ return;
1404
+ }
1405
+
1203
1406
  artifactViewerBodyEl.innerHTML = `
1204
1407
  <textarea class="artifact-editor" readonly spellcheck="false">${escapeHtml(text)}</textarea>
1205
1408
  `;
package/public/styles.css CHANGED
@@ -587,6 +587,139 @@ button.danger {
587
587
  overflow-wrap: anywhere;
588
588
  }
589
589
 
590
+ .markdown-body {
591
+ line-height: 1.58;
592
+ }
593
+
594
+ .markdown-body > :first-child {
595
+ margin-top: 0;
596
+ }
597
+
598
+ .markdown-body > :last-child {
599
+ margin-bottom: 0;
600
+ }
601
+
602
+ .markdown-body h1,
603
+ .markdown-body h2,
604
+ .markdown-body h3,
605
+ .markdown-body h4,
606
+ .markdown-body h5,
607
+ .markdown-body h6 {
608
+ margin: 0.92em 0 0.42em;
609
+ color: var(--ink);
610
+ line-height: 1.15;
611
+ }
612
+
613
+ .markdown-body h1 {
614
+ font-size: 1.38rem;
615
+ }
616
+
617
+ .markdown-body h2 {
618
+ font-size: 1.22rem;
619
+ }
620
+
621
+ .markdown-body h3 {
622
+ font-size: 1.08rem;
623
+ }
624
+
625
+ .markdown-body p,
626
+ .markdown-body ul,
627
+ .markdown-body ol,
628
+ .markdown-body blockquote,
629
+ .markdown-body pre,
630
+ .markdown-table-wrap {
631
+ margin: 0 0 0.82em;
632
+ }
633
+
634
+ .markdown-body ul,
635
+ .markdown-body ol {
636
+ padding-left: 1.35rem;
637
+ }
638
+
639
+ .markdown-body li + li {
640
+ margin-top: 0.28em;
641
+ }
642
+
643
+ .markdown-body a {
644
+ color: #0f766e;
645
+ font-weight: 800;
646
+ text-decoration-thickness: 0.08em;
647
+ text-underline-offset: 0.16em;
648
+ }
649
+
650
+ .markdown-body code {
651
+ padding: 0.12em 0.36em;
652
+ border: 1px solid rgba(15, 118, 110, 0.16);
653
+ border-radius: 7px;
654
+ background: rgba(15, 23, 42, 0.06);
655
+ color: #0f172a;
656
+ font-size: 0.92em;
657
+ }
658
+
659
+ .markdown-body pre {
660
+ max-width: 100%;
661
+ padding: 12px;
662
+ border: 1px solid rgba(15, 118, 110, 0.18);
663
+ border-radius: 14px;
664
+ background: #111827;
665
+ color: #f8fafc;
666
+ overflow: auto;
667
+ white-space: pre;
668
+ }
669
+
670
+ .markdown-body pre code {
671
+ padding: 0;
672
+ border: 0;
673
+ background: transparent;
674
+ color: inherit;
675
+ }
676
+
677
+ .markdown-body blockquote {
678
+ padding: 0.2rem 0 0.2rem 0.9rem;
679
+ border-left: 4px solid rgba(15, 118, 110, 0.32);
680
+ color: var(--muted);
681
+ }
682
+
683
+ .markdown-body hr {
684
+ height: 1px;
685
+ margin: 1rem 0;
686
+ border: 0;
687
+ background: var(--line);
688
+ }
689
+
690
+ .markdown-table-wrap {
691
+ max-width: 100%;
692
+ overflow: auto;
693
+ }
694
+
695
+ .markdown-body table {
696
+ width: 100%;
697
+ min-width: 420px;
698
+ border-collapse: collapse;
699
+ font-size: 0.92rem;
700
+ }
701
+
702
+ .markdown-body th,
703
+ .markdown-body td {
704
+ padding: 8px 10px;
705
+ border: 1px solid rgba(15, 118, 110, 0.16);
706
+ text-align: left;
707
+ vertical-align: top;
708
+ }
709
+
710
+ .markdown-body th {
711
+ background: rgba(15, 118, 110, 0.08);
712
+ }
713
+
714
+ .artifact-markdown {
715
+ min-height: 100%;
716
+ padding: 14px;
717
+ border: 1px solid rgba(15, 118, 110, 0.14);
718
+ border-radius: 16px;
719
+ background: rgba(255, 255, 255, 0.72);
720
+ overflow: auto;
721
+ }
722
+
590
723
  .chat-form {
591
724
  display: grid;
592
725
  gap: 8px;