Open bot files in a preview modal instead of a new tab
Markdown rendered, CSV/TSV as a table, images, PDFs, audio, video and sandboxed HTML in place, other text as code, with Download and Open in a new tab. Used by file chips, message attachments, the files modal, task files and the files browser. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import Link from "@moduletrace/twui/layout/Link";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import fetchApi from "@moduletrace/twui/utils/fetch/fetchApi";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import type { BotFileScopesPayload, MediaType, PublicBotFile } from "@/src/types";
|
||||
import FilePreviewLink from "@/src/components/general/file-preview-link";
|
||||
import AttachmentIcon from "./attachment-icon";
|
||||
|
||||
type Props = {
|
||||
@@ -50,8 +50,8 @@ function fetchFile({
|
||||
|
||||
/**
|
||||
* A file a bot named in a message (`fileRef`): the name it is saved under,
|
||||
* linked to its bytes at `/media/<id>` (private: only someone who reaches
|
||||
* the file's folder can load it). A folder the bot named is a `folderRef`
|
||||
* opening it in the file preview (its bytes at `/media/<id>`, private: only
|
||||
* someone who reaches the file's folder can load them). A folder the bot named is a `folderRef`
|
||||
* instead, and its chip opens the files browser. The conversation's files come with the page, so most
|
||||
* chips need no request; one that isn't among them (older than the page's
|
||||
* first page) is fetched by id. A file the user doesn't reach, or one
|
||||
@@ -85,15 +85,15 @@ export default function FileRefChip({ file_id }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={`Open ${file.name}`}
|
||||
<FilePreviewLink
|
||||
name={file.name}
|
||||
url={file.url}
|
||||
media_type={file.media_type}
|
||||
size={file.size}
|
||||
className="inline-flex h-6 max-w-full gap-1.5 items-center px-1.5 align-middle rounded-default-xs border-0 bg-sunken text-[13px] font-bold leading-4 text-ink hover:opacity-80"
|
||||
>
|
||||
<AttachmentIcon media_type={file.media_type as MediaType} />
|
||||
<Span className="truncate">{file.name}</Span>
|
||||
</Link>
|
||||
</FilePreviewLink>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Link from "@moduletrace/twui/layout/Link";
|
||||
import Row from "@moduletrace/twui/layout/Row";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import Stack from "@moduletrace/twui/layout/Stack";
|
||||
@@ -6,6 +5,7 @@ import Button from "@moduletrace/twui/layout/Button";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import type { MediaType, PublicBotFile } from "@/src/types";
|
||||
import relativeTime from "@/src/utils/relative-time";
|
||||
import FilePreviewLink from "@/src/components/general/file-preview-link";
|
||||
import AttachmentIcon from "./attachment-icon";
|
||||
|
||||
type Props = {
|
||||
@@ -29,15 +29,15 @@ export default function FileRow({ file, can_manage, busy, onDelete }: Props) {
|
||||
<AttachmentIcon media_type={file.media_type as MediaType} size={16} />
|
||||
</Row>
|
||||
<Stack className="min-w-0 flex-1 gap-0.5!">
|
||||
<Link
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={`Open ${file.name}`}
|
||||
<FilePreviewLink
|
||||
name={file.path || file.name}
|
||||
url={file.url}
|
||||
media_type={file.media_type}
|
||||
size={file.size}
|
||||
className="text-[13px] font-bold leading-5 text-ink break-words hover:opacity-80"
|
||||
>
|
||||
{file.path || file.name}
|
||||
</Link>
|
||||
</FilePreviewLink>
|
||||
<Span className="text-[12px] leading-4 text-ink-secondary">
|
||||
{[
|
||||
file.scope == "workspace" ? "Workspace" : "Bot files",
|
||||
|
||||
@@ -34,7 +34,7 @@ import WorkspaceRefChip from "./workspace-ref-chip";
|
||||
* A link in a bot message. A computer the bot named (`linkComputerRefs`)
|
||||
* becomes its switch button, a take-over request a button that opens the
|
||||
* computer, a bot it named a link to that bot's conversation, a workspace
|
||||
* a link to that workspace, a file it saved a chip that downloads it, a
|
||||
* a link to that workspace, a file it saved a chip that previews it, a
|
||||
* folder it named a chip that opens it in the files browser, an MCP server,
|
||||
* tool, skill or hook it proposed a chip that opens the confirm card, a
|
||||
* secret it showed a masked chip, a secret it asked for a chip that opens
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Img from "@moduletrace/twui/layout/Img";
|
||||
import Link from "@moduletrace/twui/layout/Link";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import type { BotMessageAttachment } from "@/src/types";
|
||||
import FilePreviewLink from "@/src/components/general/file-preview-link";
|
||||
import AttachmentIcon from "./attachment-icon";
|
||||
|
||||
/**
|
||||
* One file on a message: an image as a thumbnail, anything else as a
|
||||
* named chip. Both open the file in a new tab.
|
||||
* named chip. Both open the file in the preview.
|
||||
*/
|
||||
export default function MessageAttachment({
|
||||
name,
|
||||
@@ -16,25 +16,26 @@ export default function MessageAttachment({
|
||||
}: BotMessageAttachment) {
|
||||
if (media_type == "image") {
|
||||
return (
|
||||
<Link href={url} target="_blank" title={name} className="block">
|
||||
<FilePreviewLink name={name} url={url} media_type={media_type} title={name} className="block">
|
||||
<Img
|
||||
src={thumbnail_url || url}
|
||||
alt={name}
|
||||
className="max-h-48 max-w-64 rounded-default-lg border border-solid border-hairline object-cover"
|
||||
/>
|
||||
</Link>
|
||||
</FilePreviewLink>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={url}
|
||||
target="_blank"
|
||||
<FilePreviewLink
|
||||
name={name}
|
||||
url={url}
|
||||
media_type={media_type}
|
||||
title={name}
|
||||
className="flex max-w-64 items-center gap-2 rounded-default-lg border border-solid border-hairline bg-surface px-3 py-2 text-[13px] leading-[18px] text-ink no-underline hover:border-hairline-strong"
|
||||
>
|
||||
<AttachmentIcon media_type={media_type} />
|
||||
<Span className="truncate">{name}</Span>
|
||||
</Link>
|
||||
</FilePreviewLink>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { MediaType, StorageEntry } from "@/src/types";
|
||||
import formatBytes from "@/src/utils/format-bytes";
|
||||
import relativeTime from "@/src/utils/relative-time";
|
||||
import AttachmentIcon from "@/src/pages/admin/bots/[bot_id]/(partials)/attachment-icon";
|
||||
import FilePreviewLink from "@/src/components/general/file-preview-link";
|
||||
|
||||
type Props = {
|
||||
entry: StorageEntry;
|
||||
@@ -16,8 +17,8 @@ type Props = {
|
||||
};
|
||||
|
||||
/**
|
||||
* One entry in the open folder: a folder opens in place, a file opens in a
|
||||
* new tab (its own media record when it is one of the bot's files, else
|
||||
* One entry in the open folder: a folder opens in place, a file opens in the
|
||||
* file preview (its own media record when it is one of the bot's files, else
|
||||
* the browser's streaming route).
|
||||
*/
|
||||
export default function StorageEntryRow({ entry, selected, onOpenFolder }: Props) {
|
||||
@@ -51,15 +52,15 @@ export default function StorageEntryRow({ entry, selected, onOpenFolder }: Props
|
||||
{entry.name}
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
href={entry.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={`Open ${entry.name}`}
|
||||
<FilePreviewLink
|
||||
name={entry.name}
|
||||
url={entry.url || ""}
|
||||
media_type={entry.media_type}
|
||||
size={entry.size}
|
||||
className="text-[13px] font-bold leading-5 text-ink break-words hover:opacity-80"
|
||||
>
|
||||
{entry.name}
|
||||
</Link>
|
||||
</FilePreviewLink>
|
||||
)}
|
||||
<Span className="text-[12px] leading-4 text-ink-secondary">
|
||||
{[
|
||||
|
||||
Reference in New Issue
Block a user