-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reverted to edit 8b3959ef-92d3-4cfe-a9ed-1d26d2244f9d: "feat: add dyn…
…amic page titles based on selected conversation"
- Loading branch information
1 parent
f544d3c
commit 1e35120
Showing
1 changed file
with
106 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,75 +1,120 @@ | ||
import { marked } from "marked"; | ||
import hljs from "highlight.js"; | ||
import { markedHighlight } from "marked-highlight"; | ||
import type { Link } from "marked"; | ||
|
||
marked.use(markedHighlight({ | ||
highlight: (code, lang) => { | ||
if (lang && hljs.getLanguage(lang)) { | ||
try { | ||
return hljs.highlight(code, { language: lang }).value; | ||
} catch (err) { | ||
console.error("Error highlighting code:", err); | ||
} | ||
} | ||
return code; // Use the original code if language isn't found | ||
} | ||
})); | ||
|
||
const renderer = new marked.Renderer(); | ||
|
||
// Store the original link renderer | ||
const originalLinkRenderer = renderer.link.bind(renderer); | ||
|
||
// Customize the link renderer to add icons | ||
renderer.link = ({ href, title, text }: Link) => { | ||
if (!href) return text; | ||
|
||
const linkHtml = originalLinkRenderer({ href, title, text }); | ||
|
||
let iconSvg = ''; | ||
|
||
if (href.includes('github.com')) { | ||
// GitHub icon | ||
iconSvg = '<svg class="inline-block ml-1 w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>'; | ||
} else if (href.includes('wikipedia.org')) { | ||
// Simple "W" icon for Wikipedia | ||
iconSvg = '<span class="inline-flex items-center justify-center ml-1 w-4 h-4 text-xs font-bold bg-gray-200 dark:bg-gray-700 rounded-full">W</span>'; | ||
} else { | ||
// Generic external link icon for other links | ||
iconSvg = '<svg class="inline-block ml-1 w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>'; | ||
import hljs from "highlight.js"; | ||
|
||
marked.setOptions({ | ||
gfm: true, | ||
breaks: true, | ||
silent: true, | ||
}); | ||
|
||
marked.use( | ||
markedHighlight({ | ||
langPrefix: "hljs language-", | ||
highlight(code, lang, info) { | ||
lang = info.split(".").pop() || lang; | ||
if (lang == "shell") lang = "bash"; | ||
if (lang == "result") lang = "markdown"; | ||
const language = hljs.getLanguage(lang) ? lang : "plaintext"; | ||
return hljs.highlight(code, { language }).value; | ||
}, | ||
}) | ||
); | ||
|
||
export function processNestedCodeBlocks(content: string) { | ||
// If no code blocks or only one code block, return as-is | ||
if (content.split('```').length < 3) { | ||
const match = content.match(/```(\S*)/); | ||
return { | ||
processedContent: content, | ||
langtags: match ? [match[1]] : [] | ||
}; | ||
} | ||
|
||
// Insert the icon after the link | ||
return linkHtml.slice(0, -4) + iconSvg + '</a>'; | ||
}; | ||
|
||
export function processNestedCodeBlocks(content: string): { processedContent: string; langtags: string[] } { | ||
const lines = content.split('\n'); | ||
const langtags: string[] = []; | ||
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g; | ||
let match; | ||
let lastIndex = 0; | ||
let processedContent = ''; | ||
|
||
while ((match = codeBlockRegex.exec(content)) !== null) { | ||
const [fullMatch, lang] = match; | ||
if (lang) langtags.push(lang); | ||
processedContent += content.slice(lastIndex, match.index) + fullMatch; | ||
lastIndex = match.index + fullMatch.length; | ||
const result: string[] = []; | ||
|
||
for (let i = 0; i < lines.length; i++) { | ||
const line = lines[i]; | ||
const strippedLine = line.trim(); | ||
|
||
if (strippedLine.startsWith('```')) { | ||
if (strippedLine !== '```') { | ||
// Start of a code block with a language | ||
const lang = strippedLine.slice(3); | ||
langtags.push(lang); | ||
} | ||
result.push(line); | ||
} else { | ||
result.push(line); | ||
} | ||
} | ||
|
||
processedContent += content.slice(lastIndex); | ||
return { processedContent, langtags }; | ||
return { | ||
processedContent: result.join('\n'), | ||
langtags: langtags.filter(Boolean) | ||
}; | ||
} | ||
|
||
export function transformThinkingTags(content: string): string { | ||
export function transformThinkingTags(content: string) { | ||
if (content.startsWith('`') && content.endsWith('`')) { | ||
return content; | ||
} | ||
|
||
return content.replace( | ||
/<thinking>([\s\S]*?)<\/thinking>/g, | ||
'<details><summary>💭 Thinking</summary>\n\n$1\n\n</details>' | ||
(_match: string, thinkingContent: string) => | ||
`<details><summary>💭 Thinking</summary>\n\n${thinkingContent}\n\n</details>` | ||
); | ||
} | ||
|
||
export function parseMarkdownContent(content: string) { | ||
const processedContent = transformThinkingTags(content); | ||
const { processedContent: transformedContent, langtags } = processNestedCodeBlocks(processedContent); | ||
|
||
let parsedResult = marked.parse(transformedContent, { | ||
async: false, | ||
}); | ||
|
||
parsedResult = parsedResult.replace( | ||
/<pre><code(?:\s+class="([^"]+)")?>([^]*?)<\/code><\/pre>/g, | ||
(_, classes = "", code) => { | ||
const langtag_fallback = ((classes || "").split(" ")[1] || "Code").replace("language-", ""); | ||
const langtag = langtags?.shift() || langtag_fallback; | ||
const emoji = getCodeBlockEmoji(langtag); | ||
return ` | ||
<details> | ||
<summary>${emoji} ${langtag}</summary> | ||
<pre><code class="${classes}">${code}</code></pre> | ||
</details> | ||
`; | ||
} | ||
); | ||
|
||
return parsedResult; | ||
} | ||
|
||
function getCodeBlockEmoji(langtag: string): string { | ||
if (isPath(langtag)) return "📄"; | ||
if (isTool(langtag)) return "🛠️"; | ||
if (isOutput(langtag)) return "📤"; | ||
if (isWrite(langtag)) return "📝"; | ||
return "💻"; | ||
} | ||
|
||
export function parseMarkdownContent(content: string): string { | ||
const transformedContent = transformThinkingTags(content); | ||
return marked(transformedContent, { renderer }); | ||
} | ||
function isPath(langtag: string): boolean { | ||
return (langtag.includes("/") || langtag.includes("\\") || langtag.includes(".")) && langtag.split(" ").length === 1; | ||
} | ||
|
||
function isTool(langtag: string): boolean { | ||
return ["ipython", "shell", "tmux"].includes(langtag.split(" ")[0].toLowerCase()); | ||
} | ||
|
||
function isOutput(langtag: string): boolean { | ||
return ["stdout", "stderr", "result"].includes(langtag.toLowerCase()); | ||
} | ||
|
||
function isWrite(langtag: string): boolean { | ||
return ["save", "patch", "append"].includes(langtag.split(" ")[0].toLowerCase()); | ||
} |