linkify file paths in chat, open via /open endpoint; proxy /open in dev
This commit is contained in:
parent
1648d99178
commit
1c180de2e4
|
|
@ -50,6 +50,19 @@ function escapeHtml(s: string) {
|
||||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function linkifyPaths(html: string, cwd: string): string {
|
||||||
|
return html.replace(/<code>([^<]+)<\/code>/g, (_match, inner: string) => {
|
||||||
|
// Match: has a file extension, or contains a /
|
||||||
|
if (!/\.\w+/.test(inner) && !inner.includes('/')) return _match
|
||||||
|
// Exclude things that are clearly not paths (spaces, parens, etc.)
|
||||||
|
if (/[\s(){}[\]|;]/.test(inner)) return _match
|
||||||
|
const filePart = inner.split(':')[0]
|
||||||
|
const href = filePart.startsWith('/') ? `file://${filePart}` : `file://${cwd}/${filePart}`
|
||||||
|
const fullPath = filePart.startsWith('/') ? filePart : cwd + '/' + filePart
|
||||||
|
return `<code><a class="file-link" href="#" data-fpath="${escapeHtml(fullPath)}">${inner}</a></code>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
marked.use({ gfm: true, breaks: false, renderer })
|
marked.use({ gfm: true, breaks: false, renderer })
|
||||||
|
|
||||||
// Display math: $$...$$ as a block or inline paragraph
|
// Display math: $$...$$ as a block or inline paragraph
|
||||||
|
|
@ -132,6 +145,16 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
||||||
const handleDiagramClick = (e: MouseEvent) => {
|
const handleDiagramClick = (e: MouseEvent) => {
|
||||||
const target = e.target as HTMLElement
|
const target = e.target as HTMLElement
|
||||||
|
|
||||||
|
// File path links — open in editor
|
||||||
|
const link = target.closest('.file-link') as HTMLElement | null
|
||||||
|
if (link) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
const fp = link.getAttribute('data-fpath')
|
||||||
|
if (fp) fetch('/open?path=' + encodeURIComponent(fp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Toggle button: switch between rendered and source
|
// Toggle button: switch between rendered and source
|
||||||
const btn = target.closest('.diagram-toggle') as HTMLElement | null
|
const btn = target.closest('.diagram-toggle') as HTMLElement | null
|
||||||
if (btn) {
|
if (btn) {
|
||||||
|
|
@ -224,7 +247,7 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
class="md"
|
class="md"
|
||||||
dangerouslySetInnerHTML={{ __html: (marked.parse(msg.content) as string).replace(/^<p>/, '<p><span class="msg-prefix">a: </span>') }}
|
dangerouslySetInnerHTML={{ __html: linkifyPaths((marked.parse(msg.content) as string).replace(/^<p>/, '<p><span class="msg-prefix">a: </span>'), session.cwd) }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -878,6 +878,12 @@ details[open] > .tool-header::before {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
.file-link {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.file-link:hover { opacity: 0.8; }
|
||||||
.fs-editor {
|
.fs-editor {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ export default defineConfig({
|
||||||
'/t/': 'http://localhost:8080',
|
'/t/': 'http://localhost:8080',
|
||||||
'/sk/': 'http://localhost:8080',
|
'/sk/': 'http://localhost:8080',
|
||||||
'/backends': 'http://localhost:8080',
|
'/backends': 'http://localhost:8080',
|
||||||
|
'/open': 'http://localhost:8080',
|
||||||
'/openapi.json': 'http://localhost:8080',
|
'/openapi.json': 'http://localhost:8080',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Reference in New Issue