fix(ai): add shortcut to open this modal

- Integrated new hooks for creating and editing AI shortcuts, improving the user experience for managing shortcuts.
- Adjusted layout styles in various components for better visual consistency, including padding and margin updates.
- Removed deprecated modal handling for shortcuts, streamlining the codebase.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2025-10-17 21:43:24 +08:00
parent 2a55f9276a
commit 6ca3f79084
8 changed files with 102 additions and 72 deletions
@@ -1,10 +1,12 @@
import { AIShortcutButton } from "@follow/components/ui/ai-shortcut-button/index.js"
import { DEFAULT_SHORTCUT_TARGETS } from "@follow/shared/settings/interface"
import { nextFrame } from "@follow/utils"
import { useCallback, useMemo } from "react"
import { useTranslation } from "react-i18next"
import { useAISettingValue } from "~/atoms/settings/ai"
import { useSettingModal } from "~/modules/settings/modal/use-setting-modal-hack"
import { useCreateAIShortcutModal } from "~/modules/settings/tabs/ai/shortcuts/hooks"
import { useMainEntryId } from "../../hooks/useMainEntryId"
@@ -40,9 +42,13 @@ export const ChatShortcutsRow: React.FC<ChatShortcutsRowProps> = ({ onSelect })
return (mainEntryId ? entry : list) ?? []
}, [aiSettings.shortcuts, mainEntryId])
const handleAddShortcut = useCreateAIShortcutModal()
const handleCustomize = useCallback(() => {
showSettings("ai")
}, [showSettings])
nextFrame(() => {
handleAddShortcut()
})
}, [handleAddShortcut, showSettings])
return (
<div className="mb-3 px-1">
@@ -220,7 +220,7 @@ export const TaskItem = memo(({ task }: { task: AITask }) => {
]
return (
<div className="hover:bg-material-medium border-border group rounded-lg border p-4 transition-colors">
<div className="hover:bg-material-medium border-border group -ml-3 rounded-lg border p-3 transition-colors">
<div className="flex items-start justify-between">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2">
@@ -1,4 +1,3 @@
import { Divider } from "@follow/components/ui/divider/Divider.js"
import { Label } from "@follow/components/ui/label/index.js"
import { useTranslation } from "react-i18next"
@@ -57,7 +56,7 @@ export const SettingAI = () => {
value: t("integration.title"),
},
MCPServicesSection,
<Divider key="task-scheduling-divider" />,
{
type: "title",
value: t("tasks.section.title"),
@@ -63,7 +63,7 @@ export const MCPServiceItem = ({
]
return (
<div className="hover:bg-material-medium border-border group rounded-lg border p-4 transition-colors">
<div className="hover:bg-material-medium border-border group -ml-3 rounded-lg border p-3 transition-colors">
<div className="flex items-start justify-between">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2">
@@ -362,7 +362,7 @@ export const MCPServicesSection = () => {
</div>
{mcpEnabled && (
<div className="space-y-4">
<>
<div className="flex items-center justify-between">
<Label className="text-text text-sm font-medium">
{t("integration.mcp.services.title")}
@@ -402,24 +402,27 @@ export const MCPServicesSection = () => {
</div>
)}
{mcpServices.map((service) => (
<MCPServiceItem
key={service.id}
service={service}
onDelete={handleDeleteService}
onRefresh={handleRefreshTools}
onEdit={handleEditService}
onToggleEnabled={handleToggleEnabled}
isDeleting={
deleteConnectionMutation.isPending &&
deleteConnectionMutation.variables === service.id
}
isRefreshing={
refreshToolsMutation.isPending && refreshToolsMutation.variables?.[0] === service.id
}
/>
))}
</div>
<div className="!mt-2 space-y-4">
{mcpServices.map((service) => (
<MCPServiceItem
key={service.id}
service={service}
onDelete={handleDeleteService}
onRefresh={handleRefreshTools}
onEdit={handleEditService}
onToggleEnabled={handleToggleEnabled}
isDeleting={
deleteConnectionMutation.isPending &&
deleteConnectionMutation.variables === service.id
}
isRefreshing={
refreshToolsMutation.isPending &&
refreshToolsMutation.variables?.[0] === service.id
}
/>
))}
</div>
</>
)}
</div>
)
@@ -4,21 +4,18 @@ import {
DEFAULT_SUMMARIZE_TIMELINE_SHORTCUT_ID,
defaultAISettings,
} from "@follow/shared/settings/defaults"
import type { AIShortcut } from "@follow/shared/settings/interface"
import { useEffect } from "react"
import { useTranslation } from "react-i18next"
import { toast } from "sonner"
import { setAISetting, useAISettingValue } from "~/atoms/settings/ai"
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
import { useCreateAIShortcutModal, useEditAIShortcutModal } from "./hooks"
import { ShortcutItem } from "./ShortcutItem"
import { ShortcutModalContent } from "./ShortcutModalContent"
export const AIShortcutsSection = () => {
const { t } = useTranslation("ai")
const { shortcuts } = useAISettingValue()
const { present } = useModalStack()
useEffect(() => {
if (!shortcuts.some((shortcut) => shortcut.id === DEFAULT_SUMMARIZE_TIMELINE_SHORTCUT_ID)) {
@@ -34,50 +31,10 @@ export const AIShortcutsSection = () => {
setAISetting("shortcuts", [...shortcuts, { ...defaultSummarizeShortcut }])
}
}, [shortcuts, setAISetting])
}, [shortcuts])
const handleAddShortcut = () => {
present({
title: "Add AI Shortcut",
content: ({ dismiss }: { dismiss: () => void }) => (
<ShortcutModalContent
shortcut={null}
onSave={(shortcut) => {
const newShortcut: AIShortcut = {
...shortcut,
id: Date.now().toString(),
}
setAISetting("shortcuts", [...shortcuts, newShortcut])
toast.success(t("shortcuts.added"))
dismiss()
}}
onCancel={dismiss}
/>
),
})
}
const handleEditShortcut = (shortcut: AIShortcut) => {
present({
title: "Edit AI Shortcut",
content: ({ dismiss }: { dismiss: () => void }) => (
<ShortcutModalContent
shortcut={shortcut}
onSave={(updatedShortcut) => {
setAISetting(
"shortcuts",
shortcuts.map((s) =>
s.id === shortcut.id ? { ...updatedShortcut, id: shortcut.id } : s,
),
)
toast.success(t("shortcuts.updated"))
dismiss()
}}
onCancel={dismiss}
/>
),
})
}
const handleAddShortcut = useCreateAIShortcutModal()
const handleEditShortcut = useEditAIShortcutModal()
const handleDeleteShortcut = (id: string) => {
setAISetting(
@@ -38,7 +38,7 @@ export const ShortcutItem = ({ shortcut, onDelete, onToggle, onEdit }: ShortcutI
}
return (
<div className="hover:bg-material-medium border-border group rounded-lg border p-4 transition-colors">
<div className="hover:bg-material-medium border-border group -ml-3 rounded-lg border p-3 transition-colors">
<div className="flex items-start justify-between">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2">
@@ -0,0 +1,65 @@
import type { AIShortcut } from "@follow/shared/settings/interface"
import { useCallback } from "react"
import { useTranslation } from "react-i18next"
import { toast } from "sonner"
import { getAISettings, setAISetting } from "~/atoms/settings/ai"
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
import { ShortcutModalContent } from "./ShortcutModalContent"
export const useCreateAIShortcutModal = () => {
const { present } = useModalStack()
const { t } = useTranslation("ai")
return useCallback(() => {
present({
title: "Add AI Shortcut",
content: ({ dismiss }: { dismiss: () => void }) => (
<ShortcutModalContent
shortcut={null}
onSave={(shortcut) => {
const newShortcut: AIShortcut = {
...shortcut,
id: Date.now().toString(),
}
const { shortcuts } = getAISettings()
setAISetting("shortcuts", [...shortcuts, newShortcut])
toast.success(t("shortcuts.added"))
dismiss()
}}
onCancel={dismiss}
/>
),
})
}, [present, t])
}
export const useEditAIShortcutModal = () => {
const { present } = useModalStack()
const { t } = useTranslation("ai")
return useCallback(
(shortcut: AIShortcut) => {
present({
title: "Edit AI Shortcut",
content: ({ dismiss }: { dismiss: () => void }) => (
<ShortcutModalContent
shortcut={shortcut}
onSave={(updatedShortcut) => {
const { shortcuts } = getAISettings()
setAISetting(
"shortcuts",
shortcuts.map((s) =>
s.id === shortcut.id ? { ...updatedShortcut, id: shortcut.id } : s,
),
)
toast.success(t("shortcuts.updated"))
dismiss()
}}
onCancel={dismiss}
/>
),
})
},
[present, t],
)
}