Enable thread file attachments

This commit is contained in:
ZacharyZhang-NY committed 2026-06-07 13:45:45 -04:00
1 parent d5a7665893
commit 91d9b76281
2 files changed
+54 -7

No files matched your search

@@ -69,6 +69,7 @@ export function ChatPage({ snapshot, actions }: PageProps) {
const [openThreadId, setOpenThreadId] = useState<string | null>(null); const [openThreadId, setOpenThreadId] = useState<string | null>(null);
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const [attachmentFileId, setAttachmentFileId] = useState<string>(""); const [attachmentFileId, setAttachmentFileId] = useState<string>("");
const [threadAttachmentFileId, setThreadAttachmentFileId] = useState<string>("");
const composerRef = useRef<HTMLTextAreaElement | null>(null); const composerRef = useRef<HTMLTextAreaElement | null>(null);
const firstAvailableSpaceId = channels[0]?.id ?? directs[0]?.id ?? null; const firstAvailableSpaceId = channels[0]?.id ?? directs[0]?.id ?? null;
@@ -119,6 +120,16 @@ export function ChatPage({ snapshot, actions }: PageProps) {
); );
const pendingApprovalsInSpace = filterApprovalsForSpace(approvalsAll, activeSpace); const pendingApprovalsInSpace = filterApprovalsForSpace(approvalsAll, activeSpace);
const openThread = (threadId: string) => {
setOpenThreadId(threadId);
setThreadAttachmentFileId("");
};
const closeThread = () => {
setOpenThreadId(null);
setThreadAttachmentFileId("");
};
const sendInChannel = (event: FormEvent<HTMLFormElement>) => { const sendInChannel = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
if (!activeSpace) return; if (!activeSpace) return;
@@ -145,6 +156,7 @@ export function ChatPage({ snapshot, actions }: PageProps) {
const body = value(form, "message"); const body = value(form, "message");
if (!body) return; if (!body) return;
event.currentTarget.reset(); event.currentTarget.reset();
setThreadAttachmentFileId("");
void actions.run(() => window.agentCompany.sendMessage({ void actions.run(() => window.agentCompany.sendMessage({
orgId, orgId,
spaceId: activeSpace.id, spaceId: activeSpace.id,
@@ -152,7 +164,7 @@ export function ChatPage({ snapshot, actions }: PageProps) {
senderType: "employee", senderType: "employee",
senderId: employee.id, senderId: employee.id,
bodyMd: body, bodyMd: body,
attachmentFileIds: [], attachmentFileIds: threadAttachmentFileId ? [threadAttachmentFileId] : [],
})); }));
}; };
@@ -225,9 +237,17 @@ export function ChatPage({ snapshot, actions }: PageProps) {
})); }));
const attachFile = () => { const attachFile = () => {
importAttachment(setAttachmentFileId);
};
const attachThreadFile = () => {
importAttachment(setThreadAttachmentFileId);
};
const importAttachment = (onSelect: (fileId: string) => void) => {
void window.agentCompany.importFile({ orgId }).then((result) => { void window.agentCompany.importFile({ orgId }).then((result) => {
if (!result.ok) throw new Error(result.error.message); if (!result.ok) throw new Error(result.error.message);
if (result.data) setAttachmentFileId(result.data); if (result.data) onSelect(result.data);
}); });
}; };
@@ -248,7 +268,7 @@ export function ChatPage({ snapshot, actions }: PageProps) {
activeSpaceId={activeSpaceId} activeSpaceId={activeSpaceId}
onSelect={(spaceId) => { onSelect={(spaceId) => {
setSelectedSpaceId(spaceId); setSelectedSpaceId(spaceId);
setOpenThreadId(null); closeThread();
}} }}
onCreateSpace={() => setCreateOpen(true)} onCreateSpace={() => setCreateOpen(true)}
/> />
@@ -269,7 +289,7 @@ export function ChatPage({ snapshot, actions }: PageProps) {
currentEmployeeId={employee.id} currentEmployeeId={employee.id}
onAttachmentChange={setAttachmentFileId} onAttachmentChange={setAttachmentFileId}
onAttach={attachFile} onAttach={attachFile}
onOpenThread={setOpenThreadId} onOpenThread={openThread}
onApprove={(approvalId) => decideApproval(approvalId, "approved")} onApprove={(approvalId) => decideApproval(approvalId, "approved")}
onDecline={(approvalId) => decideApproval(approvalId, "declined")} onDecline={(approvalId) => decideApproval(approvalId, "declined")}
onEditMessage={editChatMessage} onEditMessage={editChatMessage}
@@ -292,7 +312,10 @@ export function ChatPage({ snapshot, actions }: PageProps) {
auditEvents={threadAuditEvents} auditEvents={threadAuditEvents}
context={{ employees, agents }} context={{ employees, agents }}
canIntervene={canBoardIntervene} canIntervene={canBoardIntervene}
onClose={() => setOpenThreadId(null)} attachmentFileId={threadAttachmentFileId}
onClose={closeThread}
onAttachmentChange={setThreadAttachmentFileId}
onAttach={attachThreadFile}
onSubmit={sendInThread} onSubmit={sendInThread}
onBoardSubmit={sendBoardIntervention} onBoardSubmit={sendBoardIntervention}
onOpenFile={openFile} onOpenFile={openFile}
@@ -26,7 +26,10 @@ export function ChatThreadPanel({
auditEvents, auditEvents,
context, context,
canIntervene, canIntervene,
attachmentFileId,
onClose, onClose,
onAttachmentChange,
onAttach,
onSubmit, onSubmit,
onBoardSubmit, onBoardSubmit,
onOpenFile, onOpenFile,
@@ -42,11 +45,15 @@ export function ChatThreadPanel({
auditEvents: AuditEventRecord[]; auditEvents: AuditEventRecord[];
context: MessageContext; context: MessageContext;
canIntervene: boolean; canIntervene: boolean;
attachmentFileId: string;
onClose: () => void; onClose: () => void;
onAttachmentChange: (next: string) => void;
onAttach: () => void;
onSubmit: (event: FormEvent<HTMLFormElement>) => void; onSubmit: (event: FormEvent<HTMLFormElement>) => void;
onBoardSubmit: (event: FormEvent<HTMLFormElement>) => void; onBoardSubmit: (event: FormEvent<HTMLFormElement>) => void;
onOpenFile: (fileId: string) => void; onOpenFile: (fileId: string) => void;
}) { }) {
const attachment = files.find((file) => file.id === attachmentFileId) ?? null;
return ( return (
<aside <aside
style={{ style={{
@@ -126,8 +133,25 @@ export function ChatThreadPanel({
} }
}} }}
/> />
<div className="row" style={{ paddingTop: 6, justifyContent: "flex-end" }}> <div className="row" style={{ paddingTop: 6, gap: 4 }}>
<Button tone="primary" size="small" type="submit"><Icon name="send" size={12} /> Reply</Button> <IconButton icon="attach" label="Attach file" onClick={onAttach} />
{attachment && (
<span className="pill" style={{ maxWidth: 150 }}>
<Icon name="file" size={11} />
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{attachment.name}</span>
<button
type="button"
aria-label="Remove thread attachment"
onClick={() => onAttachmentChange("")}
style={{ display: "inline-flex", border: 0, background: "transparent", padding: 0, color: "var(--text-3)", cursor: "pointer" }}
>
<Icon name="x" size={11} />
</button>
</span>
)}
<div style={{ marginLeft: "auto" }}>
<Button tone="primary" size="small" type="submit"><Icon name="send" size={12} /> Reply</Button>
</div>
</div> </div>
</div> </div>
</form> </form>