diff --git a/apps/desktop/layer/renderer/src/modules/settings/tabs/ai.tsx b/apps/desktop/layer/renderer/src/modules/settings/tabs/ai.tsx index 7e1bae11e..16fe3e757 100644 --- a/apps/desktop/layer/renderer/src/modules/settings/tabs/ai.tsx +++ b/apps/desktop/layer/renderer/src/modules/settings/tabs/ai.tsx @@ -1,51 +1,14 @@ -import { Spring } from "@follow/components/constants/spring.js" -import { Button } from "@follow/components/ui/button/index.js" -import { Input, TextArea } from "@follow/components/ui/input/index.js" -import { KbdCombined } from "@follow/components/ui/kbd/Kbd.js" -import { KeyValueEditor } from "@follow/components/ui/key-value-editor/index.js" -import { Label } from "@follow/components/ui/label/index.jsx" -import { Progress } from "@follow/components/ui/progress/index.jsx" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@follow/components/ui/select/index.js" -import { Switch } from "@follow/components/ui/switch/index.jsx" -import type { AIShortcut, MCPService } from "@follow/shared/settings/interface" -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" -import { AnimatePresence } from "motion/react" -import * as React from "react" -import { useState } from "react" import { useTranslation } from "react-i18next" -import { toast } from "sonner" -import { - AIChatPanelStyle, - setAIChatPanelStyle, - setAISetting, - setMCPEnabled, - useAIChatPanelStyle, - useAISettingValue, - useMCPEnabled, -} from "~/atoms/settings/ai" -import { m } from "~/components/common/Motion" -import { useDialog, useModalStack } from "~/components/ui/modal/stacked/hooks" -import { apiFetch } from "~/lib/api-fetch" -import { - createMCPConnection, - deleteMCPConnection, - fetchMCPConnections, - mcpQueryKeys, - refreshMCPTools, - updateMCPConnection, -} from "~/queries/mcp" +import { setAISetting, useAISettingValue } from "~/atoms/settings/ai" -import { SettingActionItem, SettingDescription, SettingTabbedSegment } from "../control" import { createDefineSettingItem } from "../helper/builder" import { createSettingBuilder } from "../helper/setting-builder" -import { SettingModalContentPortal } from "../modal/layout" +import { MCPServicesSection } from "./ai/mcp/MCPServicesSection" +import { PanelStyleSection } from "./ai/PanelStyleSection" +import { PersonalizePromptSection } from "./ai/PersonalizePromptSection" +import { AIShortcutsSection } from "./ai/shortcuts/AIShortcutsSection" +import { TokenUsageSection } from "./ai/TokenUsageSection" const SettingBuilder = createSettingBuilder(useAISettingValue) const defineSettingItem = createDefineSettingItem(useAISettingValue, setAISetting) @@ -67,7 +30,7 @@ export const SettingAI = () => { value: t("features.title"), }, - PanelStyleSegment, + PanelStyleSection, defineSettingItem("autoScrollWhenStreaming", { label: t("settings.autoScrollWhenStreaming.label"), description: t("settings.autoScrollWhenStreaming.description"), @@ -78,7 +41,7 @@ export const SettingAI = () => { value: t("personalize.title"), }, - PersonalizePromptSetting, + PersonalizePromptSection, { type: "title", @@ -96,932 +59,3 @@ export const SettingAI = () => { ) } - -const useTokenUsage = () => { - return useQuery({ - queryKey: ["aiTokenUsage"], - queryFn: async () => { - // TODO: replace with api client call - return ( - (await apiFetch("/ai/usage")) as { - code: 0 - data: { - total: number - used: number - remaining: number - resetAt: string - } - } - ).data - }, - }) -} - -const TokenUsageSection = () => { - const { t } = useTranslation("ai") - - const tokenUsage = useTokenUsage().data || { - total: 0, - used: 0, - remaining: 0, - resetAt: new Date(), - } - - const usagePercentage = tokenUsage.total === 0 ? 0 : (tokenUsage.used / tokenUsage.total) * 100 - const resetDate = new Date(tokenUsage.resetAt) - - return ( -
- {t("token_usage.description")} - -
-
-
-

- {t("token_usage.tokens_used", { - used: tokenUsage.used.toLocaleString(), - total: tokenUsage.total.toLocaleString(), - })} -

-

- {tokenUsage.remaining.toLocaleString()} {t("token_usage.tokens_remaining")} -

-
- -
-
{Math.round(usagePercentage)}%
-
-
- -
- - -
- 0 - - {t("token_usage.resets_at")}: {resetDate.toLocaleDateString()} - - {tokenUsage.total.toLocaleString()} -
-
- -
-
- Current usage -
-
-
- ) -} - -const PersonalizePromptSetting = () => { - const { t } = useTranslation("ai") - const aiSettings = useAISettingValue() - const [prompt, setPrompt] = useState(aiSettings.personalizePrompt) - const [isSaving, setIsSaving] = useState(false) - - const MAX_CHARACTERS = 500 - const currentLength = prompt.length - const isOverLimit = currentLength > MAX_CHARACTERS - const hasChanges = prompt !== aiSettings.personalizePrompt - - const handlePromptChange = (e: React.ChangeEvent) => { - const { value } = e.target - // Allow typing but show validation error if over limit - setPrompt(value) - } - - const handleSave = async () => { - if (isOverLimit) { - toast.error(`Prompt must be ${MAX_CHARACTERS} characters or less`) - return - } - - setIsSaving(true) - try { - setAISetting("personalizePrompt", prompt) - toast.success(t("personalize.saved")) - } finally { - setIsSaving(false) - } - } - - return ( -
-
- -
-