mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-20 23:21:06 +02:00
Merge pull request #1536 from CREDO23/feature-mention-chat-in-chat
[Feat] Chat : Reference past chats via @-mention as read-only context
This commit is contained in:
commit
96e42a1003
18 changed files with 835 additions and 74 deletions
|
|
@ -1,6 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { Folder as FolderIcon, Plug as PlugIcon, X as XIcon } from "lucide-react";
|
||||
import {
|
||||
Folder as FolderIcon,
|
||||
MessageSquare as MessageSquareIcon,
|
||||
Plug as PlugIcon,
|
||||
X as XIcon,
|
||||
} from "lucide-react";
|
||||
import type { NodeEntry, TElement } from "platejs";
|
||||
import type { PlateElementProps } from "platejs/react";
|
||||
import {
|
||||
|
|
@ -26,7 +31,7 @@ import type { Document } from "@/contracts/types/document.types";
|
|||
import { getMentionDocKey } from "@/lib/chat/mention-doc-key";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export type MentionKind = "doc" | "folder" | "connector";
|
||||
export type MentionKind = "doc" | "folder" | "connector" | "thread";
|
||||
|
||||
export interface MentionedDocument {
|
||||
id: number;
|
||||
|
|
@ -165,6 +170,7 @@ const MentionElement: FC<PlateElementProps<MentionElementNode>> = ({
|
|||
|
||||
const isFolder = element.kind === "folder";
|
||||
const isConnector = element.kind === "connector";
|
||||
const isThread = element.kind === "thread";
|
||||
const ctx = useContext(MentionEditorContext);
|
||||
|
||||
return (
|
||||
|
|
@ -175,6 +181,8 @@ const MentionElement: FC<PlateElementProps<MentionElementNode>> = ({
|
|||
<span className="flex items-center justify-center transition-opacity group-hover:opacity-0">
|
||||
{isFolder ? (
|
||||
<FolderIcon className="h-3 w-3" />
|
||||
) : isThread ? (
|
||||
<MessageSquareIcon className="h-3 w-3" />
|
||||
) : isConnector ? (
|
||||
(getConnectorIcon(
|
||||
element.connector_type ?? element.document_type ?? "UNKNOWN",
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import { currentThreadAtom } from "@/atoms/chat/current-thread.atom";
|
|||
import {
|
||||
type MentionedDocumentInfo,
|
||||
mentionedDocumentsAtom,
|
||||
submittedMentionsAtom,
|
||||
} from "@/atoms/chat/mentioned-documents.atom";
|
||||
import { pendingUserImageDataUrlsAtom } from "@/atoms/chat/pending-user-images.atom";
|
||||
import {
|
||||
|
|
@ -446,6 +447,7 @@ const ClipboardChip: FC<{ text: string; onDismiss: () => void }> = ({ text, onDi
|
|||
|
||||
const Composer: FC = () => {
|
||||
const [mentionedDocuments, setMentionedDocuments] = useAtom(mentionedDocumentsAtom);
|
||||
const setSubmittedMentions = useSetAtom(submittedMentionsAtom);
|
||||
const [showDocumentPopover, setShowDocumentPopover] = useState(false);
|
||||
const [showPromptPicker, setShowPromptPicker] = useState(false);
|
||||
const [mentionQuery, setMentionQuery] = useState("");
|
||||
|
|
@ -575,6 +577,13 @@ const Composer: FC = () => {
|
|||
kind: "folder",
|
||||
};
|
||||
}
|
||||
if (d.kind === "thread") {
|
||||
return {
|
||||
id: d.id,
|
||||
title: d.title,
|
||||
kind: "thread",
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: d.id,
|
||||
title: d.title,
|
||||
|
|
@ -770,6 +779,10 @@ const Composer: FC = () => {
|
|||
setClipboardInitialText(undefined);
|
||||
}
|
||||
|
||||
// Capture chips before the reset below clears the live atom, so
|
||||
// the async ``onNew`` still sees them.
|
||||
setSubmittedMentions(mentionedDocuments);
|
||||
|
||||
aui.composer().send();
|
||||
editorRef.current?.clear();
|
||||
setIsComposerInputEmpty(true);
|
||||
|
|
@ -781,6 +794,8 @@ const Composer: FC = () => {
|
|||
isBlockedByOtherUser,
|
||||
clipboardInitialText,
|
||||
aui,
|
||||
mentionedDocuments,
|
||||
setSubmittedMentions,
|
||||
setMentionedDocuments,
|
||||
]);
|
||||
|
||||
|
|
@ -788,7 +803,7 @@ const Composer: FC = () => {
|
|||
(
|
||||
docId: number,
|
||||
docType?: string,
|
||||
kind?: "doc" | "folder" | "connector",
|
||||
kind?: "doc" | "folder" | "connector" | "thread",
|
||||
connectorType?: string
|
||||
) => {
|
||||
setMentionedDocuments((prev) => {
|
||||
|
|
@ -876,6 +891,8 @@ const Composer: FC = () => {
|
|||
<DocumentMentionPicker
|
||||
ref={documentPickerRef}
|
||||
searchSpaceId={Number(search_space_id)}
|
||||
enableChatMentions
|
||||
currentChatId={threadId}
|
||||
onSelectionChange={handleDocumentsMention}
|
||||
onDone={() => {
|
||||
setShowDocumentPopover(false);
|
||||
|
|
|
|||
|
|
@ -6,9 +6,16 @@ import {
|
|||
useMessagePartText,
|
||||
} from "@assistant-ui/react";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { CheckIcon, CopyIcon, Folder as FolderIcon, Pencil, Plug } from "lucide-react";
|
||||
import {
|
||||
CheckIcon,
|
||||
CopyIcon,
|
||||
Folder as FolderIcon,
|
||||
MessageSquare,
|
||||
Pencil,
|
||||
Plug,
|
||||
} from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { type FC, useCallback, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { currentThreadAtom } from "@/atoms/chat/current-thread.atom";
|
||||
|
|
@ -66,6 +73,7 @@ const UserTextPart: FC = () => {
|
|||
const messageDocumentsMap = useAtomValue(messageDocumentsMapAtom);
|
||||
const mentionedDocs = (messageId ? messageDocumentsMap[messageId] : undefined) ?? [];
|
||||
const openEditorPanel = useSetAtom(openEditorPanelAtom);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const searchSpaceIdParam = params?.search_space_id;
|
||||
const parsedSearchSpaceId = Array.isArray(searchSpaceIdParam)
|
||||
|
|
@ -91,6 +99,17 @@ const UserTextPart: FC = () => {
|
|||
[openEditorPanel, resolvedSearchSpaceId]
|
||||
);
|
||||
|
||||
const handleOpenThread = useCallback(
|
||||
(threadId: number) => {
|
||||
if (!resolvedSearchSpaceId) {
|
||||
toast.error("Cannot open chat outside a search space.");
|
||||
return;
|
||||
}
|
||||
router.push(`/dashboard/${resolvedSearchSpaceId}/new-chat/${threadId}`);
|
||||
},
|
||||
[resolvedSearchSpaceId, router]
|
||||
);
|
||||
|
||||
const segments = parseMentionSegments(text, mentionedDocs);
|
||||
|
||||
return (
|
||||
|
|
@ -101,8 +120,11 @@ const UserTextPart: FC = () => {
|
|||
}
|
||||
const isFolder = segment.doc.kind === "folder";
|
||||
const isConnector = segment.doc.kind === "connector";
|
||||
const isThread = segment.doc.kind === "thread";
|
||||
const icon = isFolder ? (
|
||||
<FolderIcon className="size-3.5" />
|
||||
) : isThread ? (
|
||||
<MessageSquare className="size-3.5" />
|
||||
) : isConnector ? (
|
||||
(getConnectorIcon(segment.doc.connector_type, "size-3.5") ?? (
|
||||
<Plug className="size-3.5" />
|
||||
|
|
@ -118,14 +140,18 @@ const UserTextPart: FC = () => {
|
|||
tooltip={
|
||||
isFolder
|
||||
? `Folder: ${segment.doc.title}`
|
||||
: isConnector
|
||||
? `Connector account: ${segment.doc.title}`
|
||||
: segment.doc.title
|
||||
: isThread
|
||||
? `Chat: ${segment.doc.title}`
|
||||
: isConnector
|
||||
? `Connector account: ${segment.doc.title}`
|
||||
: segment.doc.title
|
||||
}
|
||||
onClick={
|
||||
isFolder || isConnector
|
||||
? undefined
|
||||
: () => handleOpenDoc(segment.doc.id, segment.doc.title)
|
||||
isThread
|
||||
? () => handleOpenThread(segment.doc.id)
|
||||
: isFolder || isConnector
|
||||
? undefined
|
||||
: () => handleOpenDoc(segment.doc.id, segment.doc.title)
|
||||
}
|
||||
className="mx-0.5"
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue