feat: shortcut mention

This commit is contained in:
DIYgod
2025-10-18 16:13:33 +08:00
parent 623963951b
commit 5d940341b2
5 changed files with 60 additions and 4 deletions
@@ -141,7 +141,7 @@ export const UserChatMessage: React.FC<UserChatMessageProps> = React.memo(({ mes
>
<div className="text-text relative flex max-w-[calc(100%-1rem)] flex-col gap-2">
{/* Normal message display - always rendered to maintain layout */}
<div className="text-text bg-mix-accent/background-1/4 rounded-2xl px-4 py-2.5 backdrop-blur-sm">
<div className="text-text bg-sidebar-light rounded-2xl px-4 py-2.5 backdrop-blur-sm">
<div className="flex select-text flex-col gap-2 text-sm">
<UserMessageParts message={message} />
</div>
@@ -10,7 +10,10 @@ import type { SerializedEditorState } from "lexical"
import * as React from "react"
import isEqual from "react-fast-compare"
import { useAISettingValue } from "~/atoms/settings/ai"
import { LexicalAIEditorNodes } from "../../editor"
import { MentionComponent } from "../../editor/plugins/mention"
function onError(error: Error) {
console.error("Lexical Read-Only Editor Error:", error)
@@ -26,6 +29,48 @@ interface UserRichTextMessageProps {
export const UserRichTextMessage: React.FC<UserRichTextMessageProps> = React.memo(
({ data, className }) => {
const aiSettings = useAISettingValue()
const { shortcuts } = aiSettings
const shortcutMention = React.useMemo(() => {
const rawText = data.text
const normalizedText = rawText.trim()
if (!normalizedText) {
return null
}
const matchedShortcut = shortcuts.find((shortcut) => {
if (!shortcut.enabled) {
return false
}
if (shortcut.prompt === rawText) {
return true
}
return shortcut.prompt.trim() === normalizedText
})
if (!matchedShortcut) {
return null
}
return {
id: matchedShortcut.id,
name: matchedShortcut.name,
type: "shortcut" as const,
value: matchedShortcut.prompt,
}
}, [data.text, shortcuts])
if (shortcutMention) {
return (
<div className={cn("text-text relative cursor-text text-sm", className)}>
<MentionComponent mentionData={shortcutMention} />
</div>
)
}
const editorState = typeof data.state === "object" ? JSON.stringify(data.state) : data.state
let initialConfig: InitialConfigType = null!
@@ -33,6 +33,7 @@ const MentionTooltipContent = ({
mentionData.type === "entry" && "bg-blue",
mentionData.type === "feed" && "bg-orange",
mentionData.type === "date" && "bg-purple",
mentionData.type === "shortcut" && "bg-amber-700",
)}
>
<MentionTypeIcon type={mentionData.type} value={mentionData.value} className="size-3" />
@@ -79,6 +80,9 @@ const getMentionStyles = (type: MentionData["type"]) => {
case "view": {
return cn(baseStyles)
}
case "shortcut": {
return cn(baseStyles, "text-amber-700 border-amber-700/20", "hover:border-amber-700/30")
}
}
}
export const MentionComponent: React.FC<MentionComponentProps> = ({ mentionData, className }) => {
@@ -89,7 +93,11 @@ export const MentionComponent: React.FC<MentionComponentProps> = ({ mentionData,
if (mentionData.type === "date") {
return getDateMentionDisplayName(mentionData, t, language, RANGE_WITH_LABEL_KEY)
}
return mentionData.name
if (mentionData.type === "shortcut") {
return mentionData.name
} else {
return `@${mentionData.name}`
}
}, [mentionData, t, language])
const handleClick = (e: React.MouseEvent) => {
@@ -108,7 +116,7 @@ export const MentionComponent: React.FC<MentionComponentProps> = ({ mentionData,
value={mentionData.value}
className="mr-1 translate-y-[2px]"
/>
<span>@{displayName}</span>
<span>{displayName}</span>
</span>
</TooltipTrigger>
<TooltipPortal>
@@ -38,6 +38,9 @@ export const MentionTypeIcon: React.FC<MentionTypeIconProps> = ({
}
return <i className={`i-mgc-grid-cute-re ${className}`} />
}
case "shortcut": {
return <i className={`i-mgc-ai-cute-re ${className}`} />
}
default: {
return <i className={`i-mgc-ai-cute-re ${className}`} />
}
@@ -1,4 +1,4 @@
export type MentionType = "entry" | "feed" | "date" | "category" | "view"
export type MentionType = "entry" | "feed" | "date" | "category" | "view" | "shortcut"
export type MentionLabelValue = string | number | boolean | MentionLabelDescriptor