Merge branch 'int'
All checks were successful
Deploy Nyla Frontend to Production / deploy (push) Successful in 42s
All checks were successful
Deploy Nyla Frontend to Production / deploy (push) Successful in 42s
This commit is contained in:
commit
28d64e0a18
3 changed files with 108 additions and 62 deletions
|
|
@ -33,6 +33,108 @@ interface ChatStreamProps {
|
|||
onOpenEditor?: () => void;
|
||||
}
|
||||
|
||||
const LONG_MESSAGE_WORD_LIMIT = 1000;
|
||||
const COLLAPSED_PREVIEW_WORDS = 200;
|
||||
|
||||
function _countWords(text: string): number {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) return 0;
|
||||
return trimmed.split(/\s+/).length;
|
||||
}
|
||||
|
||||
function _truncateToWords(text: string, maxWords: number): string {
|
||||
const words = text.trim().split(/\s+/);
|
||||
if (words.length <= maxWords) return text;
|
||||
return `${words.slice(0, maxWords).join(' ')}…`;
|
||||
}
|
||||
|
||||
const _MARKDOWN_COMPONENTS = {
|
||||
code: _CodeBlock,
|
||||
table: ({ children }: { children?: React.ReactNode }) => (
|
||||
<div style={{ overflowX: 'auto', margin: '8px 0' }}>
|
||||
<table style={{
|
||||
borderCollapse: 'collapse',
|
||||
width: '100%',
|
||||
fontSize: 13,
|
||||
}}>
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
),
|
||||
th: ({ children }: { children?: React.ReactNode }) => (
|
||||
<th style={{
|
||||
borderBottom: '2px solid #ddd',
|
||||
padding: '6px 10px',
|
||||
textAlign: 'left',
|
||||
fontWeight: 600,
|
||||
background: '#f8f9fa',
|
||||
fontSize: 12,
|
||||
}}>
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }: { children?: React.ReactNode }) => (
|
||||
<td style={{
|
||||
borderBottom: '1px solid #eee',
|
||||
padding: '5px 10px',
|
||||
fontSize: 12,
|
||||
}}>
|
||||
{children}
|
||||
</td>
|
||||
),
|
||||
a: ({ href, children }: { href?: string; children?: React.ReactNode }) => (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" style={{ color: 'var(--primary-color, #F25843)' }}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
img: ({ src, alt, ...rest }: React.ImgHTMLAttributes<HTMLImageElement>) =>
|
||||
src ? <img src={src} alt={alt || ''} {...rest} style={{ maxWidth: '100%', borderRadius: 6 }} /> : null,
|
||||
};
|
||||
|
||||
function _CollapsibleMessageMarkdown({
|
||||
text,
|
||||
allowCollapse,
|
||||
}: {
|
||||
text: string;
|
||||
allowCollapse: boolean;
|
||||
}) {
|
||||
const { t } = useLanguage();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const wordCount = _countWords(text);
|
||||
const isLong = wordCount > LONG_MESSAGE_WORD_LIMIT;
|
||||
const shouldTruncate = isLong && allowCollapse && !expanded;
|
||||
const displayText = shouldTruncate ? _truncateToWords(text, COLLAPSED_PREVIEW_WORDS) : text;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={_MARKDOWN_COMPONENTS}>
|
||||
{displayText}
|
||||
</ReactMarkdown>
|
||||
{isLong && allowCollapse && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded(prev => !prev)}
|
||||
style={{
|
||||
marginTop: 6,
|
||||
padding: 0,
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
color: 'var(--primary-color, #F25843)',
|
||||
cursor: 'pointer',
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
{expanded
|
||||
? t('Weniger anzeigen')
|
||||
: t('Mehr anzeigen ({n} Wörter)', { n: String(wordCount) })}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const ChatStream: React.FC<ChatStreamProps> = ({ messages,
|
||||
agentProgress,
|
||||
isProcessing,
|
||||
|
|
@ -49,6 +151,8 @@ export const ChatStream: React.FC<ChatStreamProps> = ({ messages,
|
|||
const enqueuedIdsRef = useRef<Set<string>>(new Set());
|
||||
const [copiedId, setCopiedId] = useState<string | null>(null);
|
||||
|
||||
const lastMessageId = messages.length > 0 ? messages[messages.length - 1].id : null;
|
||||
|
||||
const _handleCopy = useCallback((msgId: string, text: string) => {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
setCopiedId(msgId);
|
||||
|
|
@ -157,53 +261,10 @@ export const ChatStream: React.FC<ChatStreamProps> = ({ messages,
|
|||
</div>
|
||||
)}
|
||||
{msg.message && (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
code: _CodeBlock,
|
||||
table: ({ children }) => (
|
||||
<div style={{ overflowX: 'auto', margin: '8px 0' }}>
|
||||
<table style={{
|
||||
borderCollapse: 'collapse',
|
||||
width: '100%',
|
||||
fontSize: 13,
|
||||
}}>
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
),
|
||||
th: ({ children }) => (
|
||||
<th style={{
|
||||
borderBottom: '2px solid #ddd',
|
||||
padding: '6px 10px',
|
||||
textAlign: 'left',
|
||||
fontWeight: 600,
|
||||
background: '#f8f9fa',
|
||||
fontSize: 12,
|
||||
}}>
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }) => (
|
||||
<td style={{
|
||||
borderBottom: '1px solid #eee',
|
||||
padding: '5px 10px',
|
||||
fontSize: 12,
|
||||
}}>
|
||||
{children}
|
||||
</td>
|
||||
),
|
||||
a: ({ href, children }) => (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" style={{ color: 'var(--primary-color, #F25843)' }}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
img: ({ src, alt, ...rest }) =>
|
||||
src ? <img src={src} alt={alt || ''} {...rest} style={{ maxWidth: '100%', borderRadius: 6 }} /> : null,
|
||||
}}
|
||||
>
|
||||
{msg.message}
|
||||
</ReactMarkdown>
|
||||
<_CollapsibleMessageMarkdown
|
||||
text={msg.message}
|
||||
allowCollapse={!(isProcessing && msg.id === lastMessageId && msg.role === 'assistant')}
|
||||
/>
|
||||
)}
|
||||
{msg.documents && msg.documents.length > 0 && (
|
||||
<div style={{ marginTop: msg.message ? 8 : 0, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ interface WorkspaceInputProps {
|
|||
onPendingAttachDsConsumed?: () => void;
|
||||
pendingAttachFdsId?: string;
|
||||
onPendingAttachFdsConsumed?: () => void;
|
||||
onPasteAsFile?: (file: File) => void;
|
||||
draftAppend?: string;
|
||||
onDraftAppendConsumed?: () => void;
|
||||
workflowId?: string | null;
|
||||
|
|
@ -136,7 +135,6 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
|
|||
onPendingAttachDsConsumed,
|
||||
pendingAttachFdsId,
|
||||
onPendingAttachFdsConsumed,
|
||||
onPasteAsFile,
|
||||
draftAppend,
|
||||
onDraftAppendConsumed,
|
||||
workflowId,
|
||||
|
|
@ -508,17 +506,6 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
|
|||
const hasAttachments = hasFileOrSourceAttachments;
|
||||
const _controlSize = isMobile ? 38 : 40;
|
||||
|
||||
const _handlePaste = useCallback((e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
if (!onPasteAsFile) return;
|
||||
const text = e.clipboardData.getData('text/plain');
|
||||
if (text && text.length >= 1000) {
|
||||
e.preventDefault();
|
||||
const blob = new Blob([text], { type: 'text/plain' });
|
||||
const file = new File([blob], `pasted-text-${Date.now()}.txt`, { type: 'text/plain' });
|
||||
onPasteAsFile(file);
|
||||
}
|
||||
}, [onPasteAsFile]);
|
||||
|
||||
const _isTreeMimeDrag = useCallback((e: React.DragEvent) => {
|
||||
const types = e.dataTransfer.types;
|
||||
return (
|
||||
|
|
@ -768,7 +755,6 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
|
|||
value={prompt}
|
||||
onChange={_handleChange}
|
||||
onKeyDown={_handleKeyDown}
|
||||
onPaste={_handlePaste}
|
||||
onDragEnter={_handleTextareaDragEnter}
|
||||
onDragLeave={_handleTextareaDragLeave}
|
||||
onDragOver={_handleTextareaDragOver}
|
||||
|
|
|
|||
|
|
@ -503,7 +503,6 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
onPendingAttachDsConsumed={() => { setPendingAttachDsId(''); setPendingAttachDsLabel(''); }}
|
||||
pendingAttachFdsId={pendingAttachFdsId}
|
||||
onPendingAttachFdsConsumed={() => setPendingAttachFdsId('')}
|
||||
onPasteAsFile={_uploadAndAttach}
|
||||
draftAppend={draftAppend}
|
||||
onDraftAppendConsumed={() => setDraftAppend('')}
|
||||
workflowId={workspace.workflowId}
|
||||
|
|
|
|||
Loading…
Reference in a new issue