feat(components): 新增图标组件并优化历史记录功能

- 新增 Bell、Collect 和 NotCollect 图标组件
- 优化 History 组件,添加隐藏 logo 功能
- 调整 Message 组件样式,移除不必要的代码
- 更新 Scene 组件 Header 颜色
- 注释掉 tailwind.css 中的 arimo 字体权重
This commit is contained in:
zhaoweijie
2025-08-23 20:11:11 +08:00
parent e640b1254d
commit e0e41d7e21
24 changed files with 331 additions and 183 deletions

View File

@@ -24,11 +24,12 @@ export const PlaygroundEmpty = () => {
<div className="w-full pb-4 pt-[20%] grid grid-cols-3 gap-3">
{qaPrompt.map((item, index) => (
<div
key={item.id}
className="p-6 bg-gradient-to-br from-blue-50/90 via-indigo-50/90 to-purple-50/90 backdrop-blur-xl border border-white/60 shadow-xl rounded-2xl cursor-pointer hover:shadow-blue-200/40 hover:from-blue-100/90 hover:to-indigo-100/90 transition-all duration-500 hover:-translate-y-1"
onClick={() => handleQuestion(item.title)}>
<div className="flex items-center">
<div className="text-blue-500 mr-2 w-10">{item.icon}</div>
<div className="font-medium text-sm text-gray-800">
<div className="text-sm text-gray-800">
{item.title}
</div>
</div>

View File

@@ -18,7 +18,7 @@ import { defaultEmbeddingModelForRag } from "~/services/ollama"
import { ImageIcon, MicIcon, StopCircleIcon, X } from "lucide-react"
import { getVariable } from "@/utils/select-variable"
import { useTranslation } from "react-i18next"
import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect"
// import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect"
import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"
import { PiGlobe, PiNetwork } from "react-icons/pi"
import { handleChatInputKeyDown } from "@/utils/key-down"
@@ -369,11 +369,15 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
variant="filled"
size="large"
className="w-full mt-4 hover:!bg-[#0057ff1a]"
style={iodSearch ? {
color: "#0057ff",
background: "#0057ff0f",
border: "1px solid #0066ff26"
} : {}}>
style={
iodSearch
? {
color: "#0057ff",
background: "#0057ff0f",
border: "1px solid #0066ff26"
}
: {}
}>
<PiNetwork className="h-5 w-5" />
{iodSearch ? ":开" : ""}
</Button>
@@ -381,34 +385,32 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
</div>
)}
</div>
<div className="flex !justify-end gap-3">
<div className="flex !justify-end gap-1">
{!selectedKnowledge && (
<Tooltip title={t("tooltip.uploadImage")}>
<button
type="button"
<Button
color="default"
variant="text"
onClick={() => {
inputRef.current?.click()
}}
className={`flex items-center justify-center dark:text-gray-300 ${
className={`!px-[5px] flex items-center justify-center dark:text-gray-300 ${
chatMode === "rag" ? "hidden" : "block"
}`}>
<ImageIcon className="h-5 w-5" />
</button>
<ImageIcon stroke-width={1} className="h-5 w-5" />
</Button>
</Tooltip>
)}
{browserSupportsSpeechRecognition && (
<Tooltip title={t("tooltip.speechToText")}>
<button
type="button"
<Button
color="default"
variant="text"
onClick={async () => {
if (isListening) {
stopSpeechRecognition()
} else {
console.log(
"开始语音识别,语言:",
speechToTextLanguage
)
resetTranscript()
startListening({
continuous: true,
@@ -416,40 +418,43 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
})
}
}}
className={`flex items-center justify-center dark:text-gray-300`}>
className={`flex items-center justify-center dark:text-gray-300 !px-[5px]`}>
{!isListening ? (
<MicIcon className="h-5 w-5" />
<MicIcon stroke-width={1} className="h-5 w-5" />
) : (
<div className="relative">
<span className="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"></span>
<MicIcon className="h-5 w-5" />
<MicIcon
stroke-width={1}
className="h-5 w-5"
/>
</div>
)}
</button>
</Button>
</Tooltip>
)}
<KnowledgeSelect />
{/*<KnowledgeSelect />*/}
{!isSending ? (
<Dropdown.Button
type="default"
htmlType="submit"
disabled={isSending}
className="!justify-end !w-auto"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-5 h-5">
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m19.5 8.25-7.5 7.5-7.5-7.5"
/>
</svg>
}
// icon={
// <svg
// xmlns="http://www.w3.org/2000/svg"
// fill="none"
// viewBox="0 0 24 24"
// strokeWidth={1.5}
// stroke="currentColor"
// className="w-5 h-5">
// <path
// strokeLinecap="round"
// strokeLinejoin="round"
// d="m19.5 8.25-7.5 7.5-7.5-7.5"
// />
// </svg>
// }
menu={{
items: [
{
@@ -479,20 +484,6 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
]
}}>
<div className="inline-flex gap-2">
{sendWhenEnter ? (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
className="h-5 w-5"
viewBox="0 0 24 24">
<path d="M9 10L4 15 9 20"></path>
<path d="M20 4v7a4 4 0 01-4 4H4"></path>
</svg>
) : null}
{t("common:submit")}
</div>
</Dropdown.Button>

View File

@@ -1,4 +1,4 @@
import React, { createContext, useContext, useState } from "react"
import React, { createContext, useContext, useMemo, useState } from "react"
import { AnimatePresence, motion } from "framer-motion"
@@ -8,6 +8,8 @@ import { PlaygroundScene } from "@/components/Common/Playground/Scene.tsx"
import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx"
import { Card } from "antd"
import { CloseOutlined } from "@ant-design/icons"
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { Message } from "@/types/message.ts"
// 定义 Context 类型
interface IodPlaygroundContextType {
@@ -17,6 +19,7 @@ interface IodPlaygroundContextType {
setDetailHeader: React.Dispatch<React.SetStateAction<React.ReactNode>>
detailMain: React.ReactNode
setDetailMain: React.Dispatch<React.SetStateAction<React.ReactNode>>
currentMessage: Message | null
}
// 创建 Context
@@ -36,13 +39,41 @@ export const useIodPlaygroundContext = () => {
}
export const PlaygroundIod = () => {
const { messages, iodLoading, currentMessageId, iodSearch } =
useMessageOption()
const [showPlayground, setShowPlayground] = useState<boolean>(true)
const [detailHeader, setDetailHeader] = useState(<></>)
const [detailMain, setDetailMain] = useState(<></>)
const currentMessage = useMemo<Message | null>(() => {
if (iodLoading) {
return null
}
if (messages.length && iodSearch) {
// 如果不存在currentMessageId默认返回最后一个message
if (!currentMessageId) {
return messages.at(-1)
}
const currentMessage = messages?.find(
(message) => message.id === currentMessageId
)
if (currentMessage) {
return currentMessage
}
// 如果当前message不存在最后一个message
return messages.at(-1)
}
return null
}, [currentMessageId, messages, iodLoading, iodSearch])
return (
<PlaygroundContext.Provider
value={{
currentMessage,
showPlayground,
setShowPlayground,
detailMain,
@@ -65,7 +96,6 @@ const PlaygroundContent = () => {
const { showPlayground, detailMain, detailHeader, setShowPlayground } =
useIodPlaygroundContext()
return (
<AnimatePresence mode="popLayout">
{showPlayground ? (
@@ -80,7 +110,9 @@ const PlaygroundContent = () => {
}}
className="h-full grid grid-rows-12 gap-3">
<div className="w-full row-span-5">
<PlaygroundIodRelevant className={classNames.replace('!bg-[rgba(240,245,255,0.3)]', '')} />
<PlaygroundIodRelevant
className={classNames.replace("!bg-[rgba(240,245,255,0.3)]", "")}
/>
</div>
<div className="w-full row-span-4 grid grid-cols-2 gap-3 custom-scrollbar">
<PlaygroundData className={classNames} />