From defc046ca9e56802c4cc168d205efddd031c942d Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Mon, 20 Jan 2025 15:55:36 -0800 Subject: [PATCH 1/5] wip agent ui animation --- .../ChatContainer/ChatHistory/index.jsx | 164 ++++++++++++++---- frontend/src/index.css | 162 ++++++++++++++++- frontend/tailwind.config.js | 17 +- 3 files changed, 303 insertions(+), 40 deletions(-) diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx index 9bf8eeebbef..b916d762cd8 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx @@ -3,7 +3,7 @@ import HistoricalMessage from "./HistoricalMessage"; import PromptReply from "./PromptReply"; import { useManageWorkspaceModal } from "../../../Modals/ManageWorkspace"; import ManageWorkspace from "../../../Modals/ManageWorkspace"; -import { ArrowDown } from "@phosphor-icons/react"; +import { ArrowDown, CircleNotch, CaretDown, Check } from "@phosphor-icons/react"; import debounce from "lodash.debounce"; import useUser from "@/hooks/useUser"; import Chartable from "./Chartable"; @@ -31,6 +31,8 @@ export default function ChatHistory({ const isStreaming = history[history.length - 1]?.animate; const { showScrollbar } = Appearance.getSettings(); const { textSizeClass } = useTextSize(); + const [statusResponses, setStatusResponses] = useState([]); + const [isStatusActive, setIsStatusActive] = useState(false); useEffect(() => { if (!isUserScrolling && (isAtBottom || isStreaming)) { @@ -136,6 +138,17 @@ export default function ChatHistory({ ); }; + // Update status responses when history changes + useEffect(() => { + const latestStatus = history.filter(msg => msg?.type === "statusResponse" && !!msg.content); + if (latestStatus.length > 0) { + setStatusResponses(prev => [...new Set([...prev, ...latestStatus.map(s => s.content)])]); + setIsStatusActive(true); + } else { + setIsStatusActive(false); + } + }, [history]); + if (history.length === 0 && !hasAttachments) { return (
@@ -187,18 +200,15 @@ export default function ChatHistory({ const isLastBotReply = index === history.length - 1 && props.role === "assistant"; - if (props?.type === "statusResponse" && !!props.content) { - return ; - } + // Create an array of elements to render for this iteration + const elements = []; if (props.type === "rechartVisualize" && !!props.content) { - return ( + elements.push( ); - } - - if (isLastBotReply && props.animate) { - return ( + } else if (isLastBotReply && props.animate) { + elements.push( ); + } else if (props?.type !== "statusResponse") { + elements.push( + + ); } - return ( - - ); + // If this message triggered the agent and it's active, add the StatusResponse + if (props.role === "user" && + history[index + 1]?.type === "statusResponse" && + isStatusActive) { + elements.push( + + ); + } + + return elements; })} + {showing && ( )} @@ -254,16 +284,82 @@ export default function ChatHistory({ } function StatusResponse({ props }) { + const [isThinking, setIsThinking] = useState(true); + const [isExpanded, setIsExpanded] = useState(false); + const [showCheckmark, setShowCheckmark] = useState(false); + + useEffect(() => { + setIsThinking(!!props.pending); + + if (!props.pending && props.isLastMessage) { + setShowCheckmark(true); + setTimeout(() => setShowCheckmark(false), 2000); + } + }, [props.content, props.pending, props.isLastMessage]); + return (
-
-
- - {props.content} - +
+ {/* Single thought bar that updates */} +
+ 💭 +
+ + {props.content} + +
+
+ {isThinking ? ( + + ) : showCheckmark ? ( + + ) : null} + {props.previousThoughts?.length > 0 && ( + + )} +
+ + {/* Previous thoughts dropdown */} + {props.previousThoughts?.length > 0 && ( +
+
+ {props.previousThoughts.map((thought, index) => ( +
+ 💭 + + {thought} + +
+ ))} +
+
+ )}
); diff --git a/frontend/src/index.css b/frontend/src/index.css index 96d01d41cb1..248ea24aadd 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -584,21 +584,56 @@ dialog::backdrop { } @keyframes slideUp { - from { - max-height: 400px; + 0% { + transform: translateY(20px); + opacity: 0; + } + 100% { + transform: translateY(0); opacity: 1; } +} - to { - max-height: 0; +@keyframes slideUpOut { + 0% { + transform: translateY(0); + opacity: 1; + } + 100% { + transform: translateY(-20px); opacity: 0; } } -.slide-up { +.animate-slide-up { animation: slideUp 0.3s ease-out forwards; } +.animate-slide-up-out { + animation: slideUpOut 0.3s ease-out forwards; +} + +.animate-fill-forwards { + animation-fill-mode: forwards; +} + +.animate-delay-100 { + animation-delay: 100ms; +} + +.animate-bounce-subtle { + animation: bounce 2s infinite; +} + +@keyframes bounce { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-3px); + } +} + .input-label { @apply text-[14px] font-bold text-white; } @@ -946,3 +981,120 @@ does not extend the close button beyond the viewport. */ .rti--container { @apply !bg-theme-settings-input-bg !text-white !placeholder-white !placeholder-opacity-60 !text-sm !rounded-lg !p-2.5; } + +@keyframes typewriter { + from { + width: 0; + } + to { + width: 100%; + } +} + +@keyframes fadeUpIn { + 0% { + opacity: 0; + transform: translateY(5px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeUpOut { + 0% { + opacity: 1; + transform: translateY(0); + } + 100% { + opacity: 0; + transform: translateY(-10px); + } +} + +.thought-bar { + position: relative; + background: rgba(255, 255, 255, 0.1); + border-radius: 20px; + padding: 8px 16px; + display: flex; + align-items: center; + gap: 8px; + transition: all 0.2s ease; +} + +.thought-bar:hover { + background: rgba(255, 255, 255, 0.15); +} + +.thought-content { + white-space: nowrap; + overflow: hidden; +} + +.thought-content.typing { + animation: typewriter 0.5s steps(40, end) forwards; +} + +.thought-history { + position: absolute; + bottom: 100%; + left: 0; + right: 0; + margin-bottom: 8px; + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + overflow: hidden; + transition: all 0.3s ease; +} + +.thought-history.expanded { + padding: 8px; + margin-bottom: 12px; +} + +.previous-thought { + animation: fadeUpIn 0.3s ease forwards; + opacity: 0; +} + +.thought-exit { + animation: fadeUpOut 0.3s ease forwards; +} + +.animate-fadeUpIn { + animation: fadeUpIn 0.3s ease-out forwards; +} + +@keyframes bounce-subtle { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-2px); + } +} + +.animate-bounce-subtle { + animation: bounce-subtle 2s ease-in-out infinite; +} + +@keyframes thoughtTransition { + 0% { + opacity: 0; + transform: translateY(10px); + } + 30% { + opacity: 1; + transform: translateY(0); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.animate-thoughtTransition { + animation: thoughtTransition 0.5s ease-out forwards; +} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 8ecdbcdc0a4..517af2ea167 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -141,7 +141,10 @@ export default { }, animation: { sweep: "sweep 0.5s ease-in-out", - "pulse-glow": "pulse-glow 1.5s infinite" + "pulse-glow": "pulse-glow 1.5s infinite", + 'fade-in': 'fade-in 0.3s ease-out', + 'slide-up': 'slide-up 0.4s ease-out forwards', + 'bounce-subtle': 'bounce-subtle 2s ease-in-out infinite' }, keyframes: { sweep: { @@ -175,6 +178,18 @@ export default { boxShadow: "0 0 0 rgba(255, 255, 255, 0.0)", backgroundColor: "rgba(255, 255, 255, 0.0)" } + }, + 'fade-in': { + '0%': { opacity: '0' }, + '100%': { opacity: '1' } + }, + 'slide-up': { + '0%': { transform: 'translateY(10px)', opacity: '0' }, + '100%': { transform: 'translateY(0)', opacity: '1' } + }, + 'bounce-subtle': { + '0%, 100%': { transform: 'translateY(0)' }, + '50%': { transform: 'translateY(-2px)' } } } } From e337fbaf7ff43cb6c4c2a6007ee8afc883d0b8c7 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Tue, 21 Jan 2025 14:33:02 -0800 Subject: [PATCH 2/5] WIP agent ui revision --- .../ChatHistory/StatusResponse/index.jsx | 113 ++++++++ .../ChatContainer/ChatHistory/index.jsx | 246 +++++++----------- .../ChatContainer/ChatTooltips/index.jsx | 6 + frontend/src/index.css | 6 +- frontend/src/locales/fa/common.js | 144 ++++++---- frontend/src/locales/resources.js | 1 - frontend/src/utils/chat/agent.js | 2 +- frontend/src/utils/chat/index.js | 5 +- .../utils/agents/aibitat/plugins/websocket.js | 6 +- server/utils/agents/ephemeral.js | 2 + server/utils/chats/agents.js | 2 + 11 files changed, 319 insertions(+), 214 deletions(-) create mode 100644 frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/StatusResponse/index.jsx diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/StatusResponse/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/StatusResponse/index.jsx new file mode 100644 index 00000000000..c06fb35ad1e --- /dev/null +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/StatusResponse/index.jsx @@ -0,0 +1,113 @@ +import React, { useState } from "react"; +import { + CaretDown, + CircleNotch, + Check, + CheckCircle, +} from "@phosphor-icons/react"; + +export default function StatusResponse({ + messages = [], + isThinking = false, + showCheckmark = false, +}) { + const [isExpanded, setIsExpanded] = useState(false); + const currentThought = messages[messages.length - 1]; + const previousThoughts = messages.slice(0, -1); + + function handleExpandClick() { + if (!previousThoughts.length > 0) return; + setIsExpanded(!isExpanded); + } + + return ( +
+
+
+ {isThinking ? ( + + ) : showCheckmark ? ( + + ) : null} +
+ + {currentThought.content} + +
+
+ {previousThoughts?.length > 0 && ( +
+ +
+ )} +
+
+ + {/* Previous thoughts dropdown */} + {previousThoughts?.length > 0 && ( +
+
+ {previousThoughts.map((thought, index) => ( +
+

+ {index + 1}/{previousThoughts.length} +

+
+ + {thought.content} + +
+
+ ))} + {/* Append current thought to the end */} +
+

+ {previousThoughts.length + 1}/{previousThoughts.length + 1} +

+
+ + {currentThought.content} + +
+
+
+
+ )} +
+
+ ); +} diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx index b916d762cd8..1fb8a49e02a 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx @@ -1,9 +1,10 @@ import React, { useEffect, useRef, useState } from "react"; import HistoricalMessage from "./HistoricalMessage"; import PromptReply from "./PromptReply"; +import StatusResponse from "./StatusResponse"; import { useManageWorkspaceModal } from "../../../Modals/ManageWorkspace"; import ManageWorkspace from "../../../Modals/ManageWorkspace"; -import { ArrowDown, CircleNotch, CaretDown, Check } from "@phosphor-icons/react"; +import { ArrowDown } from "@phosphor-icons/react"; import debounce from "lodash.debounce"; import useUser from "@/hooks/useUser"; import Chartable from "./Chartable"; @@ -12,6 +13,7 @@ import { useParams } from "react-router-dom"; import paths from "@/utils/paths"; import Appearance from "@/models/appearance"; import useTextSize from "@/hooks/useTextSize"; +import { v4 } from "uuid"; export default function ChatHistory({ history = [], @@ -31,8 +33,6 @@ export default function ChatHistory({ const isStreaming = history[history.length - 1]?.animate; const { showScrollbar } = Appearance.getSettings(); const { textSizeClass } = useTextSize(); - const [statusResponses, setStatusResponses] = useState([]); - const [isStatusActive, setIsStatusActive] = useState(false); useEffect(() => { if (!isUserScrolling && (isAtBottom || isStreaming)) { @@ -138,17 +138,6 @@ export default function ChatHistory({ ); }; - // Update status responses when history changes - useEffect(() => { - const latestStatus = history.filter(msg => msg?.type === "statusResponse" && !!msg.content); - if (latestStatus.length > 0) { - setStatusResponses(prev => [...new Set([...prev, ...latestStatus.map(s => s.content)])]); - setIsStatusActive(true); - } else { - setIsStatusActive(false); - } - }, [history]); - if (history.length === 0 && !hasAttachments) { return (
@@ -187,80 +176,40 @@ export default function ChatHistory({ ); } + const hhistory = buildMessages({ + workspace, + history, + regenerateAssistantMessage, + saveEditedMessage, + forkThread, + }); + return (
- {history.map((props, index) => { - const isLastBotReply = - index === history.length - 1 && props.role === "assistant"; - - // Create an array of elements to render for this iteration - const elements = []; - - if (props.type === "rechartVisualize" && !!props.content) { - elements.push( - - ); - } else if (isLastBotReply && props.animate) { - elements.push( - - ); - } else if (props?.type !== "statusResponse") { - elements.push( - - ); - } - - // If this message triggered the agent and it's active, add the StatusResponse - if (props.role === "user" && - history[index + 1]?.type === "statusResponse" && - isStatusActive) { - elements.push( + {hhistory.map((item, index) => { + if (Array.isArray(item)) { + const lastMessage = history?.[history.length - 1] || {}; + const hasSubsequentMessages = index < hhistory.length - 1; + return ( ); } - - return elements; + return item; })} - {showing && ( )} @@ -283,88 +232,6 @@ export default function ChatHistory({ ); } -function StatusResponse({ props }) { - const [isThinking, setIsThinking] = useState(true); - const [isExpanded, setIsExpanded] = useState(false); - const [showCheckmark, setShowCheckmark] = useState(false); - - useEffect(() => { - setIsThinking(!!props.pending); - - if (!props.pending && props.isLastMessage) { - setShowCheckmark(true); - setTimeout(() => setShowCheckmark(false), 2000); - } - }, [props.content, props.pending, props.isLastMessage]); - - return ( -
-
- {/* Single thought bar that updates */} -
- 💭 -
- - {props.content} - -
-
- {isThinking ? ( - - ) : showCheckmark ? ( - - ) : null} - {props.previousThoughts?.length > 0 && ( - - )} -
-
- - {/* Previous thoughts dropdown */} - {props.previousThoughts?.length > 0 && ( -
-
- {props.previousThoughts.map((thought, index) => ( -
- 💭 - - {thought} - -
- ))} -
-
- )} -
-
- ); -} - function WorkspaceChatSuggestions({ suggestions = [], sendSuggestion }) { if (suggestions.length === 0) return null; return ( @@ -382,3 +249,64 @@ function WorkspaceChatSuggestions({ suggestions = [], sendSuggestion }) {
); } + +function buildMessages({ + history, + workspace, + regenerateAssistantMessage, + saveEditedMessage, + forkThread, +}) { + return history.reduce((acc, props, index) => { + const isLastBotReply = + index === history.length - 1 && props.role === "assistant"; + + if (props?.type === "statusResponse" && !!props.content) { + if (acc.length > 0 && Array.isArray(acc[acc.length - 1])) { + acc[acc.length - 1].push(props); + } else { + acc.push([props]); + } + return acc; + } + + if (props.type === "rechartVisualize" && !!props.content) { + acc.push( + + ); + } else if (isLastBotReply && props.animate) { + acc.push( + + ); + } else { + acc.push( + + ); + } + return acc; + }, []); +} diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatTooltips/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatTooltips/index.jsx index 3aa5dc6ccb1..ea3cbdfb1f7 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatTooltips/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatTooltips/index.jsx @@ -67,6 +67,12 @@ export function ChatTooltips() { delayShow={300} className="tooltip !text-xs" /> + ); } diff --git a/frontend/src/index.css b/frontend/src/index.css index 248ea24aadd..a086ee55400 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -626,7 +626,8 @@ dialog::backdrop { } @keyframes bounce { - 0%, 100% { + 0%, + 100% { transform: translateY(0); } 50% { @@ -1068,7 +1069,8 @@ does not extend the close button beyond the viewport. */ } @keyframes bounce-subtle { - 0%, 100% { + 0%, + 100% { transform: translateY(0); } 50% { diff --git a/frontend/src/locales/fa/common.js b/frontend/src/locales/fa/common.js index bfc9e9d7ab2..1593fb62b71 100644 --- a/frontend/src/locales/fa/common.js +++ b/frontend/src/locales/fa/common.js @@ -112,7 +112,8 @@ const TRANSLATIONS = { }, message: { title: "پیام‌های گفتگوی پیشنهادی", - description: "پیام‌هایی که به کاربران فضای کاری پیشنهاد می‌شود را شخصی‌سازی کنید.", + description: + "پیام‌هایی که به کاربران فضای کاری پیشنهاد می‌شود را شخصی‌سازی کنید.", add: "افزودن پیام جدید", save: "ذخیره پیام‌ها", heading: "برایم توضیح بده", @@ -126,11 +127,13 @@ const TRANSLATIONS = { }, delete: { title: "حذف فضای کاری", - description: "این فضای کاری و تمام داده‌های آن را حذف کنید. این کار فضای کاری را برای همه کاربران حذف خواهد کرد.", + description: + "این فضای کاری و تمام داده‌های آن را حذف کنید. این کار فضای کاری را برای همه کاربران حذف خواهد کرد.", delete: "حذف فضای کاری", deleting: "در حال حذف فضای کاری...", "confirm-start": "شما در حال حذف کامل", - "confirm-end": "فضای کاری هستید. این کار تمام جاسازی‌های برداری را از پایگاه داده برداری شما حذف خواهد کرد.\n\nفایل‌های اصلی منبع دست نخورده باقی خواهند ماند. این عمل برگشت‌ناپذیر است.", + "confirm-end": + "فضای کاری هستید. این کار تمام جاسازی‌های برداری را از پایگاه داده برداری شما حذف خواهد کرد.\n\nفایل‌های اصلی منبع دست نخورده باقی خواهند ماند. این عمل برگشت‌ناپذیر است.", }, }, @@ -138,12 +141,14 @@ const TRANSLATIONS = { chat: { llm: { title: "ارائه‌دهنده LLM فضای کاری", - description: "ارائه‌دهنده و مدل LLM خاصی که برای این فضای کاری استفاده خواهد شد. به طور پیش‌فرض، از ارائه‌دهنده و تنظیمات LLM سیستم استفاده می‌کند.", + description: + "ارائه‌دهنده و مدل LLM خاصی که برای این فضای کاری استفاده خواهد شد. به طور پیش‌فرض، از ارائه‌دهنده و تنظیمات LLM سیستم استفاده می‌کند.", search: "جستجوی تمام ارائه‌دهندگان LLM", }, model: { title: "مدل گفتگوی فضای کاری", - description: "مدل گفتگوی خاصی که برای این فضای کاری استفاده خواهد شد. اگر خالی باشد، از ترجیحات LLM سیستم استفاده خواهد کرد.", + description: + "مدل گفتگوی خاصی که برای این فضای کاری استفاده خواهد شد. اگر خالی باشد، از ترجیحات LLM سیستم استفاده خواهد کرد.", wait: "-- در انتظار مدل‌ها --", }, mode: { @@ -163,24 +168,30 @@ const TRANSLATIONS = { }, history: { title: "تاریخچه گفتگو", - "desc-start": "تعداد گفتگوهای قبلی که در حافظه کوتاه‌مدت پاسخ گنجانده خواهد شد.", + "desc-start": + "تعداد گفتگوهای قبلی که در حافظه کوتاه‌مدت پاسخ گنجانده خواهد شد.", recommend: "پیشنهاد: ۲۰. ", - "desc-end": "بیش از ۴۵ احتمالاً منجر به شکست مداوم گفتگو می‌شود که به اندازه پیام‌ها بستگی دارد.", + "desc-end": + "بیش از ۴۵ احتمالاً منجر به شکست مداوم گفتگو می‌شود که به اندازه پیام‌ها بستگی دارد.", }, prompt: { title: "پیش‌متن", - description: "پیش‌متنی که در این فضای کاری استفاده خواهد شد. زمینه و دستورالعمل‌ها را برای تولید پاسخ توسط هوش مصنوعی تعریف کنید. باید یک پیش‌متن دقیق ارائه دهید تا هوش مصنوعی بتواند پاسخی مرتبط و دقیق تولید کند.", + description: + "پیش‌متنی که در این فضای کاری استفاده خواهد شد. زمینه و دستورالعمل‌ها را برای تولید پاسخ توسط هوش مصنوعی تعریف کنید. باید یک پیش‌متن دقیق ارائه دهید تا هوش مصنوعی بتواند پاسخی مرتبط و دقیق تولید کند.", }, refusal: { title: "پاسخ رد در حالت پرس‌وجو", "desc-start": "در حالت", query: "پرس‌وجو", - "desc-end": "ممکن است بخواهید هنگامی که هیچ محتوایی یافت نمی‌شود، یک پاسخ رد سفارشی برگردانید.", + "desc-end": + "ممکن است بخواهید هنگامی که هیچ محتوایی یافت نمی‌شود، یک پاسخ رد سفارشی برگردانید.", }, temperature: { title: "دمای LLM", - "desc-start": 'این تنظیم میزان "خلاقیت" پاسخ‌های LLM شما را کنترل می‌کند.', - "desc-end": "هر چه عدد بالاتر باشد، خلاقیت بیشتر است. برای برخی مدل‌ها، تنظیم بسیار بالا می‌تواند منجر به پاسخ‌های نامفهوم شود.", + "desc-start": + 'این تنظیم میزان "خلاقیت" پاسخ‌های LLM شما را کنترل می‌کند.', + "desc-end": + "هر چه عدد بالاتر باشد، خلاقیت بیشتر است. برای برخی مدل‌ها، تنظیم بسیار بالا می‌تواند منجر به پاسخ‌های نامفهوم شود.", hint: "اکثر LLMها محدوده‌های مختلفی از مقادیر معتبر را دارند. برای این اطلاعات به ارائه‌دهنده LLM خود مراجعه کنید.", }, }, @@ -190,12 +201,14 @@ const TRANSLATIONS = { identifier: "شناسه پایگاه داده برداری", snippets: { title: "حداکثر قطعات متنی", - description: "این تنظیم حداکثر تعداد قطعات متنی که برای هر گفتگو یا پرس‌وجو به LLM ارسال می‌شود را کنترل می‌کند.", + description: + "این تنظیم حداکثر تعداد قطعات متنی که برای هر گفتگو یا پرس‌وجو به LLM ارسال می‌شود را کنترل می‌کند.", recommend: "پیشنهادی: 4", }, doc: { title: "آستانه شباهت سند", - description: "حداقل امتیاز شباهت مورد نیاز برای اینکه یک منبع مرتبط با گفتگو در نظر گرفته شود. هر چه عدد بالاتر باشد، منبع باید شباهت بیشتری با گفتگو داشته باشد.", + description: + "حداقل امتیاز شباهت مورد نیاز برای اینکه یک منبع مرتبط با گفتگو در نظر گرفته شود. هر چه عدد بالاتر باشد، منبع باید شباهت بیشتری با گفتگو داشته باشد.", zero: "بدون محدودیت", low: "پایین (امتیاز شباهت ≥ .25)", medium: "متوسط (امتیاز شباهت ≥ .50)", @@ -204,7 +217,8 @@ const TRANSLATIONS = { reset: { reset: "بازنشانی پایگاه داده برداری", resetting: "در حال پاک کردن بردارها...", - confirm: "شما در حال بازنشانی پایگاه داده برداری این فضای کاری هستید. این کار تمام جاسازی‌های برداری فعلی را حذف خواهد کرد.\n\nفایل‌های اصلی منبع دست نخورده باقی خواهند ماند. این عمل برگشت‌ناپذیر است.", + confirm: + "شما در حال بازنشانی پایگاه داده برداری این فضای کاری هستید. این کار تمام جاسازی‌های برداری فعلی را حذف خواهد کرد.\n\nفایل‌های اصلی منبع دست نخورده باقی خواهند ماند. این عمل برگشت‌ناپذیر است.", error: "بازنشانی پایگاه داده برداری فضای کاری امکان‌پذیر نبود!", success: "پایگاه داده برداری فضای کاری بازنشانی شد!", }, @@ -212,47 +226,59 @@ const TRANSLATIONS = { // Agent Configuration agent: { - "performance-warning": "عملکرد LLMهایی که به طور صریح از فراخوانی ابزار پشتیبانی نمی‌کنند، به شدت به قابلیت‌ها و دقت مدل وابسته است. برخی توانایی‌ها ممکن است محدود یا غیرفعال باشند.", + "performance-warning": + "عملکرد LLMهایی که به طور صریح از فراخوانی ابزار پشتیبانی نمی‌کنند، به شدت به قابلیت‌ها و دقت مدل وابسته است. برخی توانایی‌ها ممکن است محدود یا غیرفعال باشند.", provider: { title: "ارائه‌دهنده LLM عامل فضای کاری", - description: "ارائه‌دهنده و مدل LLM خاصی که برای عامل @agent این فضای کاری استفاده خواهد شد.", + description: + "ارائه‌دهنده و مدل LLM خاصی که برای عامل @agent این فضای کاری استفاده خواهد شد.", }, mode: { chat: { title: "مدل گفتگوی عامل فضای کاری", - description: "مدل گفتگوی خاصی که برای عامل @agent این فضای کاری استفاده خواهد شد.", + description: + "مدل گفتگوی خاصی که برای عامل @agent این فضای کاری استفاده خواهد شد.", }, title: "مدل عامل فضای کاری", - description: "مدل LLM خاصی که برای عامل @agent این فضای کاری استفاده خواهد شد.", + description: + "مدل LLM خاصی که برای عامل @agent این فضای کاری استفاده خواهد شد.", wait: "-- در انتظار مدل‌ها --", }, skill: { title: "مهارت‌های پیش‌فرض عامل", - description: "توانایی‌های طبیعی عامل پیش‌فرض را با این مهارت‌های از پیش ساخته شده بهبود دهید. این تنظیمات برای تمام فضاهای کاری اعمال می‌شود.", + description: + "توانایی‌های طبیعی عامل پیش‌فرض را با این مهارت‌های از پیش ساخته شده بهبود دهید. این تنظیمات برای تمام فضاهای کاری اعمال می‌شود.", rag: { title: "RAG و حافظه بلندمدت", - description: 'به عامل اجازه دهید از اسناد محلی شما برای پاسخ به پرس‌وجو استفاده کند یا از عامل بخواهید قطعات محتوا را برای بازیابی حافظه بلندمدت "به خاطر بسپارد".', + description: + 'به عامل اجازه دهید از اسناد محلی شما برای پاسخ به پرس‌وجو استفاده کند یا از عامل بخواهید قطعات محتوا را برای بازیابی حافظه بلندمدت "به خاطر بسپارد".', }, view: { title: "مشاهده و خلاصه‌سازی اسناد", - description: "به عامل اجازه دهید محتوای فایل‌های جاسازی شده فعلی فضای کاری را فهرست و خلاصه کند.", + description: + "به عامل اجازه دهید محتوای فایل‌های جاسازی شده فعلی فضای کاری را فهرست و خلاصه کند.", }, scrape: { title: "استخراج از وب‌سایت‌ها", - description: "به عامل اجازه دهید محتوای وب‌سایت‌ها را بازدید و استخراج کند.", + description: + "به عامل اجازه دهید محتوای وب‌سایت‌ها را بازدید و استخراج کند.", }, generate: { title: "تولید نمودارها", - description: "به عامل پیش‌فرض امکان تولید انواع مختلف نمودار از داده‌های ارائه شده یا داده شده در گفتگو را بدهید.", + description: + "به عامل پیش‌فرض امکان تولید انواع مختلف نمودار از داده‌های ارائه شده یا داده شده در گفتگو را بدهید.", }, save: { title: "تولید و ذخیره فایل‌ها در مرورگر", - description: "به عامل پیش‌فرض امکان تولید و نوشتن در فایل‌هایی که ذخیره می‌شوند و می‌توانند در مرورگر شما دانلود شوند را بدهید.", + description: + "به عامل پیش‌فرض امکان تولید و نوشتن در فایل‌هایی که ذخیره می‌شوند و می‌توانند در مرورگر شما دانلود شوند را بدهید.", }, web: { title: "جستجو و مرور زنده وب", - "desc-start": "با اتصال به یک ارائه‌دهنده جستجوی وب (SERP)، به عامل خود امکان جستجو در وب برای پاسخ به سؤالات خود را بدهید.", - "desc-end": "جستجوی وب در طول جلسات عامل تا زمانی که این تنظیم نشود، کار نخواهد کرد.", + "desc-start": + "با اتصال به یک ارائه‌دهنده جستجوی وب (SERP)، به عامل خود امکان جستجو در وب برای پاسخ به سؤالات خود را بدهید.", + "desc-end": + "جستجوی وب در طول جلسات عامل تا زمانی که این تنظیم نشود، کار نخواهد کرد.", }, }, }, @@ -287,7 +313,8 @@ const TRANSLATIONS = { }, message: { title: "شخصی‌سازی پیام‌ها", - description: "پیام‌های خودکار نمایش داده شده به کاربران را شخصی‌سازی کنید.", + description: + "پیام‌های خودکار نمایش داده شده به کاربران را شخصی‌سازی کنید.", new: "جدید", system: "سیستم", user: "کاربر", @@ -298,7 +325,8 @@ const TRANSLATIONS = { }, icons: { title: "آیکون‌های سفارشی پاورقی", - description: "آیکون‌های نمایش داده شده در پایین نوار کناری را شخصی‌سازی کنید.", + description: + "آیکون‌های نمایش داده شده در پایین نوار کناری را شخصی‌سازی کنید.", icon: "آیکون", link: "لینک", }, @@ -320,51 +348,63 @@ const TRANSLATIONS = { llm: { title: "ترجیحات مدل زبانی", - description: "این‌ها اعتبارنامه‌ها و تنظیمات ارائه‌دهنده مدل زبانی و جاسازی انتخابی شما هستند. مهم است که این کلیدها به‌روز و صحیح باشند در غیر این صورت AnythingLLM به درستی کار نخواهد کرد.", + description: + "این‌ها اعتبارنامه‌ها و تنظیمات ارائه‌دهنده مدل زبانی و جاسازی انتخابی شما هستند. مهم است که این کلیدها به‌روز و صحیح باشند در غیر این صورت AnythingLLM به درستی کار نخواهد کرد.", provider: "ارائه‌دهنده مدل زبانی", }, transcription: { title: "ترجیحات مدل رونویسی", - description: "این‌ها اعتبارنامه‌ها و تنظیمات ارائه‌دهنده مدل رونویسی انتخابی شما هستند. مهم است که این کلیدها به‌روز و صحیح باشند در غیر این صورت فایل‌های رسانه و صوتی رونویسی نخواهند شد.", + description: + "این‌ها اعتبارنامه‌ها و تنظیمات ارائه‌دهنده مدل رونویسی انتخابی شما هستند. مهم است که این کلیدها به‌روز و صحیح باشند در غیر این صورت فایل‌های رسانه و صوتی رونویسی نخواهند شد.", provider: "ارائه‌دهنده رونویسی", - "warn-start": "استفاده از مدل محلی Whisper روی دستگاه‌هایی با RAM یا CPU محدود می‌تواند هنگام پردازش فایل‌های رسانه‌ای باعث توقف AnythingLLM شود.", - "warn-recommend": "ما حداقل ۲ گیگابایت RAM و آپلود فایل‌های کمتر از ۱۰ مگابایت را توصیه می‌کنیم.", + "warn-start": + "استفاده از مدل محلی Whisper روی دستگاه‌هایی با RAM یا CPU محدود می‌تواند هنگام پردازش فایل‌های رسانه‌ای باعث توقف AnythingLLM شود.", + "warn-recommend": + "ما حداقل ۲ گیگابایت RAM و آپلود فایل‌های کمتر از ۱۰ مگابایت را توصیه می‌کنیم.", "warn-end": "مدل داخلی در اولین استفاده به صورت خودکار دانلود خواهد شد.", }, embedding: { title: "ترجیحات جاسازی", - "desc-start": "هنگام استفاده از یک LLM که به طور پیش‌فرض از موتور جاسازی پشتیبانی نمی‌کند - ممکن است نیاز به تعیین اعتبارنامه‌های اضافی برای جاسازی متن داشته باشید.", - "desc-end": "جاسازی فرآیند تبدیل متن به بردارها است. این اعتبارنامه‌ها برای تبدیل فایل‌ها و درخواست‌های شما به فرمتی که AnythingLLM بتواند پردازش کند، ضروری هستند.", + "desc-start": + "هنگام استفاده از یک LLM که به طور پیش‌فرض از موتور جاسازی پشتیبانی نمی‌کند - ممکن است نیاز به تعیین اعتبارنامه‌های اضافی برای جاسازی متن داشته باشید.", + "desc-end": + "جاسازی فرآیند تبدیل متن به بردارها است. این اعتبارنامه‌ها برای تبدیل فایل‌ها و درخواست‌های شما به فرمتی که AnythingLLM بتواند پردازش کند، ضروری هستند.", provider: { title: "ارائه‌دهنده جاسازی", - description: "هنگام استفاده از موتور جاسازی داخلی AnythingLLM نیازی به تنظیمات نیست.", + description: + "هنگام استفاده از موتور جاسازی داخلی AnythingLLM نیازی به تنظیمات نیست.", }, }, text: { title: "تقسیم متن و تکه‌بندی", - "desc-start": "تقسیم متن به شما امکان می‌دهد اسناد بزرگ را به بخش‌های کوچک‌تر تقسیم کنید که برای جاسازی و پردازش مناسب‌تر هستند.", - "desc-end": "سعی کنید تعادلی بین اندازه بخش و همپوشانی ایجاد کنید تا از دست رفتن اطلاعات را به حداقل برسانید.", + "desc-start": + "تقسیم متن به شما امکان می‌دهد اسناد بزرگ را به بخش‌های کوچک‌تر تقسیم کنید که برای جاسازی و پردازش مناسب‌تر هستند.", + "desc-end": + "سعی کنید تعادلی بین اندازه بخش و همپوشانی ایجاد کنید تا از دست رفتن اطلاعات را به حداقل برسانید.", "warn-start": "تغییر این مقادیر نیاز به", "warn-center": "پردازش مجدد تمام اسناد", "warn-end": "خواهد داشت.", size: { title: "حداکثر اندازه بخش", - description: "این حداکثر تعداد کاراکترهایی است که می‌تواند در یک بردار وجود داشته باشد.", + description: + "این حداکثر تعداد کاراکترهایی است که می‌تواند در یک بردار وجود داشته باشد.", recommend: "حداکثر طول مدل جاسازی", }, overlap: { title: "همپوشانی بخش‌های متن", - description: "این حداکثر همپوشانی کاراکترها است که در هنگام تکه‌بندی بین دو بخش متن مجاور رخ می‌دهد.", + description: + "این حداکثر همپوشانی کاراکترها است که در هنگام تکه‌بندی بین دو بخش متن مجاور رخ می‌دهد.", }, }, // Vector Database vector: { title: "پایگاه داده برداری", - description: "این‌ها اعتبارنامه‌ها و تنظیمات نحوه عملکرد نمونه AnythingLLM شما هستند. مهم است که این کلیدها به‌روز و صحیح باشند.", + description: + "این‌ها اعتبارنامه‌ها و تنظیمات نحوه عملکرد نمونه AnythingLLM شما هستند. مهم است که این کلیدها به‌روز و صحیح باشند.", provider: { title: "ارائه‌دهنده پایگاه داده برداری", description: "برای LanceDB نیازی به پیکربندی نیست.", @@ -374,7 +414,8 @@ const TRANSLATIONS = { // Embeddable Chat Widgets embeddable: { title: "جاسازی گفتگو", - description: "جاسازی گفتگو به شما امکان می‌دهد گفتگوی فضای کاری را در وب‌سایت یا برنامه خود قرار دهید.", + description: + "جاسازی گفتگو به شما امکان می‌دهد گفتگوی فضای کاری را در وب‌سایت یا برنامه خود قرار دهید.", create: "ایجاد جاسازی جدید", table: { workspace: "فضای کاری", @@ -386,7 +427,8 @@ const TRANSLATIONS = { "embed-chats": { title: "گفتگوهای جاسازی شده", export: "خروجی‌گیری", - description: "این لیست تمام گفتگوها و پیام‌های ثبت شده از هر جاسازی که منتشر کرده‌اید را نشان می‌دهد.", + description: + "این لیست تمام گفتگوها و پیام‌های ثبت شده از هر جاسازی که منتشر کرده‌اید را نشان می‌دهد.", table: { embed: "جاسازی", sender: "فرستنده", @@ -398,21 +440,25 @@ const TRANSLATIONS = { multi: { title: "حالت چند کاربره", - description: "نمونه خود را برای پشتیبانی از تیم خود با فعال‌سازی حالت چند کاربره تنظیم کنید.", + description: + "نمونه خود را برای پشتیبانی از تیم خود با فعال‌سازی حالت چند کاربره تنظیم کنید.", enable: { "is-enable": "حالت چند کاربره فعال است", enable: "فعال‌سازی حالت چند کاربره", - description: "به طور پیش‌فرض، شما تنها مدیر خواهید بود. به عنوان مدیر، باید برای تمام کاربران یا مدیران جدید حساب کاربری ایجاد کنید. رمز عبور خود را گم نکنید زیرا فقط یک کاربر مدیر می‌تواند رمزهای عبور را بازنشانی کند.", + description: + "به طور پیش‌فرض، شما تنها مدیر خواهید بود. به عنوان مدیر، باید برای تمام کاربران یا مدیران جدید حساب کاربری ایجاد کنید. رمز عبور خود را گم نکنید زیرا فقط یک کاربر مدیر می‌تواند رمزهای عبور را بازنشانی کند.", username: "نام کاربری حساب مدیر", password: "رمز عبور حساب مدیر", }, password: { title: "حفاظت با رمز عبور", - description: "از نمونه AnythingLLM خود با رمز عبور محافظت کنید. اگر این رمز را فراموش کنید هیچ روش بازیابی وجود ندارد، پس حتماً این رمز عبور را ذخیره کنید.", + description: + "از نمونه AnythingLLM خود با رمز عبور محافظت کنید. اگر این رمز را فراموش کنید هیچ روش بازیابی وجود ندارد، پس حتماً این رمز عبور را ذخیره کنید.", }, instance: { title: "محافظت از نمونه با رمز عبور", - description: "به طور پیش‌فرض، شما تنها مدیر خواهید بود. به عنوان مدیر، باید برای تمام کاربران یا مدیران جدید حساب کاربری ایجاد کنید. رمز عبور خود را گم نکنید زیرا فقط یک کاربر مدیر می‌تواند رمزهای عبور را بازنشانی کند.", + description: + "به طور پیش‌فرض، شما تنها مدیر خواهید بود. به عنوان مدیر، باید برای تمام کاربران یا مدیران جدید حساب کاربری ایجاد کنید. رمز عبور خود را گم نکنید زیرا فقط یک کاربر مدیر می‌تواند رمزهای عبور را بازنشانی کند.", password: "رمز عبور نمونه", }, }, @@ -420,7 +466,8 @@ const TRANSLATIONS = { // Event Logs event: { title: "گزارش رویدادها", - description: "مشاهده تمام اقدامات و رویدادهای در حال وقوع در این نمونه برای نظارت.", + description: + "مشاهده تمام اقدامات و رویدادهای در حال وقوع در این نمونه برای نظارت.", clear: "پاک کردن گزارش رویدادها", table: { type: "نوع رویداد", @@ -432,7 +479,8 @@ const TRANSLATIONS = { // Privacy & Data-Handling privacy: { title: "حریم خصوصی و مدیریت داده‌ها", - description: "این پیکربندی شما برای نحوه مدیریت داده‌ها توسط ارائه‌دهندگان شخص ثالث متصل و AnythingLLM است.", + description: + "این پیکربندی شما برای نحوه مدیریت داده‌ها توسط ارائه‌دهندگان شخص ثالث متصل و AnythingLLM است.", llm: "انتخاب مدل زبانی", embedding: "ترجیحات جاسازی", vector: "پایگاه داده برداری", diff --git a/frontend/src/locales/resources.js b/frontend/src/locales/resources.js index f0ce5b393c4..ce8aed3165f 100644 --- a/frontend/src/locales/resources.js +++ b/frontend/src/locales/resources.js @@ -29,7 +29,6 @@ import Vietnamese from "./vn/common.js"; import TraditionalChinese from "./zh_TW/common.js"; import Farsi from "./fa/common.js"; - export const defaultNS = "common"; export const resources = { en: { diff --git a/frontend/src/utils/chat/agent.js b/frontend/src/utils/chat/agent.js index babe55605f4..ad1193d304c 100644 --- a/frontend/src/utils/chat/agent.js +++ b/frontend/src/utils/chat/agent.js @@ -99,7 +99,7 @@ export default function handleSocketResponse(event, setChatHistory) { sources: [], closed: true, error: null, - animate: false, + animate: data?.animate || false, pending: false, }, ]; diff --git a/frontend/src/utils/chat/index.js b/frontend/src/utils/chat/index.js index 86c1cd4208c..abde54fe9c2 100644 --- a/frontend/src/utils/chat/index.js +++ b/frontend/src/utils/chat/index.js @@ -17,6 +17,7 @@ export default function handleChat( sources = [], error, close, + animate = false, chatId = null, action = null, metrics = {}, @@ -34,7 +35,7 @@ export default function handleChat( sources, closed: true, error, - animate: false, + animate, pending: false, metrics, }, @@ -47,7 +48,7 @@ export default function handleChat( sources, closed: true, error, - animate: false, + animate, pending: false, metrics, }); diff --git a/server/utils/agents/aibitat/plugins/websocket.js b/server/utils/agents/aibitat/plugins/websocket.js index 8c8800ff3e2..a253bd010e9 100644 --- a/server/utils/agents/aibitat/plugins/websocket.js +++ b/server/utils/agents/aibitat/plugins/websocket.js @@ -68,7 +68,11 @@ const websocket = { aibitat.introspect = (messageText) => { if (!introspection) return; // Dump thoughts when not wanted. socket.send( - JSON.stringify({ type: "statusResponse", content: messageText }) + JSON.stringify({ + type: "statusResponse", + content: messageText, + animate: true, + }) ); }; diff --git a/server/utils/agents/ephemeral.js b/server/utils/agents/ephemeral.js index d8258eca800..035de8ffe34 100644 --- a/server/utils/agents/ephemeral.js +++ b/server/utils/agents/ephemeral.js @@ -412,6 +412,7 @@ class EphemeralEventListener extends EventEmitter { attachments: [], close: false, error: null, + animate: true, }); } @@ -423,6 +424,7 @@ class EphemeralEventListener extends EventEmitter { attachments: [], close: true, error: null, + animate: false, }); }; this.on("chunk", onChunkHandler); diff --git a/server/utils/chats/agents.js b/server/utils/chats/agents.js index cd127d07f25..26de10e8ace 100644 --- a/server/utils/chats/agents.js +++ b/server/utils/chats/agents.js @@ -33,6 +33,7 @@ async function grepAgents({ )} could not be called. Chat will be handled as default chat.`, sources: [], close: true, + animate: false, error: null, }); return; @@ -61,6 +62,7 @@ async function grepAgents({ sources: [], close: true, error: null, + animate: true, }); return true; } From 227dcfe9f1ca8429b776085668e40de96b1e045d Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Tue, 21 Jan 2025 17:33:58 -0800 Subject: [PATCH 3/5] linting --- .../ChatContainer/ChatHistory/index.jsx | 24 +++- frontend/src/index.css | 122 +----------------- 2 files changed, 24 insertions(+), 122 deletions(-) diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx index 1fb8a49e02a..6d65c2abf31 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx @@ -176,14 +176,13 @@ export default function ChatHistory({ ); } - const hhistory = buildMessages({ + const compiledHistory = buildMessages({ workspace, history, regenerateAssistantMessage, saveEditedMessage, forkThread, }); - return (
- {hhistory.map((item, index) => { + {compiledHistory.map((item, index) => { if (Array.isArray(item)) { - const lastMessage = history?.[history.length - 1] || {}; - const hasSubsequentMessages = index < hhistory.length - 1; + const lastMessage = + compiledHistory?.[compiledHistory.length - 1] || {}; + const hasSubsequentMessages = index < compiledHistory.length - 1; return ( Date: Tue, 21 Jan 2025 17:37:49 -0800 Subject: [PATCH 4/5] simplify css --- .../WorkspaceChat/ChatContainer/ChatHistory/index.jsx | 3 +-- frontend/src/index.css | 10 ---------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx index 6d65c2abf31..13017c11644 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx @@ -192,8 +192,7 @@ export default function ChatHistory({ > {compiledHistory.map((item, index) => { if (Array.isArray(item)) { - const lastMessage = - compiledHistory?.[compiledHistory.length - 1] || {}; + const lastMessage = history?.[history.length - 1] || {}; const hasSubsequentMessages = index < compiledHistory.length - 1; return ( Date: Wed, 22 Jan 2025 11:07:04 -0800 Subject: [PATCH 5/5] memoize agent responses --- .../ChatContainer/ChatHistory/index.jsx | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx index 13017c11644..819a6f1c261 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, useMemo, useCallback } from "react"; import HistoricalMessage from "./HistoricalMessage"; import PromptReply from "./PromptReply"; import StatusResponse from "./StatusResponse"; @@ -176,13 +176,42 @@ export default function ChatHistory({ ); } - const compiledHistory = buildMessages({ - workspace, - history, - regenerateAssistantMessage, - saveEditedMessage, - forkThread, - }); + const compiledHistory = useMemo( + () => + buildMessages({ + workspace, + history, + regenerateAssistantMessage, + saveEditedMessage, + forkThread, + }), + [ + workspace, + history, + regenerateAssistantMessage, + saveEditedMessage, + forkThread, + ] + ); + const lastMessageInfo = useMemo(() => getLastMessageInfo(history), [history]); + const renderStatusResponse = useCallback( + (item, index) => { + const hasSubsequentMessages = index < compiledHistory.length - 1; + return ( + + ); + }, + [compiledHistory.length, lastMessageInfo] + ); + return (
- {compiledHistory.map((item, index) => { - if (Array.isArray(item)) { - const lastMessage = history?.[history.length - 1] || {}; - const hasSubsequentMessages = index < compiledHistory.length - 1; - return ( - - ); - } - return item; - })} + {compiledHistory.map((item, index) => + Array.isArray(item) ? renderStatusResponse(item, index) : item + )} {showing && ( )} @@ -231,6 +244,14 @@ export default function ChatHistory({ ); } +const getLastMessageInfo = (history) => { + const lastMessage = history?.[history.length - 1] || {}; + return { + isAnimating: lastMessage?.animate, + isStatusResponse: lastMessage?.type === "statusResponse", + }; +}; + function WorkspaceChatSuggestions({ suggestions = [], sendSuggestion }) { if (suggestions.length === 0) return null; return (