This commit is contained in:
@@ -183,6 +183,8 @@ export interface LocalTaskComment {
|
||||
author: string;
|
||||
kind: CommentKind;
|
||||
body: string;
|
||||
/** Filenames attached to this comment, saved under the task's input/ dir. */
|
||||
attachments?: string[];
|
||||
createdAt: string;
|
||||
injectedAt: string | null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LocalTaskComment } from '../../api';
|
||||
import { LocalTaskComment, getLocalFileRawUrl } from '../../api';
|
||||
import { MarkdownPreview } from '../files/FilePreview';
|
||||
import { MarkdownText } from '../../lib/markdown-text';
|
||||
import { ToolCallsSection, parseToolCallComment } from './ToolCallsSection';
|
||||
@@ -333,6 +333,34 @@ function ProgressCard({ comment, isStaleThinking }: { comment: LocalTaskComment;
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download chips for files the user attached to a comment. Files live under the
|
||||
* task's input/ dir; we link to the raw-file endpoint with a download attribute.
|
||||
*/
|
||||
function CommentAttachments({ attachments, taskId }: { attachments?: string[]; taskId: number }) {
|
||||
if (!attachments || attachments.length === 0) return null;
|
||||
return (
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||
{attachments.map(name => (
|
||||
<a
|
||||
key={name}
|
||||
href={getLocalFileRawUrl(taskId, 'input', name)}
|
||||
download={name}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={name}
|
||||
className="inline-flex items-center gap-1 max-w-[200px] px-2 py-1 rounded-md bg-white/70 dark:bg-slate-900/40 border border-slate-200 dark:border-slate-600 text-2xs text-slate-700 dark:text-slate-200 hover:bg-white dark:hover:bg-slate-800 hover:border-accent transition-colors"
|
||||
>
|
||||
<svg className="w-3 h-3 shrink-0 text-slate-400" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M10 2.5 4.2 8.3a2 2 0 0 0 2.8 2.8l5.3-5.3a3.2 3.2 0 0 0-4.5-4.5L2.4 7.2" />
|
||||
</svg>
|
||||
<span className="truncate">{name}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChatMessage({ comment, taskId, imageBaseUrl, isStaleThinking }: ChatMessageProps) {
|
||||
const { t } = useTranslation('chat');
|
||||
const { kind, author, body, createdAt } = comment;
|
||||
@@ -352,6 +380,7 @@ export function ChatMessage({ comment, taskId, imageBaseUrl, isStaleThinking }:
|
||||
{author} · {new Date(createdAt).toLocaleString()}
|
||||
</div>
|
||||
<MarkdownText text={body} />
|
||||
<CommentAttachments attachments={comment.attachments} taskId={taskId} />
|
||||
<div className={`text-[10px] mt-1.5 ${isPending ? 'text-amber-400' : 'text-green-500'}`}>
|
||||
{isPending ? t('message.waitingAgentAck') : t('message.acked', { time: new Date(comment.injectedAt!).toLocaleTimeString() })}
|
||||
</div>
|
||||
@@ -369,6 +398,7 @@ export function ChatMessage({ comment, taskId, imageBaseUrl, isStaleThinking }:
|
||||
{author} · {new Date(createdAt).toLocaleString()}
|
||||
</div>
|
||||
<MarkdownText text={body} />
|
||||
<CommentAttachments attachments={comment.attachments} taskId={taskId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user