fix(desktop): image rendering for Windows paths and MEDIA: prefix

- toFileUrl: normalize backslashes, detect Windows drive-letter paths
- resolveFileUrl: strip MEDIA: prefix from tool results, Windows path support
- handleServe: skip prepending "/" for Windows drive-letter URL paths
This commit is contained in:
viettranx committed 2026-03-27 13:07:14 +07:00
1 parent 0292440c35
commit 2c94e8070f
3 files changed
+26 -10

No files matched your search

+7 -1
View File
@@ -107,7 +107,13 @@ func (h *FilesHandler) handleServe(w http.ResponseWriter, r *http.Request) {
}
// URL path is the absolute path with leading "/" stripped (e.g. "app/.goclaw/workspace/file.png")
absPath := filepath.Clean("/" + urlPath)
// Windows drive letter: "C:/Users/..." → use directly without prepending "/"
var absPath string
if len(urlPath) >= 2 && urlPath[1] == ':' {
absPath = filepath.Clean(urlPath)
} else {
absPath = filepath.Clean("/" + urlPath)
}
// Block access to sensitive system directories
for _, prefix := range deniedFilePrefixes {
@@ -28,13 +28,21 @@ function resolveFileUrl(path: string, baseUrl?: string): string {
if (isApiClientReady()) return getApiClient().getBaseUrl() + path
return path
}
// Strip MEDIA: prefix (tool results embed paths as MEDIA:/path/to/file.png)
const clean = path.replace(/^MEDIA:/, '')
// Normalize backslashes (Windows paths: C:\Users\... → C:/Users/...)
const normalized = clean.replace(/\\/g, '/')
// Relative path with baseUrl — resolve against the base directory
if (baseUrl && !path.startsWith('/')) {
if (baseUrl && !normalized.startsWith('/') && !/^[a-zA-Z]:\//.test(normalized)) {
const baseDir = baseUrl.substring(0, baseUrl.lastIndexOf('/') + 1)
return baseDir + path
return baseDir + normalized
}
// Absolute path — preserve full path for direct file serving
const filePath = path.startsWith('/') ? `/v1/files${path}` : `/v1/files/${path.split('/').pop() ?? path}`
// Absolute path: Unix /... or Windows C:/...
const filePath = normalized.startsWith('/')
? `/v1/files${normalized}`
: /^[a-zA-Z]:\//.test(normalized)
? `/v1/files/${normalized}`
: `/v1/files/${normalized.split('/').pop() ?? normalized}`
if (isApiClientReady()) return getApiClient().getBaseUrl() + filePath
return filePath
}
+7 -5
View File
@@ -10,11 +10,13 @@ import type { AttachedFile } from '../components/chat/InputBar'
// can resolve the file directly without findInWorkspace fallback.
function toFileUrl(path: string): string {
if (!path) return ''
if (path.startsWith('/v1/files/') || path.includes('/v1/files/')) return resolveBase(path)
// Absolute path — preserve full path for direct file serving
if (path.startsWith('/')) return resolveBase(`/v1/files${path}`)
const basename = path.split('/').pop() ?? path
return resolveBase(`/v1/files/${basename}`)
if (path.includes('/v1/files/')) return resolveBase(path)
// Normalize backslashes (Windows paths: C:\Users\... → C:/Users/...)
const normalized = path.replace(/\\/g, '/')
// Absolute: Unix /... or Windows C:/...
if (normalized.startsWith('/')) return resolveBase(`/v1/files${normalized}`)
if (/^[a-zA-Z]:\//.test(normalized)) return resolveBase(`/v1/files/${normalized}`)
return resolveBase(`/v1/files/${normalized.split('/').pop() ?? normalized}`)
}
function resolveBase(path: string): string {