mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-15 00:11:15 +03:00
Wire event attachments into chat composer and messages
This commit is contained in:
parent
d62ae17fcc
commit
01519607de
@ -3,16 +3,20 @@ import { Input } from "@/components/ui/input";
|
||||
import { FaArrowUpLong } from "react-icons/fa6";
|
||||
import { LuCircleAlert } from "react-icons/lu";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState, useCallback, useRef, useEffect } from "react";
|
||||
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
||||
import axios from "axios";
|
||||
import { ChatEventThumbnailsRow } from "@/components/chat/ChatEventThumbnailsRow";
|
||||
import { MessageBubble } from "@/components/chat/ChatMessage";
|
||||
import { ToolCallsGroup } from "@/components/chat/ToolCallsGroup";
|
||||
import { ChatStartingState } from "@/components/chat/ChatStartingState";
|
||||
import { ChatAttachmentChip } from "@/components/chat/ChatAttachmentChip";
|
||||
import { ChatQuickReplies } from "@/components/chat/ChatQuickReplies";
|
||||
import { ChatPaperclipButton } from "@/components/chat/ChatPaperclipButton";
|
||||
import type { ChatMessage } from "@/types/chat";
|
||||
import {
|
||||
getEventIdsFromSearchObjectsToolCalls,
|
||||
getFindSimilarObjectsFromToolCalls,
|
||||
prependAttachment,
|
||||
streamChatCompletion,
|
||||
} from "@/utils/chatUtil";
|
||||
|
||||
@ -22,6 +26,7 @@ export default function ChatPage() {
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [attachedEventId, setAttachedEventId] = useState<string | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@ -75,12 +80,31 @@ export default function ChatPage() {
|
||||
[isLoading, t],
|
||||
);
|
||||
|
||||
const sendMessage = useCallback(() => {
|
||||
const text = input.trim();
|
||||
if (!text || isLoading) return;
|
||||
setInput("");
|
||||
submitConversation([...messages, { role: "user", content: text }]);
|
||||
}, [input, isLoading, messages, submitConversation]);
|
||||
const recentEventIds = useMemo(() => {
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const msg = messages[i];
|
||||
if (msg.role !== "assistant" || !msg.toolCalls) continue;
|
||||
const similar = getFindSimilarObjectsFromToolCalls(msg.toolCalls);
|
||||
if (similar) return similar.results.map((e) => e.id);
|
||||
const events = getEventIdsFromSearchObjectsToolCalls(msg.toolCalls);
|
||||
if (events.length > 0) return events.map((e) => e.id);
|
||||
}
|
||||
return [];
|
||||
}, [messages]);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
(textOverride?: string) => {
|
||||
const text = (textOverride ?? input).trim();
|
||||
if (!text || isLoading) return;
|
||||
const wireText = attachedEventId
|
||||
? prependAttachment(text, attachedEventId)
|
||||
: text;
|
||||
setInput("");
|
||||
setAttachedEventId(null);
|
||||
submitConversation([...messages, { role: "user", content: wireText }]);
|
||||
},
|
||||
[attachedEventId, input, isLoading, messages, submitConversation],
|
||||
);
|
||||
|
||||
const handleEditSubmit = useCallback(
|
||||
(messageIndex: number, newContent: string) => {
|
||||
@ -93,6 +117,10 @@ export default function ChatPage() {
|
||||
[messages, submitConversation],
|
||||
);
|
||||
|
||||
const handleClearAttachment = useCallback(() => {
|
||||
setAttachedEventId(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex size-full justify-center p-2 md:p-4">
|
||||
<div className="flex size-full flex-col xl:w-[50%] 3xl:w-[35%]">
|
||||
@ -170,13 +198,19 @@ export default function ChatPage() {
|
||||
<ChatEventThumbnailsRow
|
||||
events={similar.results}
|
||||
anchor={similar.anchor}
|
||||
onAttach={setAttachedEventId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const events = getEventIdsFromSearchObjectsToolCalls(
|
||||
msg.toolCalls,
|
||||
);
|
||||
return <ChatEventThumbnailsRow events={events} />;
|
||||
return (
|
||||
<ChatEventThumbnailsRow
|
||||
events={events}
|
||||
onAttach={setAttachedEventId}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
@ -200,6 +234,10 @@ export default function ChatPage() {
|
||||
sendMessage={sendMessage}
|
||||
isLoading={isLoading}
|
||||
placeholder={t("placeholder")}
|
||||
attachedEventId={attachedEventId}
|
||||
onClearAttachment={handleClearAttachment}
|
||||
onAttach={setAttachedEventId}
|
||||
recentEventIds={recentEventIds}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -210,9 +248,13 @@ export default function ChatPage() {
|
||||
type ChatEntryProps = {
|
||||
input: string;
|
||||
setInput: (value: string) => void;
|
||||
sendMessage: () => void;
|
||||
sendMessage: (textOverride?: string) => void;
|
||||
isLoading: boolean;
|
||||
placeholder: string;
|
||||
attachedEventId: string | null;
|
||||
onClearAttachment: () => void;
|
||||
onAttach: (eventId: string) => void;
|
||||
recentEventIds: string[];
|
||||
};
|
||||
|
||||
function ChatEntry({
|
||||
@ -221,6 +263,10 @@ function ChatEntry({
|
||||
sendMessage,
|
||||
isLoading,
|
||||
placeholder,
|
||||
attachedEventId,
|
||||
onClearAttachment,
|
||||
onAttach,
|
||||
recentEventIds,
|
||||
}: ChatEntryProps) {
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
@ -230,8 +276,28 @@ function ChatEntry({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-2 flex w-full flex-col items-center justify-center rounded-xl bg-secondary p-3">
|
||||
<div className="mt-2 flex w-full flex-col items-stretch justify-center gap-2 rounded-xl bg-secondary p-3">
|
||||
{attachedEventId && (
|
||||
<div className="flex items-center">
|
||||
<ChatAttachmentChip
|
||||
eventId={attachedEventId}
|
||||
mode="composer"
|
||||
onRemove={onClearAttachment}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{attachedEventId && (
|
||||
<ChatQuickReplies
|
||||
onSend={(text) => sendMessage(text)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
)}
|
||||
<div className="flex w-full flex-row items-center gap-2">
|
||||
<ChatPaperclipButton
|
||||
recentEventIds={recentEventIds}
|
||||
onAttach={onAttach}
|
||||
disabled={isLoading || attachedEventId != null}
|
||||
/>
|
||||
<Input
|
||||
className="w-full flex-1 border-transparent bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent"
|
||||
placeholder={placeholder}
|
||||
@ -244,7 +310,7 @@ function ChatEntry({
|
||||
variant="select"
|
||||
className="size-10 shrink-0 rounded-full"
|
||||
disabled={!input.trim() || isLoading}
|
||||
onClick={sendMessage}
|
||||
onClick={() => sendMessage()}
|
||||
>
|
||||
<FaArrowUpLong size="16" />
|
||||
</Button>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user