feat(UI): Introduce new UI components for file attachments and related views (#191)
Add file management API and UI components Implement directory listing, search and CRUD operations in desktop backend Expose new Files API on frontend to list, search, and modify files
This commit is contained in:
committed by
GitHub
parent
1f23b63c0b
commit
d97181bcd2
@@ -221,7 +221,7 @@ export const ChatContainer: React.FC = () => {
|
||||
style={isMobile ? { paddingBottom: 'var(--oc-keyboard-inset, 0px)' } : undefined}
|
||||
>
|
||||
<div className="flex-1 overflow-y-auto p-4 bg-background">
|
||||
<div className="chat-column space-y-4">
|
||||
<div className="chat-message-column space-y-4">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="flex gap-3 p-4">
|
||||
<Skeleton className="h-8 w-8 rounded-full" />
|
||||
|
||||
@@ -827,17 +827,17 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
||||
<div
|
||||
className={cn(
|
||||
'group w-full',
|
||||
shouldShowHeader ? 'pt-2' : 'pt-0',
|
||||
isUser ? 'pb-2' : isFollowedByAssistant ? 'pb-0' : 'pb-2'
|
||||
shouldShowHeader ? 'pt-6' : 'pt-0',
|
||||
isUser ? 'pb-4' : isFollowedByAssistant ? 'pb-0' : 'pb-8'
|
||||
)}
|
||||
data-message-id={message.info.id}
|
||||
ref={messageContainerRef}
|
||||
>
|
||||
<div className="chat-column">
|
||||
<div className="chat-message-column relative">
|
||||
{isUser ? (
|
||||
<FadeInOnReveal>
|
||||
<div className="flex justify-end">
|
||||
<div className="max-w-[85%] rounded-xl rounded-br-xs bg-primary/10 dark:bg-primary/8 px-3.5 pt-2.5 pb-1.5">
|
||||
<div className="max-w-[85%] rounded-2xl rounded-br-sm bg-primary/10 dark:bg-primary/10 px-5 py-3 shadow-sm border border-primary/5">
|
||||
<MessageBody
|
||||
messageId={message.info.id}
|
||||
parts={visibleParts}
|
||||
@@ -870,7 +870,7 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
||||
</div>
|
||||
</FadeInOnReveal>
|
||||
) : (
|
||||
<div>
|
||||
<div className="relative pl-4 ml-1">
|
||||
{shouldShowHeader && (
|
||||
<MessageHeader
|
||||
isUser={isUser}
|
||||
|
||||
@@ -168,9 +168,11 @@ const FileChip = memo(({ file, onRemove }: FileChipProps) => {
|
||||
)}
|
||||
</div>
|
||||
{getFileIcon()}
|
||||
<span title={file.serverPath || displayName}>
|
||||
{displayName}
|
||||
</span>
|
||||
<div className="overflow-hidden max-w-[200px]">
|
||||
<span className="marquee-text" title={file.serverPath || displayName}>
|
||||
{displayName}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-muted-foreground flex-shrink-0">
|
||||
({formatFileSize(file.size)})
|
||||
</span>
|
||||
@@ -289,9 +291,11 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup }: MessageFilesDis
|
||||
className="inline-flex items-center gap-1.5 px-2.5 py-1 bg-muted/30 border border-border/30 rounded-xl typography-meta"
|
||||
>
|
||||
{getFileIcon(file.mime)}
|
||||
<span>
|
||||
{extractFilename(file.filename)}
|
||||
</span>
|
||||
<div className="overflow-hidden max-w-[200px]">
|
||||
<span className="marquee-text">
|
||||
{extractFilename(file.filename)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2049,13 +2049,15 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
|
||||
<span
|
||||
key={`${currentProviderId}-${currentModelId}`}
|
||||
className={cn(
|
||||
'model-controls__model-label',
|
||||
'model-controls__model-label overflow-hidden',
|
||||
controlTextSize,
|
||||
'font-medium whitespace-nowrap text-foreground truncate min-w-0',
|
||||
'font-medium whitespace-nowrap text-foreground min-w-0',
|
||||
'max-w-[260px]'
|
||||
)}
|
||||
>
|
||||
{getCurrentModelDisplayName()}
|
||||
<span className="marquee-text">
|
||||
{getCurrentModelDisplayName()}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -2169,11 +2171,13 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
'model-controls__model-label typography-micro font-medium truncate min-w-0',
|
||||
'model-controls__model-label typography-micro font-medium overflow-hidden min-w-0',
|
||||
isMobile ? 'max-w-[120px]' : 'max-w-[220px]',
|
||||
)}
|
||||
>
|
||||
{getCurrentModelDisplayName()}
|
||||
<span className="marquee-text">
|
||||
{getCurrentModelDisplayName()}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -155,7 +155,7 @@ const UserMessageBody: React.FC<{
|
||||
style={{ contain: 'layout', transform: 'translateZ(0)' }}
|
||||
onTouchStart={isTouchContext && canCopyMessage && hasCopyableText ? revealCopyHint : undefined}
|
||||
>
|
||||
<div className="leading-normal overflow-hidden text-foreground/85">
|
||||
<div className="leading-relaxed overflow-hidden text-foreground/90 text-base">
|
||||
{textParts.map((part, index) => {
|
||||
let mentionForPart: AgentMentionInfo | undefined;
|
||||
if (agentMention && mentionToken && !mentionInjected) {
|
||||
@@ -180,8 +180,7 @@ const UserMessageBody: React.FC<{
|
||||
<MessageFilesDisplay files={parts} onShowPopup={onShowPopup} />
|
||||
{(canCopyMessage && hasCopyableText) || onRevert || onFork ? (
|
||||
<div className={cn(
|
||||
"mt-1 flex items-center justify-end gap-2 opacity-0 pointer-events-none transition-opacity duration-150 group-hover/message:opacity-100 group-hover/message:pointer-events-auto focus-within:opacity-100 focus-within:pointer-events-auto",
|
||||
copyHintVisible && "opacity-100 pointer-events-auto"
|
||||
"mt-1 flex items-center justify-end gap-2"
|
||||
)}>
|
||||
{onRevert && (
|
||||
<Tooltip delayDuration={1000}>
|
||||
@@ -284,7 +283,6 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
|
||||
errorMessage,
|
||||
}) => {
|
||||
|
||||
void _streamPhase;
|
||||
void _allowAnimation;
|
||||
const [copyHintVisible, setCopyHintVisible] = React.useState(false);
|
||||
const copyHintTimeoutRef = React.useRef<number | null>(null);
|
||||
@@ -899,11 +897,9 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
|
||||
}}
|
||||
onTouchStart={isTouchContext && canCopyMessage && hasCopyableText ? revealCopyHint : undefined}
|
||||
>
|
||||
<div
|
||||
className="px-3"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="leading-normal overflow-hidden text-foreground/90 [&_p:last-child]:mb-0 [&_ul:last-child]:mb-0 [&_ol:last-child]:mb-0"
|
||||
className="leading-relaxed overflow-hidden text-foreground/90 [&_p:last-child]:mb-0 [&_ul:last-child]:mb-0 [&_ol:last-child]:mb-0"
|
||||
>
|
||||
{renderedParts}
|
||||
{showErrorMessage && (
|
||||
|
||||
@@ -19,7 +19,7 @@ const MessageHeader: React.FC<MessageHeaderProps> = ({ isUser, providerID, agent
|
||||
|
||||
return (
|
||||
<FadeInOnReveal>
|
||||
<div className={cn('pl-3', 'mb-2')}>
|
||||
<div className={cn('mb-2')}>
|
||||
<div className={cn('flex items-center justify-between gap-2')}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-shrink-0">
|
||||
|
||||
Reference in New Issue
Block a user