main
mula.liu 2026-03-11 03:09:41 +08:00
parent 76a2ab49fe
commit b0e8cd6514
1 changed files with 61 additions and 33 deletions

View File

@ -395,9 +395,10 @@ function workspaceFileAction(path: string): 'preview' | 'download' | 'unsupporte
} }
const WORKSPACE_LINK_PREFIX = 'https://workspace.local/open/'; const WORKSPACE_LINK_PREFIX = 'https://workspace.local/open/';
const WORKSPACE_ABS_PATH_PATTERN = /\/root\/\.nanobot\/workspace\/[^\s<>"'`)\],,。!?;:]+/gi; const WORKSPACE_ABS_PATH_PATTERN =
/\/root\/\.nanobot\/workspace\/[^\n\r<>"'`]+?\.(?:md|markdown|json|txt|log|csv|tsv|yaml|yml|toml|html|htm|pdf|png|jpg|jpeg|gif|webp|svg|mp3|wav|m4a|flac|ogg|opus|aac|amr|wma|mp4|mov|avi|mkv|webm|m4v|3gp|mpeg|mpg|ts|doc|docx|xls|xlsx|xlsm|ppt|pptx|odt|ods|odp|wps)\b/gi;
const WORKSPACE_RELATIVE_PATH_PATTERN = const WORKSPACE_RELATIVE_PATH_PATTERN =
/(^|[\s(\[])(\/[^\s<>"'`)\]]+\.(?:md|markdown|json|txt|log|csv|tsv|yaml|yml|toml|html|htm|pdf|png|jpg|jpeg|gif|webp|svg))(?![A-Za-z0-9_./-])/gim; /(^|[\s(\[])(\/[^\n\r<>"'`)\]]+?\.(?:md|markdown|json|txt|log|csv|tsv|yaml|yml|toml|html|htm|pdf|png|jpg|jpeg|gif|webp|svg|mp3|wav|m4a|flac|ogg|opus|aac|amr|wma|mp4|mov|avi|mkv|webm|m4v|3gp|mpeg|mpg|ts|doc|docx|xls|xlsx|xlsm|ppt|pptx|odt|ods|odp|wps))(?![A-Za-z0-9_./-])/gim;
function buildWorkspaceLink(path: string) { function buildWorkspaceLink(path: string) {
return `${WORKSPACE_LINK_PREFIX}${encodeURIComponent(path)}`; return `${WORKSPACE_LINK_PREFIX}${encodeURIComponent(path)}`;
@ -415,28 +416,55 @@ function parseWorkspaceLink(href: string): string | null {
} }
} }
function decorateWorkspacePathsForMarkdown(text: string) { function decorateWorkspacePathsInPlainChunk(source: string): string {
const source = String(text || ''); if (!source) return source;
const normalizedExistingLinks = source.replace( const protectedLinks: string[] = [];
/\[(\/root\/\.nanobot\/workspace\/[^\]]+)\]\s*\n?\s*\((https:\/\/workspace\.local\/open\/[^)\r\n]*)\)/gi, const withProtectedAbsoluteLinks = source.replace(WORKSPACE_ABS_PATH_PATTERN, (fullPath) => {
(_full, markdownPath: string) => {
const normalized = normalizeDashboardAttachmentPath(markdownPath);
if (!normalized) return String(_full || '');
return `[${markdownPath}](${buildWorkspaceLink(normalized)})`;
},
);
const withAbsoluteLinks = normalizedExistingLinks.replace(WORKSPACE_ABS_PATH_PATTERN, (fullPath) => {
const normalized = normalizeDashboardAttachmentPath(fullPath); const normalized = normalizeDashboardAttachmentPath(fullPath);
if (!normalized) return fullPath; if (!normalized) return fullPath;
return `[${fullPath}](${buildWorkspaceLink(normalized)})`; const token = `@@WS_PATH_LINK_${protectedLinks.length}@@`;
protectedLinks.push(`[${fullPath}](${buildWorkspaceLink(normalized)})`);
return token;
}); });
return withAbsoluteLinks.replace(WORKSPACE_RELATIVE_PATH_PATTERN, (full, prefix: string, rawPath: string) => { const withRelativeLinks = withProtectedAbsoluteLinks.replace(
const normalized = normalizeDashboardAttachmentPath(rawPath); WORKSPACE_RELATIVE_PATH_PATTERN,
if (!normalized) return full; (full, prefix: string, rawPath: string) => {
return `${prefix}[${rawPath}](${buildWorkspaceLink(normalized)})`; const normalized = normalizeDashboardAttachmentPath(rawPath);
if (!normalized) return full;
return `${prefix}[${rawPath}](${buildWorkspaceLink(normalized)})`;
},
);
return withRelativeLinks.replace(/@@WS_PATH_LINK_(\d+)@@/g, (_full, idxRaw: string) => {
const idx = Number(idxRaw);
if (!Number.isFinite(idx) || idx < 0 || idx >= protectedLinks.length) return String(_full || '');
return protectedLinks[idx];
}); });
} }
function decorateWorkspacePathsForMarkdown(text: string) {
const source = String(text || '');
if (!source) return source;
// Keep existing Markdown links unchanged; only decorate plain text segments.
const markdownLinkPattern = /\[[^\]]*?\]\((?:[^)(]|\([^)(]*\))*\)/g;
let result = '';
let last = 0;
let match = markdownLinkPattern.exec(source);
while (match) {
const idx = Number(match.index || 0);
if (idx > last) {
result += decorateWorkspacePathsInPlainChunk(source.slice(last, idx));
}
result += match[0];
last = idx + match[0].length;
match = markdownLinkPattern.exec(source);
}
if (last < source.length) {
result += decorateWorkspacePathsInPlainChunk(source.slice(last));
}
return result;
}
function normalizeAttachmentPaths(raw: unknown): string[] { function normalizeAttachmentPaths(raw: unknown): string[] {
if (!Array.isArray(raw)) return []; if (!Array.isArray(raw)) return [];
return raw return raw
@ -3117,6 +3145,21 @@ export function BotDashboardModule({
) : null} ) : null}
</div> </div>
) : null} ) : null}
{isUploadingAttachments ? (
<div className="ops-upload-progress" aria-live="polite">
<div className={`ops-upload-progress-track ${attachmentUploadPercent === null ? 'is-indeterminate' : ''}`}>
<div
className="ops-upload-progress-fill"
style={{ width: `${Math.max(3, Number(attachmentUploadPercent ?? 24))}%` }}
/>
</div>
<span className="ops-upload-progress-text mono">
{attachmentUploadPercent === null
? t.uploadingFile
: `${t.uploadingFile} ${attachmentUploadPercent}%`}
</span>
</div>
) : null}
<div className="ops-composer"> <div className="ops-composer">
<input <input
ref={filePickerRef} ref={filePickerRef}
@ -3179,21 +3222,6 @@ export function BotDashboardModule({
</div> </div>
</div> </div>
</div> </div>
{isUploadingAttachments ? (
<div className="ops-upload-progress" aria-live="polite">
<div className={`ops-upload-progress-track ${attachmentUploadPercent === null ? 'is-indeterminate' : ''}`}>
<div
className="ops-upload-progress-fill"
style={{ width: `${Math.max(3, Number(attachmentUploadPercent ?? 24))}%` }}
/>
</div>
<span className="ops-upload-progress-text mono">
{attachmentUploadPercent === null
? t.uploadingFile
: `${t.uploadingFile} ${attachmentUploadPercent}%`}
</span>
</div>
) : null}
{!canChat ? ( {!canChat ? (
<div className="ops-chat-disabled-mask"> <div className="ops-chat-disabled-mask">
<div className="ops-chat-disabled-card"> <div className="ops-chat-disabled-card">