chore: message icon update & message history (#239)

This commit is contained in:
BiggerRain
2025-03-04 21:20:48 +08:00
committed by GitHub
parent 8d1bd7df6b
commit 3aa433f1da
12 changed files with 447 additions and 34 deletions

View File

@@ -554,6 +554,7 @@ const ChatAI = memo(
...chat,
messages: hits,
};
console.log("id_history2", updatedChat)
setActiveChat(updatedChat);
callback && callback(updatedChat)
} catch (error) {

View File

@@ -14,6 +14,7 @@ export interface ISource {
message?: any;
title?: string;
question?: string;
details?: any[];
}
export interface Chat {
_id: string;

View File

@@ -1,14 +1,16 @@
import { ChevronDown, ChevronUp, Loader, BadgeCheck } from "lucide-react";
import { ChevronDown, ChevronUp, Loader } from "lucide-react";
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import type { IChunkData } from "@/components/Assistant/types";
import ReadingIcon from "@/icons/Reading";
interface DeepReadeProps {
Detail?: any;
ChunkData?: IChunkData;
}
export const DeepRead = ({ ChunkData }: DeepReadeProps) => {
export const DeepRead = ({ Detail, ChunkData }: DeepReadeProps) => {
const { t } = useTranslation();
const [isThinkingExpanded, setIsThinkingExpanded] = useState(false);
@@ -18,6 +20,12 @@ export const DeepRead = ({ ChunkData }: DeepReadeProps) => {
const [Data, setData] = useState<string[]>([]);
useEffect(() => {
if (!Detail?.description) return;
setData(Detail?.description);
setLoading(false);
}, [Detail?.description]);
useEffect(() => {
if (!ChunkData?.message_chunk) return;
const timerID = setTimeout(() => {
@@ -47,7 +55,7 @@ export const DeepRead = ({ ChunkData }: DeepReadeProps) => {
}, [ChunkData?.message_chunk]);
// Must be after hooks
if (!ChunkData) return null;
if (!ChunkData && !Detail) return null;
return (
<div className="space-y-2 mb-3 w-full">
@@ -64,7 +72,7 @@ export const DeepRead = ({ ChunkData }: DeepReadeProps) => {
</>
) : (
<>
<BadgeCheck className="w-4 h-4 text-[#38C200]" />
<ReadingIcon className="w-4 h-4 text-[#38C200]" />
<span className="text-xs text-[#999999]">
{t(`assistant.message.steps.${ChunkData?.chunk_type}`, {
count: Number(Data.length),

View File

@@ -1,5 +1,4 @@
import {
Search,
ChevronUp,
ChevronDown,
SquareArrowOutUpRight,
@@ -10,8 +9,10 @@ import { useTranslation } from "react-i18next";
import { OpenURLWithBrowser } from "@/utils/index";
import type { IChunkData } from "@/components/Assistant/types";
import RetrieveIcon from "@/icons/Retrieve";
interface FetchSourceProps {
Detail?: any;
ChunkData?: IChunkData;
}
@@ -32,13 +33,18 @@ interface ISourceData {
url: string;
}
export const FetchSource = ({ ChunkData }: FetchSourceProps) => {
export const FetchSource = ({ Detail, ChunkData }: FetchSourceProps) => {
const { t } = useTranslation();
const [isSourceExpanded, setIsSourceExpanded] = useState(false);
const [total, setTotal] = useState(0);
const [data, setData] = useState<ISourceData[]>([]);
useEffect(() => {
if (!Detail?.payload) return;
setData(Detail?.payload);
}, [Detail?.payload]);
useEffect(() => {
if (!ChunkData?.message_chunk) return;
@@ -61,7 +67,7 @@ export const FetchSource = ({ ChunkData }: FetchSourceProps) => {
}, [ChunkData?.message_chunk]);
// Must be after hooks
if (!ChunkData) return null;
if (!ChunkData && !Detail) return null;
return (
<div
@@ -80,7 +86,7 @@ export const FetchSource = ({ ChunkData }: FetchSourceProps) => {
}`}
>
<div className="flex-1 min-w-0 flex items-center gap-2">
<Search className="w-4 h-4 text-[#38C200] flex-shrink-0" />
<RetrieveIcon className="w-4 h-4 text-[#38C200] flex-shrink-0" />
<span className="text-xs text-[#999999]">
{t(`assistant.message.steps.${ChunkData?.chunk_type}`, {
count: Number(total),

View File

@@ -1,10 +1,12 @@
import { ChevronDown, ChevronUp, Loader, BadgeCheck } from "lucide-react";
import { ChevronDown, ChevronUp, Loader } from "lucide-react";
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import type { IChunkData } from "@/components/Assistant/types";
import SelectionIcon from "@/icons/Selection";
interface PickSourceProps {
Detail?: any;
ChunkData?: IChunkData;
}
@@ -14,7 +16,7 @@ interface IData {
title: string;
}
export const PickSource = ({ ChunkData }: PickSourceProps) => {
export const PickSource = ({ Detail, ChunkData }: PickSourceProps) => {
const { t } = useTranslation();
const [isThinkingExpanded, setIsThinkingExpanded] = useState(false);
@@ -24,6 +26,12 @@ export const PickSource = ({ ChunkData }: PickSourceProps) => {
const [Data, setData] = useState<IData[]>([]);
useEffect(() => {
if (!Detail?.payload) return;
setData(Detail?.payload);
setLoading(false)
}, [Detail?.payload]);
useEffect(() => {
if (!ChunkData?.message_chunk) return;
@@ -78,7 +86,7 @@ export const PickSource = ({ ChunkData }: PickSourceProps) => {
}, [ChunkData?.message_chunk]);
// Must be after hooks
if (!ChunkData) return null;
if (!ChunkData && !Detail) return null;
return (
<div className="space-y-2 mb-3 w-full">
@@ -95,7 +103,7 @@ export const PickSource = ({ ChunkData }: PickSourceProps) => {
</>
) : (
<>
<BadgeCheck className="w-4 h-4 text-[#38C200]" />
<SelectionIcon className="w-4 h-4 text-[#38C200]" />
<span className="text-xs text-[#999999]">
{t(`assistant.message.steps.${ChunkData?.chunk_type}`, {
count: Data?.length,

View File

@@ -1,10 +1,12 @@
import { ChevronDown, ChevronUp, Loader, BadgeCheck } from "lucide-react";
import { ChevronDown, ChevronUp, Loader } from "lucide-react";
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import type { IChunkData } from "@/components/Assistant/types";
import UnderstandIcon from "@/icons/Understand";
interface QueryIntentProps {
Detail?: any;
ChunkData?: IChunkData;
getSuggestion?: (suggestion: string[]) => void;
}
@@ -17,7 +19,11 @@ interface IQueryData {
suggestion: string[];
}
export const QueryIntent = ({ ChunkData, getSuggestion }: QueryIntentProps) => {
export const QueryIntent = ({
Detail,
ChunkData,
getSuggestion,
}: QueryIntentProps) => {
const { t } = useTranslation();
const [isThinkingExpanded, setIsThinkingExpanded] = useState(false);
@@ -27,6 +33,12 @@ export const QueryIntent = ({ ChunkData, getSuggestion }: QueryIntentProps) => {
const [Data, setData] = useState<IQueryData | null>(null);
useEffect(() => {
if (!Detail?.payload) return;
setData(Detail?.payload);
setLoading(false);
}, [Detail?.payload]);
useEffect(() => {
if (!ChunkData?.message_chunk) return;
const timerID = setTimeout(() => {
@@ -39,7 +51,7 @@ export const QueryIntent = ({ ChunkData, getSuggestion }: QueryIntentProps) => {
const data = JSON.parse(jsonString);
// console.log("QueryIntent", data);
if (data?.suggestion && getSuggestion) {
getSuggestion(data?.suggestion)
getSuggestion(data?.suggestion);
}
setData(data);
}
@@ -62,7 +74,7 @@ export const QueryIntent = ({ ChunkData, getSuggestion }: QueryIntentProps) => {
}, [ChunkData?.message_chunk]);
// Must be after hooks
if (!ChunkData) return null;
if (!ChunkData && !Detail) return null;
return (
<div className="space-y-2 mb-3 w-full">
@@ -74,14 +86,14 @@ export const QueryIntent = ({ ChunkData, getSuggestion }: QueryIntentProps) => {
<>
<Loader className="w-4 h-4 animate-spin text-[#1990FF]" />
<span className="text-xs text-[#999999] italic">
{t(`assistant.message.steps.${ChunkData?.chunk_type}`)}
{t(`assistant.message.steps.${ChunkData?.chunk_type || Detail.type}`)}
</span>
</>
) : (
<>
<BadgeCheck className="w-4 h-4 text-[#38C200]" />
<UnderstandIcon className="w-4 h-4 text-[#38C200]" />
<span className="text-xs text-[#999999]">
{t(`assistant.message.steps.${ChunkData?.chunk_type}`)}
{t(`assistant.message.steps.${ChunkData?.chunk_type || Detail.type}`)}
</span>
</>
)}

View File

@@ -5,16 +5,23 @@ import { useTranslation } from "react-i18next";
import type { IChunkData } from "@/components/Assistant/types";
interface ThinkProps {
Detail?: any;
ChunkData?: IChunkData;
}
export const Think = ({ ChunkData }: ThinkProps) => {
export const Think = ({ Detail, ChunkData }: ThinkProps) => {
const { t } = useTranslation();
const [isThinkingExpanded, setIsThinkingExpanded] = useState(true);
const [loading, setLoading] = useState(true);
const [Data, setData] = useState("");
useEffect(() => {
if (!Detail?.description) return;
setData(Detail?.description);
setLoading(false);
}, [Detail?.description]);
useEffect(() => {
if (!ChunkData?.message_chunk) return;
const timerID = setTimeout(() => {
@@ -30,7 +37,7 @@ export const Think = ({ ChunkData }: ThinkProps) => {
}, [ChunkData?.message_chunk, Data, loading]);
// Must be after hooks
if (!ChunkData) return null;
if (!ChunkData && !Detail) return null;
return (
<div className="space-y-2 mb-3 w-full">
@@ -40,7 +47,7 @@ export const Think = ({ ChunkData }: ThinkProps) => {
>
{loading ? (
<>
<Brain className="w-4 h-4 animate-pulse text-[#999999]" />
<Brain className="w-4 h-4 animate-pulse text-[#1990FF]" />
<span className="text-xs text-[#999999] italic">
{t(`assistant.message.steps.${ChunkData?.chunk_type}`)}
</span>

View File

@@ -39,6 +39,7 @@ export const ChatMessage = memo(function ChatMessage({
const isAssistant = message?._source?.type === "assistant";
const messageContent = message?._source?.message || "";
const details = message?._source?.details || [];
const question = message?._source?.question || "";
const showActions =
@@ -47,9 +48,8 @@ export const ChatMessage = memo(function ChatMessage({
const [suggestion, setSuggestion] = useState<string[]>([]);
const getSuggestion = (suggestion: string[]) => {
setSuggestion(suggestion)
}
setSuggestion(suggestion);
};
const renderContent = () => {
if (!isAssistant) {
@@ -62,11 +62,27 @@ export const ChatMessage = memo(function ChatMessage({
return (
<>
<QueryIntent ChunkData={query_intent} getSuggestion={getSuggestion}/>
<FetchSource ChunkData={fetch_source} />
<PickSource ChunkData={pick_source} />
<DeepRead ChunkData={deep_read} />
<Think ChunkData={think} />
<QueryIntent
Detail={details.find((item) => item.type === "query_intent")}
ChunkData={query_intent}
getSuggestion={getSuggestion}
/>
<FetchSource
Detail={details.find((item) => item.type === "fetch_source")}
ChunkData={fetch_source}
/>
<PickSource
Detail={details.find((item) => item.type === "pick_source")}
ChunkData={pick_source}
/>
<DeepRead
Detail={details.find((item) => item.type === "deep_read")}
ChunkData={deep_read}
/>
<Think
Detail={details.find((item) => item.type === "think")}
ChunkData={think}
/>
<Markdown
content={messageContent || response?.message_chunk || ""}
loading={isTyping}
@@ -85,10 +101,12 @@ export const ChatMessage = memo(function ChatMessage({
}}
/>
)}
{!isTyping && <SuggestionList
suggestions={suggestion}
onSelect={(text) => onResend && onResend(text)}
/>}
{!isTyping && (
<SuggestionList
suggestions={suggestion}
onSelect={(text) => onResend && onResend(text)}
/>
)}
</>
);
};

104
src/icons/Reading.tsx Normal file
View File

@@ -0,0 +1,104 @@
import SVGWrap from "./SVGWrap";
export default function Reading(props: I.SVG) {
return (
<SVGWrap {...props} viewBox="0 0 16 16">
<g
id="deading"
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
>
<circle
id="椭圆形"
stroke="currentColor"
stroke-width="1.25"
cx="8"
cy="3"
r="1.375"
></circle>
<circle
id="椭圆形备份-2"
stroke="currentColor"
stroke-width="1.25"
cx="3"
cy="5"
r="1.375"
></circle>
<circle
id="椭圆形备份-4"
stroke="currentColor"
stroke-width="1.25"
cx="13"
cy="5"
r="1.375"
></circle>
<circle
id="椭圆形备份"
stroke="currentColor"
stroke-width="1.25"
cx="8"
cy="13"
r="1.375"
></circle>
<circle
id="椭圆形备份-3"
stroke="currentColor"
stroke-width="1.25"
cx="3"
cy="11"
r="1.375"
></circle>
<circle
id="椭圆形备份-5"
stroke="currentColor"
stroke-width="1.25"
cx="13"
cy="11"
r="1.375"
></circle>
<path
d="M8.0070039,4.03345364 L8.0070039,8.50590493 L4.1923477,10.2855921"
id="路径-13"
stroke="currentColor"
stroke-width="1.25"
></path>
<line
x1="11.7924093"
y1="8.47550067"
x2="8"
y2="10.2754456"
id="路径-13备份"
stroke="currentColor"
stroke-width="1.25"
transform="translate(9.8962, 9.3755) scale(-1, 1) translate(-9.8962, -9.3755)"
></line>
<path
d="M4.17568738,4.53038288 L6.65384701,3.54050563 M9.35480875,3.53987819 L11.7283558,4.49062879"
id="形状"
stroke="currentColor"
stroke-width="1.25"
></path>
<line
x1="3"
y1="6.06946104"
x2="3"
y2="9.97988046"
id="路径-14"
stroke="currentColor"
stroke-width="1.25"
></line>
<line
x1="13"
y1="6.06946104"
x2="13"
y2="9.97988046"
id="路径-14备份"
stroke="currentColor"
stroke-width="1.25"
></line>
</g>
</SVGWrap>
);
}

37
src/icons/Retrieve.tsx Normal file
View File

@@ -0,0 +1,37 @@
import SVGWrap from "./SVGWrap";
export default function Retrieve(props: I.SVG) {
return (
<SVGWrap {...props} viewBox="0 0 16 16">
<g
id="Retrieve"
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
>
<line
x1="10.8977456"
y1="14.5"
x2="14"
y2="10.9204757"
id="路径"
stroke="currentColor"
stroke-width="1.25"
transform="translate(12.4489, 12.7102) scale(-1, 1) translate(-12.4489, -12.7102)"
></line>
<circle
id="椭圆形"
stroke="currentColor"
stroke-width="1.25"
transform="translate(7.5, 7) scale(-1, 1) translate(-7.5, -7)"
cx="7.5"
cy="7"
r="5"
></circle>
</g>
</SVGWrap>
);
}

108
src/icons/Selection.tsx Normal file
View File

@@ -0,0 +1,108 @@
import SVGWrap from "./SVGWrap";
export default function Selection(props: I.SVG) {
return (
<SVGWrap {...props} viewBox="0 0 16 16">
<g
id="selection"
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
>
<g
id="编组"
transform="translate(1.4813, 1)"
stroke="currentColor"
stroke-width="1.25"
>
<line
x1="6.7986538"
y1="2.8"
x2="6.7986538"
y2="2.07241631e-17"
id="路径"
stroke-linecap="round"
stroke-linejoin="round"
></line>
<circle
id="椭圆形"
fill="#000000"
cx="6.7986538"
cy="6.72"
r="1"
></circle>
<line
x1="2.17440858e-17"
y1="13.5186538"
x2="4.62042688"
y2="8.89822692"
id="路径"
stroke-linecap="round"
stroke-linejoin="round"
></line>
<line
x1="10.1008425"
y1="4.16781133"
x2="10.1008425"
y2="2.66781133"
id="路径"
stroke-linecap="round"
stroke-linejoin="round"
transform="translate(10.1008, 3.4178) rotate(45) translate(-10.1008, -3.4178)"
></line>
<line
x1="12.1186538"
y1="8.12"
x2="12.1186538"
y2="5.32"
id="路径"
stroke-linecap="round"
stroke-linejoin="round"
transform="translate(12.1187, 6.72) rotate(90) translate(-12.1187, -6.72)"
></line>
<line
x1="10.1008425"
y1="10.7721887"
x2="10.1008425"
y2="9.27218867"
id="路径"
stroke-linecap="round"
stroke-linejoin="round"
transform="translate(10.1008, 10.0222) rotate(135) translate(-10.1008, -10.0222)"
></line>
<line
x1="6.7986538"
y1="13.44"
x2="6.7986538"
y2="10.64"
id="路径"
stroke-linecap="round"
stroke-linejoin="round"
transform="translate(6.7987, 12.04) rotate(180) translate(-6.7987, -12.04)"
></line>
<line
x1="1.4786538"
y1="8.12"
x2="1.4786538"
y2="5.32"
id="路径"
stroke-linecap="round"
stroke-linejoin="round"
transform="translate(1.4787, 6.72) rotate(270) translate(-1.4787, -6.72)"
></line>
<line
x1="3.49646513"
y1="4.16781133"
x2="3.49646513"
y2="2.66781133"
id="路径"
stroke-linecap="round"
stroke-linejoin="round"
transform="translate(3.4965, 3.4178) rotate(315) translate(-3.4965, -3.4178)"
></line>
</g>
</g>
</SVGWrap>
);
}

103
src/icons/Understand.tsx Normal file
View File

@@ -0,0 +1,103 @@
import SVGWrap from "./SVGWrap";
export default function Understand(props: I.SVG) {
return (
<SVGWrap {...props} viewBox="0 0 16 16">
<g
id="Understand"
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
>
<g
id="编组"
transform="translate(0.5, 0.5)"
stroke="currentColor"
stroke-width="1.25"
>
<path
d="M7.44444444,3 C9.89904333,3 11.8888889,4.95366655 11.8888889,7.36363636 C11.8888889,9.06711551 10.8946979,10.5426108 9.44492275,11.2613085 L9.44444444,12.2727273 C9.44444444,13.3772968 8.54901394,14.2727273 7.44444444,14.2727273 C6.33987494,14.2727273 5.44444444,13.3772968 5.44444444,12.2727273 L5.44396614,11.2613085 C3.99419095,10.5426108 3,9.06711551 3,7.36363636 C3,4.95366655 4.98984556,3 7.44444444,3 Z"
id="形状结合"
></path>
<line
x1="5.5"
y1="11.2156017"
x2="9.5"
y2="11.2156017"
id="路径-11"
></line>
<line
x1="7.44444444"
y1="10.6363636"
x2="7.44444444"
y2="8.45454545"
id="路径-12"
></line>
<line
x1="7.44444444"
y1="0.818181818"
x2="7.44444444"
y2="0.272727273"
id="路径-12备份"
></line>
<line
x1="12.352383"
y1="2.81770629"
x2="12.3574335"
y2="2.26720124"
id="路径-12备份"
transform="translate(12.3549, 2.5425) rotate(45) translate(-12.3549, -2.5425)"
></line>
<line
x1="14.3888889"
y1="7.64141414"
x2="14.3888889"
y2="7.08585859"
id="路径-12备份"
transform="translate(14.3889, 7.3636) rotate(90) translate(-14.3889, -7.3636)"
></line>
<line
x1="12.3574335"
y1="12.4600715"
x2="12.352383"
y2="11.9095664"
id="路径-12备份"
transform="translate(12.3549, 12.1848) rotate(135) translate(-12.3549, -12.1848)"
></line>
<line
x1="2.53145543"
y1="12.4600715"
x2="2.53650594"
y2="11.9095664"
id="路径-12备份"
transform="translate(2.534, 12.1848) rotate(225) translate(-2.534, -12.1848)"
></line>
<line
x1="0.5"
y1="7.64141414"
x2="0.5"
y2="7.08585859"
id="路径-12备份"
transform="translate(0.5, 7.3636) rotate(270) translate(-0.5, -7.3636)"
></line>
<line
x1="2.53650594"
y1="2.81770629"
x2="2.53145543"
y2="2.26720124"
id="路径-12备份"
transform="translate(2.534, 2.5425) rotate(315) translate(-2.534, -2.5425)"
></line>
<polyline
id="路径-15"
transform="translate(7.4398, 6.7308) rotate(-45) translate(-7.4398, -6.7308)"
points="6.33632897 5.60310185 6.3568375 7.83853192 8.5432888 7.85859111"
></polyline>
</g>
</g>
</SVGWrap>
);
}