+
-
+
diff --git a/src/components/InstallStep/Permissions.tsx b/src/components/InstallStep/Permissions.tsx
index de9dd8e56..cd6937fd3 100644
--- a/src/components/InstallStep/Permissions.tsx
+++ b/src/components/InstallStep/Permissions.tsx
@@ -12,127 +12,133 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import React, { useState, useEffect } from "react";
-import { useAuthStore } from "@/store/authStore";
-import { ArrowRight, Square, SquareCheckBig } from "lucide-react";
-import { Button } from "../ui/button";
-import { proxyFetchGet, proxyFetchPut } from "@/api/http";
-import privacy_settings from '@/assets/privacy_settings.png'
+import { proxyFetchGet, proxyFetchPut } from '@/api/http';
+import privacy_settings from '@/assets/privacy_settings.png';
+import { useAuthStore } from '@/store/authStore';
+import { ArrowRight, Square, SquareCheckBig } from 'lucide-react';
+import React, { useEffect, useMemo, useState } from 'react';
+import { Button } from '../ui/button';
export const Permissions: React.FC = () => {
- const { setInitState } = useAuthStore();
- const API_FIELDS = [
- "take_screenshot",
- "access_local_software",
- "access_your_address",
- "password_storage",
- ];
- const [settings, setSettings] = useState([
- {
- title: "Enable screen recording",
- checked: false,
- },
- {
- title: "Enable access Local Software",
- checked: false,
- },
- {
- title: "Grant location access",
- checked: false,
- },
- {
- title: "Share data to enhance Eigent",
- checked: false,
- },
- ]);
- useEffect(() => {
- proxyFetchGet("/api/user/privacy")
- .then((res) => {
- setSettings((prev) =>
- prev.map((item, index) => ({
- ...item,
- checked: res[API_FIELDS[index]] || false,
- }))
- );
- })
- .catch((err) => console.error("Failed to fetch settings:", err));
- }, []);
- const handleToggle = (index: number) => {
- setSettings((prev) => {
- const newSettings = [...prev];
- newSettings[index] = {
- ...newSettings[index],
- checked: !newSettings[index].checked,
- };
- return newSettings;
- });
+ const { setInitState } = useAuthStore();
+ const API_FIELDS = useMemo(
+ () => [
+ 'take_screenshot',
+ 'access_local_software',
+ 'access_your_address',
+ 'password_storage',
+ ],
+ []
+ );
+ const [settings, setSettings] = useState([
+ {
+ title: 'Enable screen recording',
+ checked: false,
+ },
+ {
+ title: 'Enable access Local Software',
+ checked: false,
+ },
+ {
+ title: 'Grant location access',
+ checked: false,
+ },
+ {
+ title: 'Share data to enhance Eigent',
+ checked: false,
+ },
+ ]);
+ useEffect(() => {
+ proxyFetchGet('/api/user/privacy')
+ .then((res) => {
+ setSettings((prev) =>
+ prev.map((item, index) => ({
+ ...item,
+ checked: res[API_FIELDS[index]] || false,
+ }))
+ );
+ })
+ .catch((err) => console.error('Failed to fetch settings:', err));
+ }, [API_FIELDS]);
+ const handleToggle = (index: number) => {
+ setSettings((prev) => {
+ const newSettings = [...prev];
+ newSettings[index] = {
+ ...newSettings[index],
+ checked: !newSettings[index].checked,
+ };
+ return newSettings;
+ });
- const requestData = {
- [API_FIELDS[0]]: settings[0].checked,
- [API_FIELDS[1]]: settings[1].checked,
- [API_FIELDS[2]]: settings[2].checked,
- [API_FIELDS[3]]: settings[3].checked,
- };
+ const requestData = {
+ [API_FIELDS[0]]: settings[0].checked,
+ [API_FIELDS[1]]: settings[1].checked,
+ [API_FIELDS[2]]: settings[2].checked,
+ [API_FIELDS[3]]: settings[3].checked,
+ };
- requestData[API_FIELDS[index]] = !settings[index].checked;
+ requestData[API_FIELDS[index]] = !settings[index].checked;
- proxyFetchPut("/api/user/privacy", requestData).catch((err) =>
- console.error("Failed to update settings:", err)
- );
- };
- return (
-
-
-
-
-
- Grant permission to activate the Agent's autonomous actions.
-
- {settings.map((item, index) => (
-
handleToggle(index)}
- className="flex gap-sm items-center p-xs hover:bg-fill-fill-tertiary-hover rounded-md cursor-pointer"
- >
-
- {item.checked ? (
-
- ) : (
-
- )}
-
-
- {item.title}
-
-
- ))}
-
-
-

-
-
-
-
-
-
-
-
-
-
- );
+ proxyFetchPut('/api/user/privacy', requestData).catch((err) =>
+ console.error('Failed to update settings:', err)
+ );
+ };
+ return (
+
+
+
+
+
+ ${`Grant permission to activate the Agent's autonomous actions.`}
+
+ {settings.map((item, index) => (
+
handleToggle(index)}
+ className="flex cursor-pointer items-center gap-sm rounded-md p-xs hover:bg-fill-fill-tertiary-hover"
+ >
+
+ {item.checked ? (
+
+ ) : (
+
+ )}
+
+
+ {item.title}
+
+
+ ))}
+
+
+

+
+
+
+
+
+
+
+
+
+ );
};
diff --git a/src/components/IntegrationList/index.tsx b/src/components/IntegrationList/index.tsx
index 3947222a6..f83178f8e 100644
--- a/src/components/IntegrationList/index.tsx
+++ b/src/components/IntegrationList/index.tsx
@@ -12,466 +12,473 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { Button } from "@/components/ui/button";
+import { fetchGet, fetchPost } from '@/api/http';
+import { Button } from '@/components/ui/button';
import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { TooltipSimple } from "@/components/ui/tooltip";
-import { CircleAlert, Settings2 } from "lucide-react";
-import { fetchGet, fetchPost } from "@/api/http";
+ Tooltip,
+ TooltipContent,
+ TooltipSimple,
+ TooltipTrigger,
+} from '@/components/ui/tooltip';
+import { CircleAlert, Settings2 } from 'lucide-react';
-import React, { useState, useCallback, useMemo } from "react";
-import ellipseIcon from "@/assets/mcp/Ellipse-25.svg";
-import { MCPEnvDialog } from "@/pages/Setting/components/MCPEnvDialog";
-import { OAuth } from "@/lib/oauth";
-import { useTranslation } from "react-i18next";
+import ellipseIcon from '@/assets/mcp/Ellipse-25.svg';
import {
- Select,
- SelectTrigger,
- SelectValue,
- SelectContent,
- SelectItem,
-} from "@/components/ui/select";
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
import {
- useIntegrationManagement,
- type IntegrationItem,
-} from "@/hooks/useIntegrationManagement";
-import { getProxyBaseURL } from "@/lib";
+ useIntegrationManagement,
+ type IntegrationItem,
+} from '@/hooks/useIntegrationManagement';
+import { getProxyBaseURL } from '@/lib';
+import { OAuth } from '@/lib/oauth';
+import { MCPEnvDialog } from '@/pages/Setting/components/MCPEnvDialog';
+import React, { useCallback, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
-type IntegrationListVariant = "select" | "manage";
+type IntegrationListVariant = 'select' | 'manage';
interface IntegrationListProps {
- items: IntegrationItem[];
- variant?: IntegrationListVariant; // "select" for AddWorker, "manage" for Setting
+ items: IntegrationItem[];
+ variant?: IntegrationListVariant; // "select" for AddWorker, "manage" for Setting
- // Select mode props (AddWorker)
- addOption?: (mcp: any, isLocal: boolean) => void;
- onShowEnvConfig?: (mcp: any) => void;
+ // Select mode props (AddWorker)
+ addOption?: (mcp: any, isLocal: boolean) => void;
+ onShowEnvConfig?: (mcp: any) => void;
- // Manage mode props (Setting)
- showSelect?: boolean;
- selectPlaceholder?: string;
- selectContent?: React.ReactNode;
- onSelectChange?: (value: string, item: IntegrationItem) => void;
- showConfigButton?: boolean;
- showInstallButton?: boolean;
- onConfigClick?: (item: IntegrationItem) => void;
- showStatusDot?: boolean;
+ // Manage mode props (Setting)
+ showSelect?: boolean;
+ selectPlaceholder?: string;
+ selectContent?: React.ReactNode;
+ onSelectChange?: (value: string, item: IntegrationItem) => void;
+ showConfigButton?: boolean;
+ showInstallButton?: boolean;
+ onConfigClick?: (item: IntegrationItem) => void;
+ showStatusDot?: boolean;
- // Common props
- installedKeys?: string[];
- oauth?: OAuth | null;
- translationNamespace?: "layout" | "setting"; // For translation keys
+ // Common props
+ installedKeys?: string[];
+ oauth?: OAuth | null;
+ translationNamespace?: 'layout' | 'setting'; // For translation keys
}
export default function IntegrationList({
- items,
- variant = "manage", // Default to manage mode for backward compatibility
- addOption,
- onShowEnvConfig,
- showSelect = false,
- selectPlaceholder = "Select...",
- selectContent,
- onSelectChange,
- showConfigButton = true,
- showInstallButton = true,
- onConfigClick,
- showStatusDot = true,
- installedKeys = [],
- oauth,
- translationNamespace = variant === "select" ? "layout" : "setting",
+ items,
+ variant = 'manage', // Default to manage mode for backward compatibility
+ addOption,
+ onShowEnvConfig,
+ showSelect = false,
+ selectPlaceholder = 'Select...',
+ selectContent,
+ onSelectChange,
+ showConfigButton = true,
+ showInstallButton = true,
+ onConfigClick,
+ showStatusDot = true,
+ installedKeys: _installedKeys = [],
+ oauth: _oauth,
+ translationNamespace = variant === 'select' ? 'layout' : 'setting',
}: IntegrationListProps) {
- const { t } = useTranslation();
- const [showEnvConfig, setShowEnvConfig] = useState(false);
- const [activeMcp, setActiveMcp] = useState
(null);
- const isSelectMode = variant === "select";
+ const { t } = useTranslation();
+ const [showEnvConfig, setShowEnvConfig] = useState(false);
+ const [activeMcp, setActiveMcp] = useState(null);
+ const isSelectMode = variant === 'select';
- // Use shared hook for integration management
- const {
- installed,
- fetchInstalled,
- saveEnvAndConfig,
- handleUninstall,
- createMcpFromItem,
- } = useIntegrationManagement(items);
+ // Use shared hook for integration management
+ const {
+ installed,
+ fetchInstalled,
+ saveEnvAndConfig,
+ handleUninstall,
+ createMcpFromItem,
+ } = useIntegrationManagement(items);
- // Install handler - different logic for select vs manage mode
- const handleInstall = useCallback(
- async (item: IntegrationItem) => {
- console.log(item);
- const searchKey = isSelectMode ? "EXA Search" : "Search";
+ // Install handler - different logic for select vs manage mode
+ const handleInstall = useCallback(
+ async (item: IntegrationItem) => {
+ console.log(item);
+ const searchKey = isSelectMode ? 'EXA Search' : 'Search';
- if (item.key === searchKey || item.key === "Lark") {
- const mcp = createMcpFromItem(item, item.key === "Lark" ? 15 : 13);
- if (isSelectMode) {
- onShowEnvConfig?.(mcp);
- } else {
- setActiveMcp(mcp);
- setShowEnvConfig(true);
- }
- return;
- }
+ if (item.key === searchKey || item.key === 'Lark') {
+ const mcp = createMcpFromItem(item, item.key === 'Lark' ? 15 : 13);
+ if (isSelectMode) {
+ onShowEnvConfig?.(mcp);
+ } else {
+ setActiveMcp(mcp);
+ setShowEnvConfig(true);
+ }
+ return;
+ }
- if (item.key === "Google Calendar") {
- const mcp = createMcpFromItem(item, 14);
- if (isSelectMode) {
- onShowEnvConfig?.(mcp);
- } else {
- setActiveMcp(mcp);
- setShowEnvConfig(true);
- }
- return;
- }
+ if (item.key === 'Google Calendar') {
+ const mcp = createMcpFromItem(item, 14);
+ if (isSelectMode) {
+ onShowEnvConfig?.(mcp);
+ } else {
+ setActiveMcp(mcp);
+ setShowEnvConfig(true);
+ }
+ return;
+ }
- // LinkedIn uses server-side OAuth flow
- if (item.key === "LinkedIn") {
- // Open LinkedIn OAuth login via the remote server (same pattern as other OAuth providers)
- const baseUrl = getProxyBaseURL();
- const oauthUrl = `${baseUrl}/api/oauth/linkedin/login`;
- window.open(oauthUrl, "_blank", "width=600,height=700");
- return;
- }
+ // LinkedIn uses server-side OAuth flow
+ if (item.key === 'LinkedIn') {
+ // Open LinkedIn OAuth login via the remote server (same pattern as other OAuth providers)
+ const baseUrl = getProxyBaseURL();
+ const oauthUrl = `${baseUrl}/api/oauth/linkedin/login`;
+ window.open(oauthUrl, '_blank', 'width=600,height=700');
+ return;
+ }
- if (installed[item.key]) return;
- await item.onInstall();
- // Only refresh in select mode
- if (isSelectMode) {
- await fetchInstalled();
- }
- },
- [
- installed,
- createMcpFromItem,
- isSelectMode,
- onShowEnvConfig,
- fetchInstalled,
- ]
- );
+ if (installed[item.key]) return;
+ await item.onInstall();
+ // Only refresh in select mode
+ if (isSelectMode) {
+ await fetchInstalled();
+ }
+ },
+ [
+ installed,
+ createMcpFromItem,
+ isSelectMode,
+ onShowEnvConfig,
+ fetchInstalled,
+ ]
+ );
- // onConnect handler - different logic for select vs manage mode
- const onConnect = async (mcp: any) => {
- console.log("[IntegrationList onConnect] Starting for", mcp.key);
+ // onConnect handler - different logic for select vs manage mode
+ const onConnect = async (mcp: any) => {
+ console.log('[IntegrationList onConnect] Starting for', mcp.key);
- // Refresh configs first to get latest state
- await fetchInstalled();
+ // Refresh configs first to get latest state
+ await fetchInstalled();
- // Save all environment variables
- await Promise.all(
- Object.keys(mcp.install_command.env).map(async (key) => {
- return saveEnvAndConfig(mcp.key, key, mcp.install_command.env[key]);
- })
- );
+ // Save all environment variables
+ await Promise.all(
+ Object.keys(mcp.install_command.env).map(async (key) => {
+ return saveEnvAndConfig(mcp.key, key, mcp.install_command.env[key]);
+ })
+ );
- // After saving env vars, handle Google Calendar authorization flow
- if (mcp.key === "Google Calendar") {
- console.log(
- "[IntegrationList onConnect] Google Calendar detected, starting auth flow"
- );
+ // After saving env vars, handle Google Calendar authorization flow
+ if (mcp.key === 'Google Calendar') {
+ console.log(
+ '[IntegrationList onConnect] Google Calendar detected, starting auth flow'
+ );
- // Trigger install/authorization
- const calendarItem = items.find((item) => item.key === "Google Calendar");
- try {
- if (calendarItem && calendarItem.onInstall) {
- await calendarItem.onInstall();
- } else {
- await fetchPost("/install/tool/google_calendar");
- }
- } catch (_) {}
+ // Trigger install/authorization
+ const calendarItem = items.find((item) => item.key === 'Google Calendar');
+ try {
+ if (calendarItem && calendarItem.onInstall) {
+ await calendarItem.onInstall();
+ } else {
+ await fetchPost('/install/tool/google_calendar');
+ }
+ } catch (_) {}
- // Select mode: poll OAuth status
- if (isSelectMode) {
- console.log(
- "[IntegrationList onConnect] Starting OAuth status polling"
- );
+ // Select mode: poll OAuth status
+ if (isSelectMode) {
+ console.log(
+ '[IntegrationList onConnect] Starting OAuth status polling'
+ );
- const start = Date.now();
- const timeoutMs = 5 * 60 * 1000; // 5 minutes
- while (Date.now() - start < timeoutMs) {
- try {
- const statusRes: any = await fetchGet(
- "/oauth/status/google_calendar"
- );
- console.log(
- "[IntegrationList onConnect] OAuth status:",
- statusRes?.status
- );
+ const start = Date.now();
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes
+ while (Date.now() - start < timeoutMs) {
+ try {
+ const statusRes: any = await fetchGet(
+ '/oauth/status/google_calendar'
+ );
+ console.log(
+ '[IntegrationList onConnect] OAuth status:',
+ statusRes?.status
+ );
- if (statusRes?.status === "success") {
- console.log(
- "[IntegrationList onConnect] Success! Closing dialog"
- );
- await fetchInstalled();
- onClose();
- return;
- }
- if (
- statusRes?.status === "failed" ||
- statusRes?.status === "cancelled"
- ) {
- console.log(
- "[IntegrationList onConnect] Failed/cancelled, keeping dialog open"
- );
- return;
- }
- } catch (err) {
- console.log("[IntegrationList onConnect] Polling error:", err);
- }
- await new Promise((r) => setTimeout(r, 1500));
- }
- console.log("[IntegrationList onConnect] Polling timeout");
- return;
- }
- }
+ if (statusRes?.status === 'success') {
+ console.log(
+ '[IntegrationList onConnect] Success! Closing dialog'
+ );
+ await fetchInstalled();
+ onClose();
+ return;
+ }
+ if (
+ statusRes?.status === 'failed' ||
+ statusRes?.status === 'cancelled'
+ ) {
+ console.log(
+ '[IntegrationList onConnect] Failed/cancelled, keeping dialog open'
+ );
+ return;
+ }
+ } catch (err) {
+ console.log('[IntegrationList onConnect] Polling error:', err);
+ }
+ await new Promise((r) => setTimeout(r, 1500));
+ }
+ console.log('[IntegrationList onConnect] Polling timeout');
+ return;
+ }
+ }
- // Select mode: add to tools and close
- if (isSelectMode && addOption) {
- console.log(
- "[IntegrationList onConnect] Non-Google Calendar, closing immediately"
- );
- await fetchInstalled();
- addOption(mcp, true);
- onClose();
- } else {
- // Manage mode: just close
- await fetchInstalled();
- onClose();
- }
- };
+ // Select mode: add to tools and close
+ if (isSelectMode && addOption) {
+ console.log(
+ '[IntegrationList onConnect] Non-Google Calendar, closing immediately'
+ );
+ await fetchInstalled();
+ addOption(mcp, true);
+ onClose();
+ } else {
+ // Manage mode: just close
+ await fetchInstalled();
+ onClose();
+ }
+ };
- const onClose = () => {
- setShowEnvConfig(false);
- setActiveMcp(null);
- };
+ const onClose = () => {
+ setShowEnvConfig(false);
+ setActiveMcp(null);
+ };
- const handleOpenConfig = useCallback(
- (item: IntegrationItem) => {
- // if external handler provided by parent, use it
- if (onConfigClick) {
- onConfigClick(item);
- return;
- }
- // default behavior: if item has env vars, open built-in MCP config dialog
- if (item?.env_vars && item.env_vars.length > 0) {
- const mcp = createMcpFromItem(item, -1);
- setActiveMcp(mcp);
- setShowEnvConfig(true);
- }
- },
- [onConfigClick, createMcpFromItem]
- );
+ const handleOpenConfig = useCallback(
+ (item: IntegrationItem) => {
+ // if external handler provided by parent, use it
+ if (onConfigClick) {
+ onConfigClick(item);
+ return;
+ }
+ // default behavior: if item has env vars, open built-in MCP config dialog
+ if (item?.env_vars && item.env_vars.length > 0) {
+ const mcp = createMcpFromItem(item, -1);
+ setActiveMcp(mcp);
+ setShowEnvConfig(true);
+ }
+ },
+ [onConfigClick, createMcpFromItem]
+ );
- const COMING_SOON_ITEMS = [
- "Slack",
- "X(Twitter)",
- "WhatsApp",
- // "LinkedIn", // LinkedIn OAuth is now supported
- "Reddit",
- "Github",
- ];
+ const COMING_SOON_ITEMS = useMemo(
+ () => [
+ 'Slack',
+ 'X(Twitter)',
+ 'WhatsApp',
+ // "LinkedIn", // LinkedIn OAuth is now supported
+ 'Reddit',
+ 'Github',
+ ],
+ []
+ );
- const sortedItems = useMemo(() => {
- const available = items.filter((item) => !COMING_SOON_ITEMS.includes(item.name));
- const comingSoon = items.filter((item) => COMING_SOON_ITEMS.includes(item.name));
- return [...available, ...comingSoon];
- }, [items]);
+ const sortedItems = useMemo(() => {
+ const available = items.filter(
+ (item) => !COMING_SOON_ITEMS.includes(item.name)
+ );
+ const comingSoon = items.filter((item) =>
+ COMING_SOON_ITEMS.includes(item.name)
+ );
+ return [...available, ...comingSoon];
+ }, [items, COMING_SOON_ITEMS]);
- // Determine container and item styles based on variant
- const containerClassName = isSelectMode
- ? "space-y-3"
- : "flex flex-col w-full items-start justify-start gap-4";
+ // Determine container and item styles based on variant
+ const containerClassName = isSelectMode
+ ? 'space-y-3'
+ : 'flex flex-col w-full items-start justify-start gap-4';
- const itemClassName = isSelectMode
- ? "cursor-pointer hover:bg-gray-100 px-3 py-2 flex justify-between"
- : "w-full px-6 py-4 bg-surface-secondary rounded-2xl";
+ const itemClassName = isSelectMode
+ ? 'cursor-pointer hover:bg-gray-100 px-3 py-2 flex justify-between'
+ : 'w-full px-6 py-4 bg-surface-secondary rounded-2xl';
- const titleClassName = isSelectMode
- ? "text-base leading-snug font-bold text-text-action"
- : "text-label-lg font-bold text-text-heading";
+ const titleClassName = isSelectMode
+ ? 'text-base leading-snug font-bold text-text-action'
+ : 'text-label-lg font-bold text-text-heading';
- return (
-
-
- {sortedItems.map((item) => {
- const isInstalled = !!installed[item.key];
- const isComingSoon = COMING_SOON_ITEMS.includes(item.name);
+ return (
+
+
+ {sortedItems.map((item) => {
+ const isInstalled = !!installed[item.key];
+ const isComingSoon = COMING_SOON_ITEMS.includes(item.name);
- return (
-
-
{
- if (!isComingSoon) {
- if (item.env_vars.length === 0 || isInstalled) {
- // Ensure toolkit field is passed and normalized for known cases
- const normalizedToolkit =
- item.name === "Notion"
- ? "notion_mcp_toolkit"
- : item.toolkit;
- addOption?.(
- { ...item, toolkit: normalizedToolkit },
- true
- );
- } else {
- handleInstall(item);
- }
- }
- }
- : undefined
- }
- >
- {isSelectMode ? (
-
- {(isSelectMode || showStatusDot) && (
-

- )}
-
{item.name}
-
-
-
-
-
-
- ) : (
-
-
- {showStatusDot && (
-

- )}
-
{item.name}
-
-
-
-
-
-
- {item.desc}
-
-
-
-
-
- {showConfigButton && (
-
- )}
- {showInstallButton && (
-
- )}
-
-
- )}
- {isSelectMode && item.env_vars.length !== 0 && (
-
- )}
-
+ return (
+
+
{
+ if (!isComingSoon) {
+ if (item.env_vars.length === 0 || isInstalled) {
+ // Ensure toolkit field is passed and normalized for known cases
+ const normalizedToolkit =
+ item.name === 'Notion'
+ ? 'notion_mcp_toolkit'
+ : item.toolkit;
+ addOption?.(
+ { ...item, toolkit: normalizedToolkit },
+ true
+ );
+ } else {
+ handleInstall(item);
+ }
+ }
+ }
+ : undefined
+ }
+ >
+ {isSelectMode ? (
+
+ {(isSelectMode || showStatusDot) && (
+

+ )}
+
{item.name}
+
+
+
+
+
+
+ ) : (
+
+
+ {showStatusDot && (
+

+ )}
+
{item.name}
+
+
+
+
+
+
+ {item.desc}
+
+
+
+
+
+ {showConfigButton && (
+
+ )}
+ {showInstallButton && (
+
+ )}
+
+
+ )}
+ {isSelectMode && item.env_vars.length !== 0 && (
+
+ )}
+
- {!isSelectMode && showSelect && (
-
-
-
- {" "}
- Default {item.name}
-
-
-
-
-
-
- )}
-
- );
- })}
-
- );
+ {!isSelectMode && showSelect && (
+
+
+
+ {' '}
+ Default {item.name}
+
+
+
+
+
+
+ )}
+
+ );
+ })}
+
+ );
}
diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx
index 7763b0ca4..0eef1dce1 100644
--- a/src/components/Layout/index.tsx
+++ b/src/components/Layout/index.tsx
@@ -12,110 +12,117 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import TopBar from "@/components/TopBar";
-import { Outlet } from "react-router-dom";
-import HistorySidebar from "../HistorySidebar";
-import { InstallDependencies } from "@/components/InstallStep/InstallDependencies";
-import { useAuthStore } from "@/store/authStore";
-import { useEffect, useState } from "react";
-import { AnimationJson } from "@/components/AnimationJson";
-import animationData from "@/assets/animation/onboarding_success.json";
-import CloseNoticeDialog from "../Dialog/CloseNotice";
-import { useInstallationUI } from "@/store/installationStore";
-import { useInstallationSetup } from "@/hooks/useInstallationSetup";
-import InstallationErrorDialog from "../InstallStep/InstallationErrorDialog/InstallationErrorDialog";
-import Halo from "../Halo";
-import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
+import animationData from '@/assets/animation/onboarding_success.json';
+import { AnimationJson } from '@/components/AnimationJson';
+import { InstallDependencies } from '@/components/InstallStep/InstallDependencies';
+import TopBar from '@/components/TopBar';
+import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
+import { useInstallationSetup } from '@/hooks/useInstallationSetup';
+import { useAuthStore } from '@/store/authStore';
+import { useInstallationUI } from '@/store/installationStore';
+import { useEffect, useState } from 'react';
+import { Outlet } from 'react-router-dom';
+import CloseNoticeDialog from '../Dialog/CloseNotice';
+import HistorySidebar from '../HistorySidebar';
+import InstallationErrorDialog from '../InstallStep/InstallationErrorDialog/InstallationErrorDialog';
const Layout = () => {
- const { initState, isFirstLaunch, setIsFirstLaunch, setInitState } = useAuthStore();
- const [noticeOpen, setNoticeOpen] = useState(false);
+ const {
+ initState,
+ isFirstLaunch,
+ setIsFirstLaunch,
+ setInitState: _setInitState,
+ } = useAuthStore();
+ const [noticeOpen, setNoticeOpen] = useState(false);
- //Get Chatstore for the active project's task
- const { chatStore } = useChatStoreAdapter();
- if (!chatStore) {
- console.log(chatStore);
+ //Get Chatstore for the active project's task
+ const { chatStore } = useChatStoreAdapter();
- return Loading...
;
- }
+ const {
+ installationState,
+ latestLog,
+ error,
+ backendError,
+ isInstalling,
+ shouldShowInstallScreen,
+ retryInstallation,
+ retryBackend,
+ } = useInstallationUI();
- const {
- installationState,
- latestLog,
- error,
- backendError,
- isInstalling,
- shouldShowInstallScreen,
- retryInstallation,
- retryBackend,
- } = useInstallationUI();
+ useInstallationSetup();
- useInstallationSetup();
+ useEffect(() => {
+ const handleBeforeClose = () => {
+ const currentStatus =
+ chatStore.tasks[chatStore.activeTaskId as string]?.status;
+ if (['running', 'pause'].includes(currentStatus)) {
+ setNoticeOpen(true);
+ } else {
+ window.electronAPI.closeWindow(true);
+ }
+ };
- useEffect(() => {
- const handleBeforeClose = () => {
- const currentStatus = chatStore.tasks[chatStore.activeTaskId as string]?.status;
- if(["running", "pause"].includes(currentStatus)) {
- setNoticeOpen(true);
- } else {
- window.electronAPI.closeWindow(true);
- }
- };
+ window.ipcRenderer.on('before-close', handleBeforeClose);
- window.ipcRenderer.on("before-close", handleBeforeClose);
+ return () => {
+ window.ipcRenderer.removeAllListeners('before-close');
+ };
+ }, [chatStore.tasks, chatStore.activeTaskId]);
- return () => {
- window.ipcRenderer.removeAllListeners("before-close");
- };
- }, [chatStore.tasks, chatStore.activeTaskId]);
+ // Determine what to show based on states
+ const shouldShowOnboarding =
+ initState === 'done' && isFirstLaunch && !isInstalling;
- // Determine what to show based on states
- const shouldShowOnboarding = initState === "done" && isFirstLaunch && !isInstalling;
+ const actualShouldShowInstallScreen =
+ shouldShowInstallScreen ||
+ initState !== 'done' ||
+ installationState === 'waiting-backend';
+ const shouldShowMainContent = !actualShouldShowInstallScreen;
- const actualShouldShowInstallScreen = shouldShowInstallScreen || initState !== 'done' || installationState === 'waiting-backend';
- const shouldShowMainContent = !actualShouldShowInstallScreen;
+ if (!chatStore) {
+ console.log(chatStore);
- return (
-
-
-
- {/* Onboarding animation */}
- {shouldShowOnboarding && (
-
setIsFirstLaunch(false)}
- animationData={animationData}
- />
- )}
+ return Loading...
;
+ }
- {/* Installation screen */}
- {actualShouldShowInstallScreen && }
+ return (
+
+
+
+ {/* Onboarding animation */}
+ {shouldShowOnboarding && (
+
setIsFirstLaunch(false)}
+ animationData={animationData}
+ />
+ )}
- {/* Main app content */}
- {shouldShowMainContent && (
- <>
-
-
- >
- )}
+ {/* Installation screen */}
+ {actualShouldShowInstallScreen && }
- {(backendError || (error && installationState === "error")) && (
-
- )}
+ {/* Main app content */}
+ {shouldShowMainContent && (
+ <>
+
+
+ >
+ )}
-
-
-
- );
+ {(backendError || (error && installationState === 'error')) && (
+
+ )}
+
+
+
+
+ );
};
export default Layout;
diff --git a/src/components/MenuButton/MenuButton.tsx b/src/components/MenuButton/MenuButton.tsx
index 770a0fd7d..603fb5b67 100644
--- a/src/components/MenuButton/MenuButton.tsx
+++ b/src/components/MenuButton/MenuButton.tsx
@@ -12,102 +12,130 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react";
-import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
-import { cva, type VariantProps } from "class-variance-authority";
-import { cn } from "@/lib/utils";
-import { AnimateIcon as AnimateIconProvider } from "@/components/animate-ui/icons/icon";
+import { AnimateIcon as AnimateIconProvider } from '@/components/animate-ui/icons/icon';
+import { cn } from '@/lib/utils';
+import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
+import { cva, type VariantProps } from 'class-variance-authority';
+import * as React from 'react';
const menuButtonVariants = cva(
- "relative inline-flex items-center justify-center select-none rounded-xs transition-colors duration-200 ease-in-out outline-none disabled:opacity-30 disabled:pointer-events-none bg-menubutton-fill-default border border-solid border-menubutton-border-default hover:bg-menubutton-fill-hover hover:border-menubutton-border-hover focus:bg-menubutton-fill-active focus:border-menubutton-border-active data-[state=on]:bg-menubutton-fill-active data-[state=on]:border-menubutton-border-active text-foreground cursor-pointer data-[state=on]:shadow-button-shadow rounded-lg",
- {
- variants: {
- size: {
- xs: "py-1 px-2 gap-1 text-label-sm font-bold [&_svg]:size-[16px]",
- sm: "p-2 gap-1 text-label-sm font-bold [&_svg]:size-[20px]",
- md: "p-2 gap-1 text-label-md font-bold [&_svg]:size-[24px]",
- },
- },
- defaultVariants: {
- size: "md",
- },
- }
+ 'relative inline-flex items-center justify-center select-none rounded-xs transition-colors duration-200 ease-in-out outline-none disabled:opacity-30 disabled:pointer-events-none bg-menubutton-fill-default border border-solid border-menubutton-border-default hover:bg-menubutton-fill-hover hover:border-menubutton-border-hover focus:bg-menubutton-fill-active focus:border-menubutton-border-active data-[state=on]:bg-menubutton-fill-active data-[state=on]:border-menubutton-border-active text-foreground cursor-pointer data-[state=on]:shadow-button-shadow rounded-lg',
+ {
+ variants: {
+ size: {
+ xs: 'py-1 px-2 gap-1 text-label-sm font-bold [&_svg]:size-[16px]',
+ sm: 'p-2 gap-1 text-label-sm font-bold [&_svg]:size-[20px]',
+ md: 'p-2 gap-1 text-label-md font-bold [&_svg]:size-[24px]',
+ },
+ },
+ defaultVariants: {
+ size: 'md',
+ },
+ }
);
type MenuToggleContextValue = VariantProps;
const MenuToggleGroupContext = React.createContext({
- size: "md",
+ size: 'md',
});
-type MenuToggleGroupProps = React.ComponentPropsWithoutRef & VariantProps;
+type MenuToggleGroupProps = React.ComponentPropsWithoutRef<
+ typeof ToggleGroupPrimitive.Root
+> &
+ VariantProps;
export const MenuToggleGroup = React.forwardRef<
- React.ElementRef,
- MenuToggleGroupProps
->(({ className, size, children, orientation = "vertical", ...props }, ref) => (
-
-
- {children}
-
-
+ React.ElementRef,
+ MenuToggleGroupProps
+>(({ className, size, children, orientation = 'vertical', ...props }, ref) => (
+
+
+ {children}
+
+
));
MenuToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
type MenuToggleItemProps = React.ComponentPropsWithoutRef<
- typeof ToggleGroupPrimitive.Item
- > & VariantProps & {
- icon?: React.ReactNode;
- subIcon?: React.ReactNode;
- showSubIcon?: boolean;
- disableIconAnimation?: boolean;
- iconAnimateOnHover?: boolean | string;
- };
+ typeof ToggleGroupPrimitive.Item
+> &
+ VariantProps & {
+ icon?: React.ReactNode;
+ subIcon?: React.ReactNode;
+ showSubIcon?: boolean;
+ disableIconAnimation?: boolean;
+ iconAnimateOnHover?: boolean | string;
+ };
export const MenuToggleItem = React.forwardRef<
- React.ElementRef,
- MenuToggleItemProps
->(({ className, children, size, icon, subIcon, showSubIcon = false, disableIconAnimation = false, iconAnimateOnHover = true, ...props }, ref) => {
- const context = React.useContext(MenuToggleGroupContext);
+ React.ElementRef,
+ MenuToggleItemProps
+>(
+ (
+ {
+ className,
+ children,
+ size,
+ icon,
+ subIcon,
+ showSubIcon = false,
+ disableIconAnimation = false,
+ iconAnimateOnHover = true,
+ ...props
+ },
+ ref
+ ) => {
+ const context = React.useContext(MenuToggleGroupContext);
const iconNode = icon;
return (
-
+
- {showSubIcon && subIcon ? (
- <>
- {children}
-
- {subIcon}
-
- >
- ) : (
-
- {iconNode}
- {children}
-
- )}
+ {showSubIcon && subIcon ? (
+ <>
+ {children}
+
+ {subIcon}
+
+ >
+ ) : (
+
+ {iconNode}
+ {children}
+
+ )}
-
+
);
-});
+ }
+);
MenuToggleItem.displayName = ToggleGroupPrimitive.Item.displayName;
export { menuButtonVariants };
-
-
diff --git a/src/components/Navigation/index.tsx b/src/components/Navigation/index.tsx
index 804069a5a..f0b4c4496 100644
--- a/src/components/Navigation/index.tsx
+++ b/src/components/Navigation/index.tsx
@@ -12,100 +12,93 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react";
+import * as React from 'react';
-import {
- Tabs,
- TabsList,
- TabsTrigger,
- TabsContent,
-} from "@/components/ui/tabs";
-import { cn } from "@/lib/utils";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { cn } from '@/lib/utils';
export type VerticalNavItem = {
- value: string;
- label: React.ReactNode;
- icon?: React.ReactNode;
- content?: React.ReactNode;
- disabled?: boolean;
+ value: string;
+ label: React.ReactNode;
+ icon?: React.ReactNode;
+ content?: React.ReactNode;
+ disabled?: boolean;
};
export type VerticalNavigationProps = {
- items: VerticalNavItem[];
- defaultValue?: string;
- value?: string;
- onValueChange?: (value: string) => void;
- className?: string;
- listClassName?: string;
- triggerClassName?: string;
- contentClassName?: string;
+ items: VerticalNavItem[];
+ defaultValue?: string;
+ value?: string;
+ onValueChange?: (value: string) => void;
+ className?: string;
+ listClassName?: string;
+ triggerClassName?: string;
+ contentClassName?: string;
};
export function VerticalNavigation({
- items,
- defaultValue,
- value,
- onValueChange,
- className,
- listClassName,
- triggerClassName,
- contentClassName,
+ items,
+ defaultValue,
+ value,
+ onValueChange,
+ className,
+ listClassName,
+ triggerClassName,
+ contentClassName,
}: VerticalNavigationProps) {
- const initial = React.useMemo(() => {
- if (value) return undefined;
- if (defaultValue) return defaultValue;
- return items[0]?.value;
- }, [value, defaultValue, items]);
+ const initial = React.useMemo(() => {
+ if (value) return undefined;
+ if (defaultValue) return defaultValue;
+ return items[0]?.value;
+ }, [value, defaultValue, items]);
- return (
-
-
- {items.map((item) => (
-
- {item.icon ? (
-
- {item.icon}
-
- ) : null}
- {item.label}
-
- ))}
-
+ return (
+
+
+ {items.map((item) => (
+
+ {item.icon ? (
+
+ {item.icon}
+
+ ) : null}
+ {item.label}
+
+ ))}
+
-
- {items.map((item) => (
-
- {item.content}
-
- ))}
-
-
- );
+
+ {items.map((item) => (
+
+ {item.content}
+
+ ))}
+
+
+ );
}
export default VerticalNavigation;
-
-
diff --git a/src/components/SearchHistoryDialog.tsx b/src/components/SearchHistoryDialog.tsx
index 0f97d4569..c4e47b19b 100644
--- a/src/components/SearchHistoryDialog.tsx
+++ b/src/components/SearchHistoryDialog.tsx
@@ -12,138 +12,138 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-"use client";
+'use client';
-import { useEffect, useState } from "react";
-import {
- Calculator,
- Calendar,
- CreditCard,
- ScanFace,
- Search,
- Settings,
- Smile,
- User,
-} from "lucide-react";
+import { ScanFace, Search } from 'lucide-react';
+import { useEffect, useState } from 'react';
+import GroupedHistoryView from '@/components/GroupedHistoryView';
import {
- CommandDialog,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
- CommandSeparator,
- CommandShortcut,
-} from "@/components/ui/command";
-import { Button } from "./ui/button";
-import { DialogTitle } from "./ui/dialog";
-import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
-import { proxyFetchGet } from "@/api/http";
-import { useNavigate } from "react-router-dom";
-import { generateUniqueId } from "@/lib";
-import { useTranslation } from "react-i18next";
-import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
-import { replayProject } from "@/lib";
-import { fetchHistoryTasks } from "@/service/historyApi";
-import GroupedHistoryView from "@/components/GroupedHistoryView";
-import { useGlobalStore } from "@/store/globalStore";
+ CommandDialog,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+} from '@/components/ui/command';
+import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
+import { replayProject } from '@/lib';
+import { fetchHistoryTasks } from '@/service/historyApi';
+import { useGlobalStore } from '@/store/globalStore';
+import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
+import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
+import { Button } from './ui/button';
+import { DialogTitle } from './ui/dialog';
export function SearchHistoryDialog() {
- const {t} = useTranslation()
- const [open, setOpen] = useState(false);
- const [historyTasks, setHistoryTasks] = useState([]);
- const { history_type } = useGlobalStore();
- //Get Chatstore for the active project's task
- const { chatStore, projectStore } = useChatStoreAdapter();
- if (!chatStore) {
- return Loading...
;
- }
-
- const navigate = useNavigate();
- const handleSetActive = (projectId: string, question: string, historyId: string) => {
- const project = projectStore.getProjectById(projectId);
- //If project exists
- if (project) {
- // if there is record, show result
- projectStore.setHistoryId(projectId, historyId);
- projectStore.setActiveProject(projectId)
- navigate(`/`);
- setOpen(false);
- } else {
- // if there is no record, execute replay
- handleReplay(projectId, question, historyId);
- }
- };
+ const { t } = useTranslation();
+ const [open, setOpen] = useState(false);
+ const [historyTasks, setHistoryTasks] = useState([]);
+ const { history_type } = useGlobalStore();
+ //Get Chatstore for the active project's task
+ const { chatStore, projectStore } = useChatStoreAdapter();
- const handleReplay = async (projectId: string, question: string, historyId: string) => {
- setOpen(false);
- await replayProject(projectStore, navigate, projectId, question, historyId);
- };
+ const navigate = useNavigate();
+ const handleSetActive = (
+ projectId: string,
+ question: string,
+ historyId: string
+ ) => {
+ const project = projectStore.getProjectById(projectId);
+ //If project exists
+ if (project) {
+ // if there is record, show result
+ projectStore.setHistoryId(projectId, historyId);
+ projectStore.setActiveProject(projectId);
+ navigate(`/`);
+ setOpen(false);
+ } else {
+ // if there is no record, execute replay
+ handleReplay(projectId, question, historyId);
+ }
+ };
- const handleDelete = (taskId: string) => {
- // TODO: Implement delete functionality similar to HistorySidebar
- console.log("Delete task:", taskId);
- };
+ const handleReplay = async (
+ projectId: string,
+ question: string,
+ historyId: string
+ ) => {
+ setOpen(false);
+ await replayProject(projectStore, navigate, projectId, question, historyId);
+ };
- const handleShare = (taskId: string) => {
- // TODO: Implement share functionality similar to HistorySidebar
- console.log("Share task:", taskId);
- };
+ const handleDelete = (taskId: string) => {
+ // TODO: Implement delete functionality similar to HistorySidebar
+ console.log('Delete task:', taskId);
+ };
- useEffect(() => {
- fetchHistoryTasks(setHistoryTasks);
- }, []);
- return (
- <>
-
-
-
- {t("dashboard.search-dialog")}
-
-
-
- {t("dashboard.no-results")}
- {history_type === "grid" ? (
-
-
-
- ) : (
-
- {historyTasks.map((task) => (
- handleSetActive(task.task_id, task.question, task.id)}
- >
-
-
- {task.question}
-
-
- ))}
-
- )}
-
-
-
- >
- );
+ const handleShare = (taskId: string) => {
+ // TODO: Implement share functionality similar to HistorySidebar
+ console.log('Share task:', taskId);
+ };
+
+ useEffect(() => {
+ fetchHistoryTasks(setHistoryTasks);
+ }, []);
+
+ if (!chatStore) {
+ return Loading...
;
+ }
+
+ return (
+ <>
+
+
+
+ {t('dashboard.search-dialog')}
+
+
+
+ {t('dashboard.no-results')}
+ {history_type === 'grid' ? (
+
+
+
+ ) : (
+
+ {historyTasks.map((task) => (
+
+ handleSetActive(task.task_id, task.question, task.id)
+ }
+ >
+
+
+ {task.question}
+
+
+ ))}
+
+ )}
+
+
+
+ >
+ );
}
diff --git a/src/components/SearchInput/index.tsx b/src/components/SearchInput/index.tsx
index 08a1a2293..2a1c5cf1d 100644
--- a/src/components/SearchInput/index.tsx
+++ b/src/components/SearchInput/index.tsx
@@ -12,27 +12,26 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { Input } from "@/components/ui/input";
-import { Search } from "lucide-react";
-import { useTranslation } from "react-i18next";
+import { Input } from '@/components/ui/input';
+import { Search } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
interface SearchInputProps {
- value: string;
- onChange: (e: React.ChangeEvent) => void;
+ value: string;
+ onChange: (e: React.ChangeEvent) => void;
}
export default function SearchInput({ value, onChange }: SearchInputProps) {
- const { t } = useTranslation();
- return (
-
- }
- />
-
-
- );
+ const { t } = useTranslation();
+ return (
+
+ }
+ />
+
+ );
}
diff --git a/src/components/SideBar/index.tsx b/src/components/SideBar/index.tsx
index aa49cfcbb..eb5b5ed5f 100644
--- a/src/components/SideBar/index.tsx
+++ b/src/components/SideBar/index.tsx
@@ -12,65 +12,70 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import React, { useState } from "react";
-import { MenuToggleGroup, MenuToggleItem } from "@/components/MenuButton/MenuButton";
-import { FileDown, Inbox, LayoutGrid, MessageCircleQuestion, Network, Settings2Icon } from "lucide-react";
-import giftIcon from "@/assets/gift.svg";
+import giftIcon from '@/assets/gift.svg';
+import {
+ MenuToggleGroup,
+ MenuToggleItem,
+} from '@/components/MenuButton/MenuButton';
+import {
+ FileDown,
+ Inbox,
+ LayoutGrid,
+ MessageCircleQuestion,
+ Network,
+ Settings2Icon,
+} from 'lucide-react';
+import { useState } from 'react';
// Icons - you can replace these with actual icon components
-const HomeIcon = () => (
-
-);
+const HomeIcon = () => ;
-const WorkflowIcon = () => (
-
-);
+const WorkflowIcon = () => ;
-const InboxIcon = () => (
-
-);
+const InboxIcon = () => ;
-const SettingsIcon = () => (
-
-);
+const SettingsIcon = () => ;
-const BugIcon = () => (
-
-);
+const BugIcon = () => ;
const ReferIcon = () => (
-
+
);
-const SupportIcon = () => (
-
-);
+const SupportIcon = () => ;
interface SideBarProps {
className?: string;
}
export default function SideBar({ className }: SideBarProps) {
- const [activeItem, setActiveItem] = useState("home");
+ const [activeItem, setActiveItem] = useState('home');
const menuItems = [
- { id: "home", icon: , label: "Home" },
- { id: "workflow", icon: , label: "Workflow" },
- { id: "inbox", icon: , label: "Inbox" },
- { id: "settings", icon: , label: "Settings" },
+ { id: 'home', icon: , label: 'Home' },
+ { id: 'workflow', icon: , label: 'Workflow' },
+ { id: 'inbox', icon: , label: 'Inbox' },
+ { id: 'settings', icon: , label: 'Settings' },
];
const bottomItems = [
- { id: "bug", icon: , label: "Bug" },
- { id: "refer", icon: , label: "Refer" },
- { id: "support", icon: , label: "Support" },
+ { id: 'bug', icon: , label: 'Bug' },
+ { id: 'refer', icon: , label: 'Refer' },
+ { id: 'support', icon: , label: 'Support' },
];
return (
-
+
{/* Main menu items */}
-
+
{menuItems.map((item) => (
{/* Bottom menu items */}
-
-
+
+
{bottomItems.map((item) => (
void;
- clickable?: boolean;
+ all?: number;
+ done: number;
+ progress: number;
+ skipped: number;
+ reAssignTo?: number;
+ failed?: number;
+ forceVisible?: boolean;
+ selectedState?: TaskStateType;
+ onStateChange?: (selectedState: TaskStateType) => void;
+ clickable?: boolean;
}
export const TaskState = ({
- all,
- done,
- reAssignTo,
- progress,
- skipped,
- failed,
- forceVisible = false,
- selectedState = "all",
- onStateChange,
- clickable = true,
+ all,
+ done,
+ reAssignTo,
+ progress,
+ skipped,
+ failed,
+ forceVisible = false,
+ selectedState = 'all',
+ onStateChange,
+ clickable = true,
}: TaskStateProps) => {
- //Get Chatstore for the active project's task
- const { chatStore } = useChatStoreAdapter();
- if (!chatStore) {
- return Loading...
;
- }
-
- const { t } = useTranslation();
- const handleStateClick = (state: TaskStateType) => {
- if (!clickable || !onStateChange) return;
- onStateChange(state || "all");
- };
+ //Get Chatstore for the active project's task
+ const { chatStore } = useChatStoreAdapter();
- const isSelected = (state: TaskStateType) => {
- return selectedState === state;
- };
+ const { t } = useTranslation();
+ const handleStateClick = (state: TaskStateType) => {
+ if (!clickable || !onStateChange) return;
+ onStateChange(state || 'all');
+ };
- const numberClass = `rounded-lg inline-block align-bottom transition-all duration-300 ease-in-out max-w-[40px] group-hover:max-w-[40px] group-hover:opacity-100`;
+ const isSelected = (state: TaskStateType) => {
+ return selectedState === state;
+ };
- return (
-
-
- {/* All */}
- {all && (forceVisible || all > 0) ? (
-
handleStateClick("all")}
- >
-
- {t("chat.all")} {all}
-
-
- ) : null}
+ const numberClass = `rounded-lg inline-block align-bottom transition-all duration-300 ease-in-out max-w-[40px] group-hover:max-w-[40px] group-hover:opacity-100`;
- {/* Done */}
- {done && (forceVisible || done > 0) ? (
-
handleStateClick("done")}
- >
-
-
- {t("chat.done")} {done}
-
-
- ) : null}
+ if (!chatStore) {
+ return
Loading...
;
+ }
- {/* Reassigned */}
- {reAssignTo && (forceVisible || reAssignTo > 0) ? (
-
handleStateClick("reassigned")}
- >
-
-
- {t("chat.reassigned")}{" "}
- {reAssignTo}
-
-
- ) : null}
+ return (
+
+
+ {/* All */}
+ {all && (forceVisible || all > 0) ? (
+
handleStateClick('all')}
+ >
+
+ {t('chat.all')} {all}
+
+
+ ) : null}
- {/* Ongoing */}
- {progress && (forceVisible || progress > 0) ? (
-
handleStateClick("ongoing")}
- >
-
-
- {t("chat.ongoing")}{" "}
- {progress}
-
-
- ) : null}
+ {/* Done */}
+ {done && (forceVisible || done > 0) ? (
+
handleStateClick('done')}
+ >
+
+
+ {t('chat.done')} {done}
+
+
+ ) : null}
- {/* Failed */}
- {failed && (forceVisible || failed > 0) ? (
-
handleStateClick("failed")}
- >
-
-
- {t("chat.failed")} {failed}
-
-
- ) : null}
- {/* Pending */}
- {skipped && (forceVisible || skipped > 0) ? (
-
handleStateClick("pending")}
- >
-
-
- {t("chat.pending")} {skipped}
-
-
- ) : null}
-
-
- );
+ {/* Reassigned */}
+ {reAssignTo && (forceVisible || reAssignTo > 0) ? (
+
handleStateClick('reassigned')}
+ >
+
+
+ {t('chat.reassigned')}{' '}
+ {reAssignTo}
+
+
+ ) : null}
+
+ {/* Ongoing */}
+ {progress && (forceVisible || progress > 0) ? (
+
handleStateClick('ongoing')}
+ >
+
+
+ {t('chat.ongoing')}{' '}
+ {progress}
+
+
+ ) : null}
+
+ {/* Failed */}
+ {failed && (forceVisible || failed > 0) ? (
+
handleStateClick('failed')}
+ >
+
+
+ {t('chat.failed')} {failed}
+
+
+ ) : null}
+ {/* Pending */}
+ {skipped && (forceVisible || skipped > 0) ? (
+
handleStateClick('pending')}
+ >
+
+
+ {t('chat.pending')} {skipped}
+
+
+ ) : null}
+
+
+ );
};
diff --git a/src/components/Terminal/index.tsx b/src/components/Terminal/index.tsx
index 500d64d1f..4dd2b6f96 100644
--- a/src/components/Terminal/index.tsx
+++ b/src/components/Terminal/index.tsx
@@ -12,374 +12,387 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { useEffect, useRef, useState, useCallback } from "react";
-import { Terminal } from "@xterm/xterm";
-import { FitAddon } from "@xterm/addon-fit";
-import { WebLinksAddon } from "@xterm/addon-web-links";
-import "@xterm/xterm/css/xterm.css";
-import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
+import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
+import { FitAddon } from '@xterm/addon-fit';
+import { WebLinksAddon } from '@xterm/addon-web-links';
+import { Terminal } from '@xterm/xterm';
+import '@xterm/xterm/css/xterm.css';
+import { useCallback, useEffect, useRef, useState } from 'react';
// Terminal Component Properties Interface
interface TerminalComponentProps {
- content?: string[]; // terminal content array
- instanceId?: string; // terminal instance identifier, for multiple terminals
- showWelcome?: boolean; // whether to show welcome information
+ content?: string[]; // terminal content array
+ instanceId?: string; // terminal instance identifier, for multiple terminals
+ showWelcome?: boolean; // whether to show welcome information
}
export default function TerminalComponent({
- content,
- instanceId = "default",
- showWelcome = false,
+ content,
+ instanceId = 'default',
+ showWelcome = false,
}: TerminalComponentProps) {
- //Get Chatstore for the active project's task
- const { chatStore } = useChatStoreAdapter();
- if (!chatStore) {
- return Loading...
;
- }
-
+ //Get Chatstore for the active project's task
+ const { chatStore } = useChatStoreAdapter();
- // DOM references
- const terminalContainerRef = useRef(null); // terminal container reference
- const terminalRef = useRef(null); // terminal element reference
+ // DOM references
+ const terminalContainerRef = useRef(null); // terminal container reference
+ const terminalRef = useRef(null); // terminal element reference
- // xterm.js related references
- const xtermRef = useRef(null); // xterm instance reference
- const fitAddonRef = useRef(null); // fit addon reference
+ // xterm.js related references
+ const xtermRef = useRef(null); // xterm instance reference
+ const fitAddonRef = useRef(null); // fit addon reference
- // state management
- const lastTerminalLength = useRef(0); // record last content length, for incremental update
- const [currentLine, setCurrentLine] = useState(""); // current input line
- const [cursorPos, setCursorPos] = useState(0); // cursor position
- const currentLineRef = useRef(""); // current input line ref, for event handling
- const cursorPosRef = useRef(0); // cursor position ref, for event handling
+ // state management
+ const lastTerminalLength = useRef(0); // record last content length, for incremental update
+ const [currentLine, setCurrentLine] = useState(''); // current input line
+ const [cursorPos, setCursorPos] = useState(0); // cursor position
+ const currentLineRef = useRef(''); // current input line ref, for event handling
+ const cursorPosRef = useRef(0); // cursor position ref, for event handling
- // terminal configuration
- const promptText = "Eigent:~$ "; // prompt text
- const isInitialized = useRef(false); // initialization identifier, prevent duplicate initialization
+ // terminal configuration
+ const promptText = 'Eigent:~$ '; // prompt text
+ const isInitialized = useRef(false); // initialization identifier, prevent duplicate initialization
- // synchronize state to ref, for event handling
- useEffect(() => {
- currentLineRef.current = currentLine;
- }, [currentLine]);
+ // synchronize state to ref, for event handling
+ useEffect(() => {
+ currentLineRef.current = currentLine;
+ }, [currentLine]);
- useEffect(() => {
- cursorPosRef.current = cursorPos;
- }, [cursorPos]);
+ useEffect(() => {
+ cursorPosRef.current = cursorPos;
+ }, [cursorPos]);
- // keyboard input handling function
- const handleKeyInput = useCallback(
- ({ key, domEvent }: { key: string; domEvent: KeyboardEvent }) => {
- const ev = domEvent;
- const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey; // check if it is printable character
- const terminal = xtermRef.current;
- if (!terminal) return;
+ // keyboard input handling function
+ const handleKeyInput = useCallback(
+ ({ key, domEvent }: { key: string; domEvent: KeyboardEvent }) => {
+ const ev = domEvent;
+ const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey; // check if it is printable character
+ const terminal = xtermRef.current;
+ if (!terminal) return;
- if (ev.keyCode === 13) {
- // Enter key: execute command
- terminal.writeln("");
- if (currentLineRef.current.trim()) {
- terminal.writeln(
- `\x1b[90m# Executed: ${currentLineRef.current}\x1b[0m`
- );
- terminal.writeln(
- `\x1b[33m⚠ Interactive mode not fully implemented\x1b[0m`
- );
- }
- setCurrentLine("");
- setCursorPos(0);
- terminal.write(promptText);
- } else if (ev.keyCode === 8) {
- // Backspace key: delete character
- if (cursorPosRef.current > 0) {
- const newLine =
- currentLineRef.current.slice(0, cursorPosRef.current - 1) +
- currentLineRef.current.slice(cursorPosRef.current);
- setCurrentLine(newLine);
- setCursorPos(cursorPosRef.current - 1);
- terminal.write("\b \b"); // delete character before cursor
- }
- } else if (ev.keyCode === 37) {
- // left arrow: move cursor left
- if (cursorPosRef.current > 0) {
- setCursorPos(cursorPosRef.current - 1);
- terminal.write("\x1b[D"); // ANSI escape sequence: move cursor left
- }
- } else if (ev.keyCode === 39) {
- // right arrow: move cursor right
- if (cursorPosRef.current < currentLineRef.current.length) {
- setCursorPos(cursorPosRef.current + 1);
- terminal.write("\x1b[C"); // ANSI escape sequence: move cursor right
- }
- } else if (printable) {
- // printable character: insert at cursor position
- const newLine =
- currentLineRef.current.slice(0, cursorPosRef.current) +
- key +
- currentLineRef.current.slice(cursorPosRef.current);
- setCurrentLine(newLine);
- setCursorPos(cursorPosRef.current + 1);
- terminal.write(key);
- }
- },
- [promptText]
- );
+ if (ev.keyCode === 13) {
+ // Enter key: execute command
+ terminal.writeln('');
+ if (currentLineRef.current.trim()) {
+ terminal.writeln(
+ `\x1b[90m# Executed: ${currentLineRef.current}\x1b[0m`
+ );
+ terminal.writeln(
+ `\x1b[33m⚠ Interactive mode not fully implemented\x1b[0m`
+ );
+ }
+ setCurrentLine('');
+ setCursorPos(0);
+ terminal.write(promptText);
+ } else if (ev.keyCode === 8) {
+ // Backspace key: delete character
+ if (cursorPosRef.current > 0) {
+ const newLine =
+ currentLineRef.current.slice(0, cursorPosRef.current - 1) +
+ currentLineRef.current.slice(cursorPosRef.current);
+ setCurrentLine(newLine);
+ setCursorPos(cursorPosRef.current - 1);
+ terminal.write('\b \b'); // delete character before cursor
+ }
+ } else if (ev.keyCode === 37) {
+ // left arrow: move cursor left
+ if (cursorPosRef.current > 0) {
+ setCursorPos(cursorPosRef.current - 1);
+ terminal.write('\x1b[D'); // ANSI escape sequence: move cursor left
+ }
+ } else if (ev.keyCode === 39) {
+ // right arrow: move cursor right
+ if (cursorPosRef.current < currentLineRef.current.length) {
+ setCursorPos(cursorPosRef.current + 1);
+ terminal.write('\x1b[C'); // ANSI escape sequence: move cursor right
+ }
+ } else if (printable) {
+ // printable character: insert at cursor position
+ const newLine =
+ currentLineRef.current.slice(0, cursorPosRef.current) +
+ key +
+ currentLineRef.current.slice(cursorPosRef.current);
+ setCurrentLine(newLine);
+ setCursorPos(cursorPosRef.current + 1);
+ terminal.write(key);
+ }
+ },
+ [promptText]
+ );
- // initialize xterm terminal
- useEffect(() => {
- if (!terminalRef.current || isInitialized.current) return;
- console.log("isInitialized.current", isInitialized.current);
- // mark as initialized
- isInitialized.current = true;
+ // initialize xterm terminal
+ useEffect(() => {
+ if (!terminalRef.current || isInitialized.current || !chatStore) return;
+ console.log('isInitialized.current', isInitialized.current);
+ // mark as initialized
+ isInitialized.current = true;
- // create terminal instance
- const terminal = new Terminal({
- theme: {
- background: "transparent", // transparent background
- foreground: "#ffffff", // white foreground
- cursor: "#00ff00", // green cursor
- cursorAccent: "#00ff00", // cursor accent
- },
- fontFamily: '"Courier New", Courier, monospace', // monospace font
- fontSize: 12, // font size
- lineHeight: 1.2, // line height
- letterSpacing: 0, // letter spacing
- cursorBlink: true, // cursor blink
- allowProposedApi: true, // allow proposed API
- scrollback: 1000, // scrollback lines
- rightClickSelectsWord: true, // right click selects word
- smoothScrollDuration: 0, // smooth scroll duration
- fastScrollModifier: "alt", // fast scroll modifier
- convertEol: true, // convert end of line
- windowsMode: true, // Windows mode
- cols: 100, // columns
- rows: 30, // rows
- });
+ // create terminal instance
+ const terminal = new Terminal({
+ theme: {
+ background: 'transparent', // transparent background
+ foreground: '#ffffff', // white foreground
+ cursor: '#00ff00', // green cursor
+ cursorAccent: '#00ff00', // cursor accent
+ },
+ fontFamily: '"Courier New", Courier, monospace', // monospace font
+ fontSize: 12, // font size
+ lineHeight: 1.2, // line height
+ letterSpacing: 0, // letter spacing
+ cursorBlink: true, // cursor blink
+ allowProposedApi: true, // allow proposed API
+ scrollback: 1000, // scrollback lines
+ rightClickSelectsWord: true, // right click selects word
+ smoothScrollDuration: 0, // smooth scroll duration
+ fastScrollModifier: 'alt', // fast scroll modifier
+ convertEol: true, // convert end of line
+ windowsMode: true, // Windows mode
+ cols: 100, // columns
+ rows: 30, // rows
+ });
- // add plugins
- const fitAddon = new FitAddon(); // fit addon
- const webLinksAddon = new WebLinksAddon(); // web links addon
+ // add plugins
+ const fitAddon = new FitAddon(); // fit addon
+ const webLinksAddon = new WebLinksAddon(); // web links addon
- terminal.loadAddon(fitAddon);
- terminal.loadAddon(webLinksAddon);
+ terminal.loadAddon(fitAddon);
+ terminal.loadAddon(webLinksAddon);
- // open terminal
- terminal.open(terminalRef.current);
+ // open terminal
+ terminal.open(terminalRef.current);
- // wait for layout to stabilize and adapt size, then write content
- setTimeout(() => {
- fitAddon.fit(); // adapt container size
+ // wait for layout to stabilize and adapt size, then write content
+ setTimeout(() => {
+ fitAddon.fit(); // adapt container size
- // only show welcome information when needed
- if (showWelcome) {
- terminal.writeln("\x1b[32m=== Eigent Terminal ===\x1b[0m");
- terminal.writeln(`\x1b[32mInstance: ${instanceId}\x1b[0m`);
- terminal.writeln("\x1b[32mReady for commands...\x1b[0m");
- terminal.writeln("");
- }
+ // only show welcome information when needed
+ if (showWelcome) {
+ terminal.writeln('\x1b[32m=== Eigent Terminal ===\x1b[0m');
+ terminal.writeln(`\x1b[32mInstance: ${instanceId}\x1b[0m`);
+ terminal.writeln('\x1b[32mReady for commands...\x1b[0m');
+ terminal.writeln('');
+ }
- // show prompt
- // terminal.write(promptText);
- }, 300);
+ // show prompt
+ // terminal.write(promptText);
+ }, 300);
- // save reference
- xtermRef.current = terminal;
- fitAddonRef.current = fitAddon;
+ // save reference
+ xtermRef.current = terminal;
+ fitAddonRef.current = fitAddon;
- // add keyboard input handling
- terminal.onKey(handleKeyInput);
+ // add keyboard input handling
+ terminal.onKey(handleKeyInput);
- // clean up function
- return () => {
- terminal.dispose(); // destroy terminal instance
- xtermRef.current = null;
- isInitialized.current = false;
- };
- }, [handleKeyInput, promptText, showWelcome, instanceId]);
+ // clean up function
+ return () => {
+ terminal.dispose(); // destroy terminal instance
+ xtermRef.current = null;
+ isInitialized.current = false;
+ };
+ }, [handleKeyInput, promptText, showWelcome, instanceId, chatStore]);
- // listen to container size change
- useEffect(() => {
- if (!terminalContainerRef.current || !fitAddonRef.current) return;
+ // listen to container size change
+ useEffect(() => {
+ if (!terminalContainerRef.current || !fitAddonRef.current || !chatStore)
+ return;
- // use ResizeObserver to listen to container size change
- const resizeObserver = new ResizeObserver((entries) => {
- for (const entry of entries) {
- // delay execution of fit to ensure layout stability
- setTimeout(() => {
- if (fitAddonRef.current) {
- fitAddonRef.current.fit();
- }
- }, 100);
- }
- });
+ // use ResizeObserver to listen to container size change
+ const resizeObserver = new ResizeObserver((entries) => {
+ for (const _entry of entries) {
+ // delay execution of fit to ensure layout stability
+ setTimeout(() => {
+ if (fitAddonRef.current) {
+ fitAddonRef.current.fit();
+ }
+ }, 100);
+ }
+ });
- resizeObserver.observe(terminalContainerRef.current);
+ resizeObserver.observe(terminalContainerRef.current);
- // listen to window size change
- const handleResize = () => {
- setTimeout(() => {
- if (fitAddonRef.current) {
- fitAddonRef.current.fit();
- }
- }, 150);
- };
- window.addEventListener("resize", handleResize);
+ // listen to window size change
+ const handleResize = () => {
+ setTimeout(() => {
+ if (fitAddonRef.current) {
+ fitAddonRef.current.fit();
+ }
+ }, 150);
+ };
+ window.addEventListener('resize', handleResize);
- // clean up listeners
- return () => {
- resizeObserver.disconnect();
- window.removeEventListener("resize", handleResize);
- };
- }, []);
+ // clean up listeners
+ return () => {
+ resizeObserver.disconnect();
+ window.removeEventListener('resize', handleResize);
+ };
+ }, [chatStore]);
- // listen to terminal data change and write to xterm
- useEffect(() => {
- if (!xtermRef.current || !content) return;
- const terminalData = content;
- const currentLength = terminalData.length;
+ // listen to terminal data change and write to xterm
+ useEffect(() => {
+ if (!xtermRef.current || !content || !chatStore) return;
+ const terminalData = content;
+ const currentLength = terminalData.length;
- // check if it is the case of component re-initialization
- // if lastTerminalLength is 0 but content has data, it means re-initialization
- if (lastTerminalLength.current === 0 && currentLength > 0) {
- console.log("component re-initialization, skip history data write");
- lastTerminalLength.current = currentLength;
- return;
- }
+ // check if it is the case of component re-initialization
+ // if lastTerminalLength is 0 but content has data, it means re-initialization
+ if (lastTerminalLength.current === 0 && currentLength > 0) {
+ console.log('component re-initialization, skip history data write');
+ lastTerminalLength.current = currentLength;
+ return;
+ }
- // only process new data (incremental update)
- if (currentLength > lastTerminalLength.current) {
- const newData = terminalData.slice(lastTerminalLength.current);
+ // only process new data (incremental update)
+ if (currentLength > lastTerminalLength.current) {
+ const newData = terminalData.slice(lastTerminalLength.current);
- console.log("newData", newData);
- newData.forEach((item) => {
- if (!xtermRef.current) return;
+ console.log('newData', newData);
+ newData.forEach((item) => {
+ if (!xtermRef.current) return;
- // move to line head and clear whole line
- xtermRef.current.write("\r");
- xtermRef.current.write("\x1b[2K"); // clear whole line
+ // move to line head and clear whole line
+ xtermRef.current.write('\r');
+ xtermRef.current.write('\x1b[2K'); // clear whole line
- // process output content
- const formattedOutput = item
- .replace(/\r?\n$/, "") // remove trailing newline
- .replace(/\t/g, " ") // convert tab to 4 spaces
- .replace(/\r/g, ""); // remove carriage return
+ // process output content
+ const formattedOutput = item
+ .replace(/\r?\n$/, '') // remove trailing newline
+ .replace(/\t/g, ' ') // convert tab to 4 spaces
+ .replace(/\r/g, ''); // remove carriage return
- if (formattedOutput.trim()) {
- xtermRef.current.writeln(`\x1b[36m[Eigent]\x1b[0m ${formattedOutput}`);
- } else {
- xtermRef.current.writeln("");
- }
+ if (formattedOutput.trim()) {
+ xtermRef.current.writeln(
+ `\x1b[36m[Eigent]\x1b[0m ${formattedOutput}`
+ );
+ } else {
+ xtermRef.current.writeln('');
+ }
- // re-display prompt
- xtermRef.current.write(promptText);
+ // re-display prompt
+ xtermRef.current.write(promptText);
- // re-display current input
- if (currentLineRef.current) {
- xtermRef.current.write(currentLineRef.current);
+ // re-display current input
+ if (currentLineRef.current) {
+ xtermRef.current.write(currentLineRef.current);
- // if cursor is not at the end, move to the correct position
- if (cursorPosRef.current < currentLineRef.current.length) {
- const moveBack =
- currentLineRef.current.length - cursorPosRef.current;
- for (let i = 0; i < moveBack; i++) {
- xtermRef.current.write("\x1b[D"); // move cursor left
- }
- }
- }
- });
+ // if cursor is not at the end, move to the correct position
+ if (cursorPosRef.current < currentLineRef.current.length) {
+ const moveBack =
+ currentLineRef.current.length - cursorPosRef.current;
+ for (let i = 0; i < moveBack; i++) {
+ xtermRef.current.write('\x1b[D'); // move cursor left
+ }
+ }
+ }
+ });
- lastTerminalLength.current = currentLength;
- }
- }, [content, promptText]);
+ lastTerminalLength.current = currentLength;
+ }
+ }, [content, promptText, chatStore]);
- // reset terminal when switching task
- useEffect(() => {
- if (!xtermRef.current) return;
+ // reset terminal when switching task
+ useEffect(() => {
+ if (!xtermRef.current || !chatStore) return;
- // clear terminal
- xtermRef.current.clear();
+ // clear terminal
+ xtermRef.current.clear();
- // reset state
- lastTerminalLength.current = 0;
- setCurrentLine("");
- setCursorPos(0);
+ // reset state
+ lastTerminalLength.current = 0;
+ // Use setTimeout to defer state updates and avoid cascading renders
+ setTimeout(() => {
+ setCurrentLine('');
+ setCursorPos(0);
+ }, 0);
- // delay re-initialization
- setTimeout(() => {
- if (!xtermRef.current || !fitAddonRef.current) return;
+ // delay re-initialization
+ setTimeout(() => {
+ if (!xtermRef.current || !fitAddonRef.current) return;
- // re-adapt size
- fitAddonRef.current.fit();
+ // re-adapt size
+ fitAddonRef.current.fit();
- // only show switch information on main instance
- if (showWelcome) {
- xtermRef.current.writeln("\x1b[32m=== Eigent Terminal ===\x1b[0m");
- xtermRef.current.writeln(`\x1b[32mInstance: ${instanceId}\x1b[0m`);
- xtermRef.current.writeln("\x1b[32mTask switched...\x1b[0m");
- xtermRef.current.writeln("");
- }
+ // only show switch information on main instance
+ if (showWelcome) {
+ xtermRef.current.writeln('\x1b[32m=== Eigent Terminal ===\x1b[0m');
+ xtermRef.current.writeln(`\x1b[32mInstance: ${instanceId}\x1b[0m`);
+ xtermRef.current.writeln('\x1b[32mTask switched...\x1b[0m');
+ xtermRef.current.writeln('');
+ }
- // if there is history data, re-write
- if (chatStore.activeTaskId) {
- const terminalData = content || [];
- if (terminalData.length > 0) {
- xtermRef.current.writeln("\x1b[90m--- Previous Output ---\x1b[0m");
- terminalData.forEach((item) => {
- const formattedOutput = item
- .replace(/\r?\n$/, "")
- .replace(/\t/g, " ")
- .replace(/\r/g, "");
+ // if there is history data, re-write
+ if (chatStore.activeTaskId) {
+ const terminalData = content || [];
+ if (terminalData.length > 0) {
+ xtermRef.current.writeln('\x1b[90m--- Previous Output ---\x1b[0m');
+ terminalData.forEach((item) => {
+ const formattedOutput = item
+ .replace(/\r?\n$/, '')
+ .replace(/\t/g, ' ')
+ .replace(/\r/g, '');
- if (formattedOutput.trim()) {
- xtermRef.current?.writeln(
- `\x1b[36m[Eigent]\x1b[0m ${formattedOutput}`
- );
- }
- });
- xtermRef.current.writeln(
- "\x1b[90m--- End Previous Output ---\x1b[0m"
- );
- xtermRef.current.writeln("");
- }
- lastTerminalLength.current = terminalData.length;
- }
+ if (formattedOutput.trim()) {
+ xtermRef.current?.writeln(
+ `\x1b[36m[Eigent]\x1b[0m ${formattedOutput}`
+ );
+ }
+ });
+ xtermRef.current.writeln(
+ '\x1b[90m--- End Previous Output ---\x1b[0m'
+ );
+ xtermRef.current.writeln('');
+ }
+ lastTerminalLength.current = terminalData.length;
+ }
- // show prompt
- xtermRef.current.write(promptText);
- }, 200);
- }, [chatStore.activeTaskId, showWelcome, instanceId]);
+ // show prompt
+ xtermRef.current.write(promptText);
+ }, 200);
+ }, [
+ chatStore?.activeTaskId,
+ showWelcome,
+ instanceId,
+ content,
+ promptText,
+ chatStore,
+ ]);
- // render terminal component
- return (
-
- {/* background blur effect */}
-
+ if (!chatStore) {
+ return
Loading...
;
+ }
- {/* terminal container */}
-
+ // render terminal component
+ return (
+
+ {/* background blur effect */}
+
- {/* custom style: override xterm.js character spacing */}
-
+
+ {/* custom style: override xterm.js character spacing */}
+
-
- );
+ }}
+ />
+
+ );
}
diff --git a/src/components/TerminalAgentWrokSpace/index.tsx b/src/components/TerminalAgentWrokSpace/index.tsx
index b04c87266..bc00f56f1 100644
--- a/src/components/TerminalAgentWrokSpace/index.tsx
+++ b/src/components/TerminalAgentWrokSpace/index.tsx
@@ -12,214 +12,204 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { useEffect, useState, useRef } from "react";
+import { fetchPut } from '@/api/http';
+import Terminal from '@/components/Terminal';
+import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
import {
- ArrowDown,
- ArrowUp,
- Bird,
- Bot,
- ChevronLeft,
- CodeXml,
- FileText,
- GalleryThumbnails,
- Globe,
- Hand,
- Image,
- Settings2,
-} from "lucide-react";
-import { Button } from "../ui/button";
-import { fetchPut } from "@/api/http";
-import Terminal from "@/components/Terminal";
-import { useTranslation } from "react-i18next";
-import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
+ ArrowDown,
+ ArrowUp,
+ Bird,
+ Bot,
+ ChevronLeft,
+ CodeXml,
+ FileText,
+ GalleryThumbnails,
+ Globe,
+ Image,
+ Settings2,
+} from 'lucide-react';
+import { useMemo, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Button } from '../ui/button';
export default function TerminalAgentWrokSpace() {
- //Get Chatstore for the active project's task
- const { chatStore, projectStore } = useChatStoreAdapter();
- if (!chatStore) {
- return Loading...
;
- }
-
- const { t } = useTranslation();
- const [isSingleMode, setIsSingleMode] = useState(false);
- const scrollContainerRef = useRef(null);
+ //Get Chatstore for the active project's task
+ const { chatStore, projectStore } = useChatStoreAdapter();
+ const { t } = useTranslation();
+ const [isSingleMode, setIsSingleMode] = useState(false);
+ const scrollContainerRef = useRef(null);
+ const [isTakeControl, setIsTakeControl] = useState(false);
- const agentMap = {
- developer_agent: {
- name: "Developer Agent",
- icon: ,
- textColor: "text-emerald-700",
- bgColor: "bg-bg-fill-coding-active",
- shapeColor: "bg-bg-fill-coding-default",
- borderColor: "border-bg-fill-coding-active",
- bgColorLight: "bg-emerald-200",
- },
- browser_agent: {
- name: "Browser Agent",
- icon: ,
- textColor: "text-blue-700",
- bgColor: "bg-bg-fill-browser-active",
- shapeColor: "bg-bg-fill-browser-default",
- borderColor: "border-bg-fill-browser-active",
- bgColorLight: "bg-blue-200",
- },
- document_agent: {
- name: "Document Agent",
- icon: ,
- textColor: "text-yellow-700",
- bgColor: "bg-bg-fill-writing-active",
- shapeColor: "bg-bg-fill-writing-default",
- borderColor: "border-bg-fill-writing-active",
- bgColorLight: "bg-yellow-200",
- },
- multi_modal_agent: {
- name: "Multi Modal Agent",
- icon: ,
- textColor: "text-fuchsia-700",
- bgColor: "bg-bg-fill-multimodal-active",
- shapeColor: "bg-bg-fill-multimodal-default",
- borderColor: "border-bg-fill-multimodal-active",
- bgColorLight: "bg-fuchsia-200",
- },
- social_medium_agent: {
- name: "Social Media Agent",
- icon: ,
- textColor: "text-purple-700",
- bgColor: "bg-violet-700",
- shapeColor: "bg-violet-300",
- borderColor: "border-violet-700",
- bgColorLight: "bg-purple-50",
- },
- };
- const [activeAgent, setActiveAgent] = useState(null);
- useEffect(() => {
- const taskAssigning =
- chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning;
- if (taskAssigning) {
- const activeAgent = taskAssigning.find(
- (item) =>
- item.agent_id ===
- chatStore.tasks[chatStore.activeTaskId as string]?.activeWorkSpace
- );
- setActiveAgent(() => {
- if (activeAgent) {
- return activeAgent;
- }
- return null;
- });
- }
- }, [
- chatStore.tasks[chatStore.activeTaskId as string].taskAssigning,
- chatStore.tasks[chatStore.activeTaskId as string].activeWorkSpace,
- ]);
+ const activeTaskId = chatStore?.activeTaskId;
+ const taskAssigning = chatStore?.tasks[activeTaskId as string]?.taskAssigning;
+ const activeWorkSpace =
+ chatStore?.tasks[activeTaskId as string]?.activeWorkSpace;
- const [isTakeControl, setIsTakeControl] = useState(false);
- const handleTakeControl = (id: string) => {
- console.log("handleTakeControl", id);
- fetchPut(`/task/${projectStore.activeProjectId}/take-control`, {
- action: "pause",
- });
- setIsTakeControl(true);
- };
+ // Use useMemo to derive activeAgent from taskAssigning and activeWorkSpace
+ const activeAgent = useMemo(() => {
+ if (!chatStore || !taskAssigning) return null;
+ return (
+ taskAssigning.find((item) => item.agent_id === activeWorkSpace) || null
+ );
+ }, [chatStore, taskAssigning, activeWorkSpace]);
- return isTakeControl ? (
-
-
-
-
-
-
-
-
- ) : (
-
-
-
-
-
-
-
-
- {agentMap[activeAgent?.type as keyof typeof agentMap]?.name}
-
-
-
- {
- activeAgent?.tasks?.filter(
- (task) => task.status && task.status !== "running"
- ).length
- }
- /{activeAgent?.tasks?.length}
-
-
-
-
+ if (!chatStore) {
+ return
Loading...
;
+ }
- {activeAgent?.tasks.filter(
- (task) => task?.terminal && task?.terminal.length > 0
- )?.length === 1 ? (
-
- {activeAgent?.tasks.filter(
- (task) => task?.terminal && task?.terminal.length > 0
- )[0] && (
-
- // handleTakeControl(
- // activeAgent?.activeWebviewIds?.[0]?.id || ""
- // )
- // }
- className="cursor-pointer relative h-full w-full group pt-sm rounded-b-2xl"
- >
-
task?.terminal && task?.terminal.length > 0
- )[0].terminal
- }
- />
- {/*
+ const agentMap = {
+ developer_agent: {
+ name: 'Developer Agent',
+ icon:
,
+ textColor: 'text-emerald-700',
+ bgColor: 'bg-bg-fill-coding-active',
+ shapeColor: 'bg-bg-fill-coding-default',
+ borderColor: 'border-bg-fill-coding-active',
+ bgColorLight: 'bg-emerald-200',
+ },
+ browser_agent: {
+ name: 'Browser Agent',
+ icon:
,
+ textColor: 'text-blue-700',
+ bgColor: 'bg-bg-fill-browser-active',
+ shapeColor: 'bg-bg-fill-browser-default',
+ borderColor: 'border-bg-fill-browser-active',
+ bgColorLight: 'bg-blue-200',
+ },
+ document_agent: {
+ name: 'Document Agent',
+ icon:
,
+ textColor: 'text-yellow-700',
+ bgColor: 'bg-bg-fill-writing-active',
+ shapeColor: 'bg-bg-fill-writing-default',
+ borderColor: 'border-bg-fill-writing-active',
+ bgColorLight: 'bg-yellow-200',
+ },
+ multi_modal_agent: {
+ name: 'Multi Modal Agent',
+ icon:
,
+ textColor: 'text-fuchsia-700',
+ bgColor: 'bg-bg-fill-multimodal-active',
+ shapeColor: 'bg-bg-fill-multimodal-default',
+ borderColor: 'border-bg-fill-multimodal-active',
+ bgColorLight: 'bg-fuchsia-200',
+ },
+ social_medium_agent: {
+ name: 'Social Media Agent',
+ icon:
,
+ textColor: 'text-purple-700',
+ bgColor: 'bg-violet-700',
+ shapeColor: 'bg-violet-300',
+ borderColor: 'border-violet-700',
+ bgColorLight: 'bg-purple-50',
+ },
+ };
+ const _handleTakeControl = (id: string) => {
+ console.log('handleTakeControl', id);
+ fetchPut(`/task/${projectStore.activeProjectId}/take-control`, {
+ action: 'pause',
+ });
+ setIsTakeControl(true);
+ };
+
+ return isTakeControl ? (
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+ {agentMap[activeAgent?.type as keyof typeof agentMap]?.name}
+
+
+
+ {
+ activeAgent?.tasks?.filter(
+ (task) => task.status && task.status !== 'running'
+ ).length
+ }
+ /{activeAgent?.tasks?.length}
+
+
+
+
+
+ {activeAgent?.tasks.filter(
+ (task) => task?.terminal && task?.terminal.length > 0
+ )?.length === 1 ? (
+
+ {activeAgent?.tasks.filter(
+ (task) => task?.terminal && task?.terminal.length > 0
+ )[0] && (
+
+ // handleTakeControl(
+ // activeAgent?.activeWebviewIds?.[0]?.id || ""
+ // )
+ // }
+ className="group relative h-full w-full cursor-pointer rounded-b-2xl pt-sm"
+ >
+
task?.terminal && task?.terminal.length > 0
+ )[0].terminal
+ }
+ />
+ {/*
*/}
-
- )}
-
- ) : (
- activeAgent?.tasks.filter(
- (task) => task?.terminal && task?.terminal.length > 0
- ) && (
-
- {activeAgent?.tasks
- .filter((task) => task?.terminal && task?.terminal.length > 0)
- .map((task) => {
- return (
-
-
- {/*
+ )}
+
+ ) : (
+ activeAgent?.tasks.filter(
+ (task) => task?.terminal && task?.terminal.length > 0
+ ) && (
+
+ {activeAgent?.tasks
+ .filter((task) => task?.terminal && task?.terminal.length > 0)
+ .map((task) => {
+ return (
+
+
+ {/*
handleTakeControl(task.id)}
className="flex justify-center items-center opacity-0 transition-all group-hover:opacity-100 rounded-lg absolute inset-0 w-full h-full bg-black/20 pointer-events-none"
>
@@ -267,83 +257,83 @@ export default function TerminalAgentWrokSpace() {
*/}
-
- );
- })}
-
- )
- )}
- {activeAgent?.tasks.filter(
- (task) => task?.terminal && task?.terminal.length > 0
- ).length !== 1 && (
-
- {isSingleMode && (
-
- )}
- {isSingleMode && (
-
- )}
-
-
- )}
-
-
- );
-}
\ No newline at end of file
+
+ );
+ })}
+
+ )
+ )}
+ {activeAgent?.tasks.filter(
+ (task) => task?.terminal && task?.terminal.length > 0
+ ).length !== 1 && (
+
+ {isSingleMode && (
+
+ )}
+ {isSingleMode && (
+
+ )}
+
+
+ )}
+
+
+ );
+}
diff --git a/src/components/ThemeProvider.tsx b/src/components/ThemeProvider.tsx
index aca29f906..7350e9c2f 100644
--- a/src/components/ThemeProvider.tsx
+++ b/src/components/ThemeProvider.tsx
@@ -12,37 +12,35 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { useEffect } from 'react';
import { useAuthStore } from '@/store/authStore';
+import { useEffect } from 'react';
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const { appearance } = useAuthStore();
- useEffect(() => {
- // set data-theme attribute based on appearance
- const root = document.documentElement;
+ useEffect(() => {
+ // set data-theme attribute based on appearance
+ const root = document.documentElement;
- // remove all possible data-theme attributes
- root.removeAttribute('data-theme');
+ // remove all possible data-theme attributes
+ root.removeAttribute('data-theme');
- // set the corresponding data-theme based on appearance
- if (appearance === "transparent") {
- root.setAttribute("data-theme", "transparent");
- } else if (appearance === "light") {
+ // set the corresponding data-theme based on appearance
+ if (appearance === 'transparent') {
+ root.setAttribute('data-theme', 'transparent');
+ } else if (appearance === 'light') {
root.setAttribute('data-theme', 'light');
} else if (appearance === 'dark') {
root.setAttribute('data-theme', 'dark');
- }
- else {
+ } else {
root.setAttribute('data-theme', 'light');
}
-
}, [appearance]);
- // initialize theme
- useEffect(() => {
- const root = document.documentElement;
- const currentTheme = root.getAttribute("data-theme");
+ // initialize theme
+ useEffect(() => {
+ const root = document.documentElement;
+ const currentTheme = root.getAttribute('data-theme');
if (!currentTheme) {
if (appearance === 'transparent') {
@@ -52,8 +50,8 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
} else {
root.setAttribute('data-theme', 'light');
}
- }
- }, []); // only execute once when the component is mounted
+ }
+ }, [appearance]); // only execute once when the component is mounted
return <>{children}>;
-}
\ No newline at end of file
+}
diff --git a/src/components/Toast/creditsToast.tsx b/src/components/Toast/creditsToast.tsx
index efb102bd7..9c5f2319a 100644
--- a/src/components/Toast/creditsToast.tsx
+++ b/src/components/Toast/creditsToast.tsx
@@ -12,32 +12,32 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { toast } from "sonner";
-import i18n from "@/i18n";
+import i18n from '@/i18n';
+import { toast } from 'sonner';
export function showCreditsToast() {
- toast.dismiss();
- toast(
-
,
- {
- duration: Infinity,
- closeButton: true,
- }
- );
+ toast.dismiss();
+ toast(
+
,
+ {
+ duration: Infinity,
+ closeButton: true,
+ }
+ );
}
diff --git a/src/components/Toast/storageToast.tsx b/src/components/Toast/storageToast.tsx
index 5547a2c1e..e10639a0c 100644
--- a/src/components/Toast/storageToast.tsx
+++ b/src/components/Toast/storageToast.tsx
@@ -12,27 +12,30 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { toast } from "sonner";
-import { useTranslation } from "react-i18next";
+import i18n from '@/i18n';
+import { toast } from 'sonner';
+
export function showStorageToast() {
- toast.dismiss();
- const { t } = useTranslation();
- toast(
-
,
- {
- duration: Infinity,
- closeButton: true,
- }
- );
+ toast.dismiss();
+ toast(
+
,
+ {
+ duration: Infinity,
+ closeButton: true,
+ }
+ );
}
diff --git a/src/components/Toast/trafficToast.tsx b/src/components/Toast/trafficToast.tsx
index 70993310e..5778f68fc 100644
--- a/src/components/Toast/trafficToast.tsx
+++ b/src/components/Toast/trafficToast.tsx
@@ -12,18 +12,20 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { toast } from "sonner";
-import { useTranslation } from "react-i18next";
+import i18n from '@/i18n';
+import { toast } from 'sonner';
+
export function showTrafficToast() {
- toast.dismiss();
- const { t } = useTranslation();
- toast(
-
- {t("chat.we-re-experiencing-high-traffic-please-try-again-in-a-few-moments")}
-
,
- {
- duration: 5000,
- closeButton: true,
- }
- );
+ toast.dismiss();
+ toast(
+
+ {i18n.t(
+ 'chat.we-re-experiencing-high-traffic-please-try-again-in-a-few-moments'
+ )}
+
,
+ {
+ duration: 5000,
+ closeButton: true,
+ }
+ );
}
diff --git a/src/components/TopBar/index.tsx b/src/components/TopBar/index.tsx
index c75a33606..ff2042097 100644
--- a/src/components/TopBar/index.tsx
+++ b/src/components/TopBar/index.tsx
@@ -12,397 +12,447 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { useState, useRef, useEffect, useMemo } from "react";
import {
- Settings,
- Minus,
- Square,
- X,
- FileDown,
- Plus,
- Power,
- ChevronDown,
- ChevronLeft,
- House,
-} from "lucide-react";
-import folderIcon from "@/assets/Folder.svg";
-import { Button } from "@/components/ui/button";
-import { useLocation, useNavigate } from "react-router-dom";
-import { useSidebarStore } from "@/store/sidebarStore";
-import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
-import giftIcon from "@/assets/gift.svg";
-import { getAuthStore } from "@/store/authStore";
-import { useTranslation } from "react-i18next";
-import { proxyFetchGet, fetchPut, fetchDelete, proxyFetchDelete } from "@/api/http";
-import { toast } from "sonner";
-import EndNoticeDialog from "@/components/Dialog/EndNotice";
-import { share } from "@/lib/share";
-import { TooltipSimple } from "@/components/ui/tooltip";
-
+ fetchDelete,
+ fetchPut,
+ proxyFetchDelete,
+ proxyFetchGet,
+} from '@/api/http';
+import folderIcon from '@/assets/Folder.svg';
+import giftIcon from '@/assets/gift.svg';
+import EndNoticeDialog from '@/components/Dialog/EndNotice';
+import { Button } from '@/components/ui/button';
+import { TooltipSimple } from '@/components/ui/tooltip';
+import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
+import { share } from '@/lib/share';
+import { getAuthStore } from '@/store/authStore';
+import { useSidebarStore } from '@/store/sidebarStore';
+import {
+ ChevronDown,
+ ChevronLeft,
+ FileDown,
+ House,
+ Minus,
+ Plus,
+ Power,
+ Settings,
+ Square,
+ X,
+} from 'lucide-react';
+import { useEffect, useMemo, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useLocation, useNavigate } from 'react-router-dom';
+import { toast } from 'sonner';
+
function HeaderWin() {
- const { t } = useTranslation();
- const titlebarRef = useRef
(null);
- const controlsRef = useRef(null);
- const [platform, setPlatform] = useState("");
- const navigate = useNavigate();
- const location = useLocation();
- //Get Chatstore for the active project's task
- const { chatStore, projectStore } = useChatStoreAdapter();
- if (!chatStore) {
- return Loading...
;
- }
-
- const { toggle } = useSidebarStore();
- const { token } = getAuthStore();
- const [endDialogOpen, setEndDialogOpen] = useState(false);
- const [endProjectLoading, setEndProjectLoading] = useState(false);
- useEffect(() => {
- const p = window.electronAPI.getPlatform();
- setPlatform(p);
- }, []);
+ const { t } = useTranslation();
+ const titlebarRef = useRef(null);
+ const controlsRef = useRef(null);
+ const [platform, setPlatform] = useState('');
+ const navigate = useNavigate();
+ const location = useLocation();
+ //Get Chatstore for the active project's task
+ const { chatStore, projectStore } = useChatStoreAdapter();
+ const { toggle } = useSidebarStore();
+ const _authStore = getAuthStore();
+ const [endDialogOpen, setEndDialogOpen] = useState(false);
+ const [endProjectLoading, setEndProjectLoading] = useState(false);
+ useEffect(() => {
+ const p = window.electronAPI.getPlatform();
+ setPlatform(p);
+ }, []);
- const exportLog = async () => {
- try {
- const response = await window.electronAPI.exportLog();
+ const exportLog = async () => {
+ try {
+ const response = await window.electronAPI.exportLog();
- if (!response.success) {
- alert(t("layout.export-cancelled") + response.error);
- return;
- }
- if (response.savedPath) {
- window.location.href =
- "https://github.com/eigent-ai/eigent/issues/new/choose";
- alert(t("layout.log-saved") + response.savedPath);
- }
- } catch (e: any) {
- alert(t("layout.export-error") + e.message);
- }
- };
+ if (!response.success) {
+ alert(t('layout.export-cancelled') + response.error);
+ return;
+ }
+ if (response.savedPath) {
+ window.location.href =
+ 'https://github.com/eigent-ai/eigent/issues/new/choose';
+ alert(t('layout.log-saved') + response.savedPath);
+ }
+ } catch (e: any) {
+ alert(t('layout.export-error') + e.message);
+ }
+ };
- // create new project handler reused by plus icon and label
- const createNewProject = () => {
- //Handles refocusing id & nonduplicate internally
- projectStore.createProject("new project");
- navigate("/");
- };
+ // create new project handler reused by plus icon and label
+ const createNewProject = () => {
+ //Handles refocusing id & nonduplicate internally
+ projectStore.createProject('new project');
+ navigate('/');
+ };
- const activeTaskTitle = useMemo(() => {
- if (
- chatStore.activeTaskId &&
- chatStore.tasks[chatStore.activeTaskId as string]?.summaryTask
- ) {
- return chatStore.tasks[
- chatStore.activeTaskId as string
- ].summaryTask.split("|")[0];
- }
- return t("layout.new-project");
- }, [
- chatStore.activeTaskId,
- chatStore.tasks[chatStore.activeTaskId as string]?.summaryTask,
- ]);
+ const activeTaskTitle = useMemo(() => {
+ if (
+ chatStore?.activeTaskId &&
+ chatStore.tasks[chatStore.activeTaskId as string]?.summaryTask
+ ) {
+ return chatStore.tasks[
+ chatStore.activeTaskId as string
+ ].summaryTask.split('|')[0];
+ }
+ return t('layout.new-project');
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [
+ chatStore?.activeTaskId,
+ chatStore?.tasks[chatStore?.activeTaskId as string]?.summaryTask,
+ t,
+ ]);
- const getReferFriendsLink = async () => {
- try {
- const res: any = await proxyFetchGet("/api/user/invite_code");
- if (res?.invite_code) {
- const inviteLink = `https://www.eigent.ai/signup?invite_code=${res.invite_code}`;
- await navigator.clipboard.writeText(inviteLink);
- toast.success(t("layout.invitation-link-copied"));
- } else {
- toast.error(t("layout.failed-to-get-invite-code"));
- }
- } catch (error) {
- console.error("Failed to get referral link:", error);
- toast.error(t("layout.failed-to-get-invitation-link"));
- }
- };
+ if (!chatStore) {
+ return Loading...
;
+ }
- //TODO: Mark ChatStore details as completed
- const handleEndProject = async () => {
- const taskId = chatStore.activeTaskId;
- const projectId = projectStore.activeProjectId;
+ const getReferFriendsLink = async () => {
+ try {
+ const res: any = await proxyFetchGet('/api/user/invite_code');
+ if (res?.invite_code) {
+ const inviteLink = `https://www.eigent.ai/signup?invite_code=${res.invite_code}`;
+ await navigator.clipboard.writeText(inviteLink);
+ toast.success(t('layout.invitation-link-copied'));
+ } else {
+ toast.error(t('layout.failed-to-get-invite-code'));
+ }
+ } catch (error) {
+ console.error('Failed to get referral link:', error);
+ toast.error(t('layout.failed-to-get-invitation-link'));
+ }
+ };
- if (!taskId) {
- toast.error(t("layout.no-active-project-to-end"));
- return;
- }
+ //TODO: Mark ChatStore details as completed
+ const handleEndProject = async () => {
+ const taskId = chatStore.activeTaskId;
+ const projectId = projectStore.activeProjectId;
- const historyId = projectId ? projectStore.getHistoryId(projectId) : null;
+ if (!taskId) {
+ toast.error(t('layout.no-active-project-to-end'));
+ return;
+ }
- setEndProjectLoading(true);
- try {
- const task = chatStore.tasks[taskId];
+ const historyId = projectId ? projectStore.getHistoryId(projectId) : null;
- // Stop the task if it's running
- if (task && task.status === 'running') {
- await fetchPut(`/task/${taskId}/take-control`, {
- action: 'stop',
- });
- }
+ setEndProjectLoading(true);
+ try {
+ const task = chatStore.tasks[taskId];
- // Stop Workforce
- try {
- await fetchDelete(`/chat/${projectId}`);
- } catch (error) {
- console.log("Task may not exist on backend:", error);
- }
+ // Stop the task if it's running
+ if (task && task.status === 'running') {
+ await fetchPut(`/task/${taskId}/take-control`, {
+ action: 'stop',
+ });
+ }
- // Delete from history using historyId
- if (historyId && task.status !== "finished") {
- try {
- await proxyFetchDelete(`/api/chat/history/${historyId}`);
- // Remove from local store
- chatStore.removeTask(taskId);
- } catch (error) {
- console.log("History may not exist:", error);
- }
- } else {
- console.warn("No historyId found for project or task finished, skipping history deletion");
- }
+ // Stop Workforce
+ try {
+ await fetchDelete(`/chat/${projectId}`);
+ } catch (error) {
+ console.log('Task may not exist on backend:', error);
+ }
+ // Delete from history using historyId
+ if (historyId && task.status !== 'finished') {
+ try {
+ await proxyFetchDelete(`/api/chat/history/${historyId}`);
+ // Remove from local store
+ chatStore.removeTask(taskId);
+ } catch (error) {
+ console.log('History may not exist:', error);
+ }
+ } else {
+ console.warn(
+ 'No historyId found for project or task finished, skipping history deletion'
+ );
+ }
- // Create a completely new project instead of just a new task
- // This ensures we start fresh without any residual state
- projectStore.createProject("new project");
+ // Create a completely new project instead of just a new task
+ // This ensures we start fresh without any residual state
+ projectStore.createProject('new project');
- // Navigate to home with replace to force refresh
- navigate("/", { replace: true });
+ // Navigate to home with replace to force refresh
+ navigate('/', { replace: true });
- toast.success(t("layout.project-ended-successfully"), {
- closeButton: true,
- });
- } catch (error) {
- console.error("Failed to end project:", error);
- toast.error(t("layout.failed-to-end-project"), {
- closeButton: true,
- });
- } finally {
- setEndProjectLoading(false);
- setEndDialogOpen(false);
- }
- };
+ toast.success(t('layout.project-ended-successfully'), {
+ closeButton: true,
+ });
+ } catch (error) {
+ console.error('Failed to end project:', error);
+ toast.error(t('layout.failed-to-end-project'), {
+ closeButton: true,
+ });
+ } finally {
+ setEndProjectLoading(false);
+ setEndDialogOpen(false);
+ }
+ };
- const handleShare = async (taskId: string) => {
- share(taskId);
- };
+ const handleShare = async (taskId: string) => {
+ share(taskId);
+ };
- return (
-
- {/* left */}
- {platform !== "darwin" && (
-
- Eigent
-
- )}
+ return (
+
+ {/* left */}
+ {platform !== 'darwin' && (
+
+
+ Eigent
+
+
+ )}
- {/* center */}
-
-
-
-
-
- {location.pathname === "/history" && (
-
-
-
- )}
- {location.pathname !== "/history" && (
-
-
-
-
-
-
-
-
- )}
- {location.pathname !== "/history" && (
- <>
- {activeTaskTitle === t("layout.new-project") ? (
-
-
-
- ) : (
-
-
-
- )}
- >
- )}
-
- {/* right */}
- {location.pathname !== "/history" && (
-
- {chatStore.activeTaskId &&
- chatStore.tasks[chatStore.activeTaskId as string] &&
- (
- (chatStore.tasks[chatStore.activeTaskId as string]?.messages?.length || 0) > 0 ||
- chatStore.tasks[chatStore.activeTaskId as string]?.hasMessages ||
- chatStore.tasks[chatStore.activeTaskId as string]?.status !== 'pending'
- ) && (
-
-
-
- )}
- {chatStore.activeTaskId &&
- chatStore.tasks[chatStore.activeTaskId as string]?.status === 'finished' && (
-
-
-
- )}
- {chatStore.activeTaskId && chatStore.tasks[chatStore.activeTaskId as string] && (
-
-
-
- )}
-
-
-
-
-
-
-
- )}
- {location.pathname === "/history" && (
-
-
- )}
-
- {/* Custom window controls only for Linux (Windows and macOS use native controls) */}
- {platform !== "darwin" && platform !== "win32" && (
-
-
window.electronAPI.minimizeWindow()}
- >
-
-
-
window.electronAPI.toggleMaximizeWindow()}
- >
-
-
-
window.electronAPI.closeWindow()}
- >
-
-
-
- )}
-
-
- );
+ {/* center */}
+
+
+
+
+
+ {location.pathname === '/history' && (
+
+
+
+ )}
+ {location.pathname !== '/history' && (
+
+
+
+
+
+
+
+
+ )}
+ {location.pathname !== '/history' && (
+ <>
+ {activeTaskTitle === t('layout.new-project') ? (
+
+
+
+ ) : (
+
+
+
+ )}
+ >
+ )}
+
+ {/* right */}
+ {location.pathname !== '/history' && (
+
+ {chatStore.activeTaskId &&
+ chatStore.tasks[chatStore.activeTaskId as string] &&
+ ((chatStore.tasks[chatStore.activeTaskId as string]?.messages
+ ?.length || 0) > 0 ||
+ chatStore.tasks[chatStore.activeTaskId as string]
+ ?.hasMessages ||
+ chatStore.tasks[chatStore.activeTaskId as string]?.status !==
+ 'pending') && (
+
+
+
+ )}
+ {chatStore.activeTaskId &&
+ chatStore.tasks[chatStore.activeTaskId as string]?.status ===
+ 'finished' && (
+
+
+
+ )}
+ {chatStore.activeTaskId &&
+ chatStore.tasks[chatStore.activeTaskId as string] && (
+
+
+
+ )}
+
+
+
+
+
+
+
+ )}
+ {location.pathname === '/history' && (
+
+ )}
+
+ {/* Custom window controls only for Linux (Windows and macOS use native controls) */}
+ {platform !== 'darwin' && platform !== 'win32' && (
+
+
window.electronAPI.minimizeWindow()}
+ >
+
+
+
window.electronAPI.toggleMaximizeWindow()}
+ >
+
+
+
window.electronAPI.closeWindow()}
+ >
+
+
+
+ )}
+
+
+ );
}
export default HeaderWin;
diff --git a/src/components/WindowControls/index.css b/src/components/WindowControls/index.css
index fbec636e9..1a99e3287 100644
--- a/src/components/WindowControls/index.css
+++ b/src/components/WindowControls/index.css
@@ -12,14 +12,13 @@
cursor: pointer;
text-align: center;
line-height: 20px;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- pointer-events: auto;
- -webkit-app-region: no-drag;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ pointer-events: auto;
+ -webkit-app-region: no-drag;
}
-.control-btn:hover{
+.control-btn:hover {
background-color: #f0f0f0;
}
-
diff --git a/src/components/WindowControls/index.tsx b/src/components/WindowControls/index.tsx
index 85b70da7d..f5725998b 100644
--- a/src/components/WindowControls/index.tsx
+++ b/src/components/WindowControls/index.tsx
@@ -12,67 +12,66 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { Minus, Square, X } from "lucide-react";
-import { useRef, useEffect, useState } from "react";
-import "./index.css";
+import { Minus, Square, X } from 'lucide-react';
+import { useEffect, useRef, useState } from 'react';
+import './index.css';
export default function WindowControls() {
- const controlsRef = useRef(null);
- const [platform, setPlatform] = useState("");
+ const controlsRef = useRef(null);
+ const [platform, setPlatform] = useState('');
- useEffect(() => {
- const p = window.electronAPI.getPlatform();
- setPlatform(p);
+ useEffect(() => {
+ const p = window.electronAPI.getPlatform();
+ setPlatform(p);
- // Hide custom controls on macOS (uses native traffic lights)
- // and on Windows (now uses native frame with native controls)
- if (p === "darwin" || p === "win32") {
- if (controlsRef.current) {
- controlsRef.current.style.display = "none";
- }
- }
- }, []);
+ // Hide custom controls on macOS (uses native traffic lights)
+ // and on Windows (now uses native frame with native controls)
+ if (p === 'darwin' || p === 'win32') {
+ if (controlsRef.current) {
+ controlsRef.current.style.display = 'none';
+ }
+ }
+ }, []);
- // Don't render custom controls on macOS or Windows (both use native controls)
- if (platform === "darwin" || platform === "win32") {
- return null;
- }
+ // Don't render custom controls on macOS or Windows (both use native controls)
+ if (platform === 'darwin' || platform === 'win32') {
+ return null;
+ }
- return (
-
-
window.electronAPI.minimizeWindow()}
- >
-
-
-
window.electronAPI.toggleMaximizeWindow()}
- >
-
-
-
{
- e.stopPropagation();
- e.preventDefault();
- // Trigger window close - this will go through the before-close handler
- // which checks if tasks are running and shows confirmation if needed
- window.electronAPI.closeWindow(false);
- }}
- onMouseDown={(e) => {
- e.stopPropagation();
- }}
- >
-
-
-
- );
+ return (
+
+
window.electronAPI.minimizeWindow()}
+ >
+
+
+
window.electronAPI.toggleMaximizeWindow()}
+ >
+
+
+
{
+ e.stopPropagation();
+ e.preventDefault();
+ // Trigger window close - this will go through the before-close handler
+ // which checks if tasks are running and shows confirmation if needed
+ window.electronAPI.closeWindow(false);
+ }}
+ onMouseDown={(e) => {
+ e.stopPropagation();
+ }}
+ >
+
+
+
+ );
}
-
diff --git a/src/components/WorkFlow/MarkDown.tsx b/src/components/WorkFlow/MarkDown.tsx
index cded54758..b47a11d89 100644
--- a/src/components/WorkFlow/MarkDown.tsx
+++ b/src/components/WorkFlow/MarkDown.tsx
@@ -12,205 +12,205 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { useState, useEffect } from "react";
-import ReactMarkdown from "react-markdown";
-import remarkGfm from "remark-gfm";
-import { isHtmlDocument } from "@/lib/htmlFontStyles";
+import { isHtmlDocument } from '@/lib/htmlFontStyles';
+import { useEffect, useState } from 'react';
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
export const MarkDown = ({
- content,
- speed = 15,
- onTyping,
- enableTypewriter = true, // Whether to enable typewriter effect
- pTextSize = "text-xs",
- olPadding = "",
+ content,
+ speed = 15,
+ onTyping,
+ enableTypewriter = true, // Whether to enable typewriter effect
+ pTextSize = 'text-xs',
+ olPadding = '',
}: {
- content: string;
- speed?: number;
- onTyping?: () => void;
- enableTypewriter?: boolean;
- pTextSize?: string;
- olPadding?: string;
+ content: string;
+ speed?: number;
+ onTyping?: () => void;
+ enableTypewriter?: boolean;
+ pTextSize?: string;
+ olPadding?: string;
}) => {
- const [displayedContent, setDisplayedContent] = useState("");
+ const [displayedContent, setDisplayedContent] = useState('');
- useEffect(() => {
- if (!enableTypewriter) {
- setDisplayedContent(content);
- return;
- }
+ useEffect(() => {
+ if (!enableTypewriter) {
+ setDisplayedContent(content);
+ return;
+ }
- setDisplayedContent("");
- let index = 0;
+ setDisplayedContent('');
+ let index = 0;
- const timer = setInterval(() => {
- if (index < content.length) {
- setDisplayedContent(content.slice(0, index + 1));
- index++;
- if (onTyping) {
- onTyping();
- }
- } else {
- clearInterval(timer);
- }
- }, speed);
+ const timer = setInterval(() => {
+ if (index < content.length) {
+ setDisplayedContent(content.slice(0, index + 1));
+ index++;
+ if (onTyping) {
+ onTyping();
+ }
+ } else {
+ clearInterval(timer);
+ }
+ }, speed);
- return () => clearInterval(timer);
- }, [content, speed]);
+ return () => clearInterval(timer);
+ }, [content, speed, enableTypewriter, onTyping]);
- // process line breaks, convert \n to
tag
- const processContent = (text: string) => {
- return text.replace(/\\n/g, " \n "); // add two spaces before \n, so ReactMarkdown will recognize it as a line break
- };
+ // process line breaks, convert \n to
tag
+ const processContent = (text: string) => {
+ return text.replace(/\\n/g, ' \n '); // add two spaces before \n, so ReactMarkdown will recognize it as a line break
+ };
- // If content is a pure HTML document, render in a styled pre block
- if (isHtmlDocument(content)) {
- // Trim leading whitespace from each line for consistent alignment
- const formattedHtml = displayedContent
- .split('\n')
- .map(line => line.trimStart())
- .join('\n')
- .trim();
- return (
-
- );
- }
+ // If content is a pure HTML document, render in a styled pre block
+ if (isHtmlDocument(content)) {
+ // Trim leading whitespace from each line for consistent alignment
+ const formattedHtml = displayedContent
+ .split('\n')
+ .map((line) => line.trimStart())
+ .join('\n')
+ .trim();
+ return (
+
+ );
+ }
- return (
-
-
(
-
- {children}
-
- ),
- h2: ({ children }) => (
-
- {children}
-
- ),
- h3: ({ children }) => (
-
- {children}
-
- ),
- p: ({ children }) => (
-
- {children}
-
- ),
- ul: ({ children }) => (
-
- ),
- // ol: ({ children }) => (
- //
- // {children}
- //
- // ),
- li: ({ children }) => (
- {children}
- ),
- a: ({ children, href }) => (
-
- {children}
-
- ),
- code: ({ children }) => (
-
- {children}
-
- ),
- pre: ({ children }) => (
-
- {children}
-
- ),
- blockquote: ({ children }) => (
-
- {children}
-
- ),
- strong: ({ children }) => (
-
- {children}
-
- ),
- em: ({ children }) => (
- {children}
- ),
- table: ({ children }) => (
-
- ),
- thead: ({ children }) => (
-
- {children}
-
- ),
- tbody: ({ children }) => (
- {children}
- ),
- tr: ({ children }) => {children}
,
- th: ({ children }) => (
-
- {children}
- |
- ),
- td: ({ children }) => (
-
- {children}
- |
- ),
- }}
- >
- {processContent(displayedContent)}
-
-
- );
+ return (
+
+
(
+
+ {children}
+
+ ),
+ h2: ({ children }) => (
+
+ {children}
+
+ ),
+ h3: ({ children }) => (
+
+ {children}
+
+ ),
+ p: ({ children }) => (
+
+ {children}
+
+ ),
+ ul: ({ children }) => (
+
+ ),
+ // ol: ({ children }) => (
+ //
+ // {children}
+ //
+ // ),
+ li: ({ children }) => (
+ {children}
+ ),
+ a: ({ children, href }) => (
+
+ {children}
+
+ ),
+ code: ({ children }) => (
+
+ {children}
+
+ ),
+ pre: ({ children }) => (
+
+ {children}
+
+ ),
+ blockquote: ({ children }) => (
+
+ {children}
+
+ ),
+ strong: ({ children }) => (
+
+ {children}
+
+ ),
+ em: ({ children }) => (
+ {children}
+ ),
+ table: ({ children }) => (
+
+ ),
+ thead: ({ children }) => (
+
+ {children}
+
+ ),
+ tbody: ({ children }) => (
+ {children}
+ ),
+ tr: ({ children }) => {children}
,
+ th: ({ children }) => (
+
+ {children}
+ |
+ ),
+ td: ({ children }) => (
+
+ {children}
+ |
+ ),
+ }}
+ >
+ {processContent(displayedContent)}
+
+
+ );
};
diff --git a/src/components/WorkFlow/index.tsx b/src/components/WorkFlow/index.tsx
index 03e15fa72..499cacaad 100644
--- a/src/components/WorkFlow/index.tsx
+++ b/src/components/WorkFlow/index.tsx
@@ -12,384 +12,394 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { useEffect, useRef, useState, useCallback, useMemo } from "react";
+import { Button } from '@/components/ui/button';
import {
- PanOnScrollMode,
- ReactFlow,
- useNodesState,
- useReactFlow,
- Node as FlowNode,
- NodeTypes,
-} from "@xyflow/react";
-import { Button } from "@/components/ui/button";
-import { Node as CustomNodeComponent } from "./node";
+ Node as FlowNode,
+ NodeTypes,
+ PanOnScrollMode,
+ ReactFlow,
+ useNodesState,
+ useReactFlow,
+} from '@xyflow/react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { Node as CustomNodeComponent } from './node';
-import { SquareStack, ChevronLeft, ChevronRight, Share } from "lucide-react";
-import "@xyflow/react/dist/style.css";
-import { useWorkerList } from "@/store/authStore";
-import { share } from "@/lib/share";
-import { useTranslation } from "react-i18next";
-import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
+import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
+import { share } from '@/lib/share';
+import { useWorkerList } from '@/store/authStore';
+import '@xyflow/react/dist/style.css';
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
interface NodeData {
- agent: Agent;
- img?: ActiveWebView[];
- isExpanded?: boolean;
- onExpandChange?: (nodeId: string, isExpanded: boolean) => void;
- [key: string]: any;
+ agent: Agent;
+ img?: ActiveWebView[];
+ isExpanded?: boolean;
+ onExpandChange?: (nodeId: string, isExpanded: boolean) => void;
+ [key: string]: any;
}
type CustomNode = FlowNode;
const nodeTypes: NodeTypes = {
- node: (props: any) => ,
+ node: (props: any) => ,
};
const VIEWPORT_ANIMATION_DURATION = 500;
export default function Workflow({
- taskAssigning,
+ taskAssigning,
}: {
- taskAssigning: Agent[];
+ taskAssigning: Agent[];
}) {
- const {t} = useTranslation();
- //Get Chatstore for the active project's task
- const { chatStore } = useChatStoreAdapter();
- if (!chatStore) {
- return Loading...
;
- }
+ const { t } = useTranslation();
+ //Get Chatstore for the active project's task
+ const { chatStore } = useChatStoreAdapter();
+ const [isEditMode, _setIsEditMode] = useState(false);
+ const [_lastViewport, setLastViewport] = useState({ x: 0, y: 0, zoom: 1 });
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
+ const workerList = useWorkerList();
+ const containerRef = useRef(null);
+ const [containerWidth, setContainerWidth] = useState(0);
+ const isEditModeRef = useRef(isEditMode);
+ const { setViewport, getViewport } = useReactFlow();
+ const [isAnimating, setIsAnimating] = useState(false);
+ const totalNodesWidth = useMemo(() => {
+ if (!nodes.length) return 0;
- const [isEditMode, setIsEditMode] = useState(false);
- const [lastViewport, setLastViewport] = useState({ x: 0, y: 0, zoom: 1 });
- const [nodes, setNodes, onNodesChange] = useNodesState([]);
- const workerList = useWorkerList();
- const containerRef = useRef(null);
- const [containerWidth, setContainerWidth] = useState(0);
- const baseWorker: Agent[] = [
- {
- tasks: [],
- agent_id: "developer_agent",
- tools: [
- "Human Toolkit",
- "Terminal Toolkit",
- "Note Taking Toolkit",
- "Web Deploy Toolkit",
- ],
- name: "Developer Agent",
- type: "developer_agent",
- log: [],
- activeWebviewIds: [],
- },
- {
- tasks: [],
- agent_id: "browser_agent",
- name: "Browser Agent",
- type: "browser_agent",
- tools: [
- "Search Toolkit",
- "Browser Toolkit",
- "Human Toolkit",
- "Note Taking Toolkit",
- "Terminal Toolkit",
- ],
- log: [],
- activeWebviewIds: [],
- },
- {
- tasks: [],
- tools: [
- "Video Downloader Toolkit",
- "Audio Analysis Toolkit",
- "Image Analysis Toolkit",
- "Open AI Image Toolkit",
- "Human Toolkit",
- "Terminal Toolkit",
- "Note Taking Toolkit",
- "Search Toolkit",
- ],
- agent_id: "multi_modal_agent",
- name: "Multi Modal Agent",
- type: "multi_modal_agent",
- log: [],
- activeWebviewIds: [],
- },
- // {
- // tasks: [],
- // agent_id: "social_medium_agent",
- // name: "Social Medium Agent",
- // type: "social_medium_agent",
- // log: [],
- // activeWebviewIds: [],
- // },
- {
- tasks: [],
- agent_id: "document_agent",
- name: "Document Agent",
- tools: [
- "File Write Toolkit",
- "Pptx Toolkit",
- "Human Toolkit",
- "Mark It Down Toolkit",
- "Excel Toolkit",
- "Note Taking Toolkit",
- "Terminal Toolkit",
- "Google Drive Mcp Toolkit",
- ],
- type: "document_agent",
- log: [],
- activeWebviewIds: [],
- },
- ];
+ const widths = nodes.map((node) => (node.data.isExpanded ? 684 : 342));
+ const spacing = Math.max(nodes.length - 1, 0) * 20;
- const isEditModeRef = useRef(isEditMode);
+ return widths.reduce((sum, width) => sum + width, 0) + spacing + 16; // padding buffer
+ }, [nodes]);
- // update ref value
- useEffect(() => {
- isEditModeRef.current = isEditMode;
- }, [isEditMode]);
+ const minViewportX = useMemo(() => {
+ if (!containerWidth) return 0;
+ const contentWidth = Math.max(totalNodesWidth, containerWidth);
+ return Math.min(0, containerWidth - contentWidth);
+ }, [containerWidth, totalNodesWidth]);
- const reSetNodePosition = () => {
- if (!isEditMode) {
- // re-calculate all node x positions
- setNodes((prev: CustomNode[]) => {
- let currentX = 8; // start x position
+ const clampViewportX = useCallback(
+ (x: number) => Math.min(0, Math.max(minViewportX, x)),
+ [minViewportX]
+ );
- return prev.map((node) => {
- // calculate node width and position based on expansion state
- const nodeWidth = node.data.isExpanded ? 684 : 342;
- const newPosition = { x: currentX, y: node.position.y };
- currentX += nodeWidth + 20; // 20 is the spacing between nodes
+ const baseWorker: Agent[] = useMemo(
+ () => [
+ {
+ tasks: [],
+ agent_id: 'developer_agent',
+ tools: [
+ 'Human Toolkit',
+ 'Terminal Toolkit',
+ 'Note Taking Toolkit',
+ 'Web Deploy Toolkit',
+ ],
+ name: 'Developer Agent',
+ type: 'developer_agent',
+ log: [],
+ activeWebviewIds: [],
+ },
+ {
+ tasks: [],
+ agent_id: 'browser_agent',
+ name: 'Browser Agent',
+ type: 'browser_agent',
+ tools: [
+ 'Search Toolkit',
+ 'Browser Toolkit',
+ 'Human Toolkit',
+ 'Note Taking Toolkit',
+ 'Terminal Toolkit',
+ ],
+ log: [],
+ activeWebviewIds: [],
+ },
+ {
+ tasks: [],
+ tools: [
+ 'Video Downloader Toolkit',
+ 'Audio Analysis Toolkit',
+ 'Image Analysis Toolkit',
+ 'Open AI Image Toolkit',
+ 'Human Toolkit',
+ 'Terminal Toolkit',
+ 'Note Taking Toolkit',
+ 'Search Toolkit',
+ ],
+ agent_id: 'multi_modal_agent',
+ name: 'Multi Modal Agent',
+ type: 'multi_modal_agent',
+ log: [],
+ activeWebviewIds: [],
+ },
+ // {
+ // tasks: [],
+ // agent_id: "social_medium_agent",
+ // name: "Social Medium Agent",
+ // type: "social_medium_agent",
+ // log: [],
+ // activeWebviewIds: [],
+ // },
+ {
+ tasks: [],
+ agent_id: 'document_agent',
+ name: 'Document Agent',
+ tools: [
+ 'File Write Toolkit',
+ 'Pptx Toolkit',
+ 'Human Toolkit',
+ 'Mark It Down Toolkit',
+ 'Excel Toolkit',
+ 'Note Taking Toolkit',
+ 'Terminal Toolkit',
+ 'Google Drive Mcp Toolkit',
+ ],
+ type: 'document_agent',
+ log: [],
+ activeWebviewIds: [],
+ },
+ ],
+ []
+ );
- return {
- ...node,
- position: newPosition,
- };
- });
- });
- }
- };
+ // update ref value
+ useEffect(() => {
+ isEditModeRef.current = isEditMode;
+ }, [isEditMode]);
- // when exiting edit mode, re-calculate node positions
- useEffect(() => {
- if (!isEditMode) {
- reSetNodePosition();
- }
- }, [isEditMode, setNodes]);
+ const reSetNodePosition = useCallback(() => {
+ if (!isEditMode) {
+ // re-calculate all node x positions
+ setNodes((prev: CustomNode[]) => {
+ let currentX = 8; // start x position
- // update isEditMode state for all nodes
- useEffect(() => {
- setNodes((prev: CustomNode[]) => {
- return prev.map((node) => ({
- ...node,
- data: {
- ...node.data,
- isEditMode: isEditMode,
- },
- }));
- });
- }, [isEditMode, setNodes]);
+ return prev.map((node) => {
+ // calculate node width and position based on expansion state
+ const nodeWidth = node.data.isExpanded ? 684 : 342;
+ const newPosition = { x: currentX, y: node.position.y };
+ currentX += nodeWidth + 20; // 20 is the spacing between nodes
- const handleExpandChange = useCallback(
- (nodeId: string, isExpanded: boolean) => {
- if (isEditMode) {
- setNodes((prev: CustomNode[]) => {
- return prev.map((node) => {
- // update current node expansion state
- const updatedNode = {
- ...node,
- data: {
- ...node.data,
- isExpanded:
- node.id === nodeId ? isExpanded : node.data.isExpanded,
- isEditMode: isEditMode,
- },
- };
+ return {
+ ...node,
+ position: newPosition,
+ };
+ });
+ });
+ }
+ }, [isEditMode, setNodes]);
- return {
- ...updatedNode,
- };
- });
- });
- } else {
- // update node expansion state and re-calculate all node x positions
- setNodes((prev: CustomNode[]) => {
- let currentX = 8; // start x position
+ // when exiting edit mode, re-calculate node positions
+ useEffect(() => {
+ if (!isEditMode) {
+ reSetNodePosition();
+ }
+ }, [isEditMode, reSetNodePosition]);
- return prev.map((node) => {
- // update current node expansion state
- const updatedNode = {
- ...node,
- data: {
- ...node.data,
- isExpanded:
- node.id === nodeId ? isExpanded : node.data.isExpanded,
- isEditMode: isEditMode,
- },
- };
+ // update isEditMode state for all nodes
+ useEffect(() => {
+ setNodes((prev: CustomNode[]) => {
+ return prev.map((node) => ({
+ ...node,
+ data: {
+ ...node.data,
+ isEditMode: isEditMode,
+ },
+ }));
+ });
+ }, [isEditMode, setNodes]);
- // calculate node width and position based on expansion state
- const nodeWidth = updatedNode.data.isExpanded ? 684 : 342;
- const newPosition = { x: currentX, y: node.position.y };
- currentX += nodeWidth + 20; // 20 is the spacing between nodes
+ const handleExpandChange = useCallback(
+ (nodeId: string, isExpanded: boolean) => {
+ if (isEditMode) {
+ setNodes((prev: CustomNode[]) => {
+ return prev.map((node) => {
+ // update current node expansion state
+ const updatedNode = {
+ ...node,
+ data: {
+ ...node.data,
+ isExpanded:
+ node.id === nodeId ? isExpanded : node.data.isExpanded,
+ isEditMode: isEditMode,
+ },
+ };
- return {
- ...updatedNode,
- position: newPosition,
- };
- });
- });
- }
- },
- [setNodes, isEditMode]
- );
+ return {
+ ...updatedNode,
+ };
+ });
+ });
+ } else {
+ // update node expansion state and re-calculate all node x positions
+ setNodes((prev: CustomNode[]) => {
+ let currentX = 8; // start x position
- useEffect(() => {
- // console.log("workerList ", workerList);
- setNodes((prev: CustomNode[]) => {
- if (!taskAssigning) return prev;
- // Agents not yet in taskAssigning (from baseWorker or workerList)
- const base = [...baseWorker, ...workerList].filter(
- (worker) => !taskAssigning.find((agent) => agent.type === worker.type)
- );
- let targetData = [...prev];
- // Merge all agents
- const allAgents = [...taskAssigning, ...base];
- // Sort: agents with tasks come first, then agents without tasks
- const sortedAgents = allAgents.sort((a, b) => {
- const aHasTasks = a.tasks && a.tasks.length > 0;
- const bHasTasks = b.tasks && b.tasks.length > 0;
- if (aHasTasks && !bHasTasks) return -1;
- if (!aHasTasks && bHasTasks) return 1;
- return 0;
- });
- targetData = sortedAgents.map((agent, index) => {
- const node = targetData.find((node) => node.id === agent.agent_id);
- if (node) {
- return {
- ...node,
- data: {
- ...node.data,
- img: agent?.activeWebviewIds,
- agent: agent,
- onExpandChange: handleExpandChange,
- isEditMode: isEditMode,
- workerInfo: agent?.workerInfo,
- },
- position: isEditMode
- ? node.position
- : { x: index * (342 + 20) + 8, y: 16 },
- };
- } else {
- return {
- id: agent.agent_id,
- data: {
- type: agent.type,
- agent: agent,
- img: agent?.activeWebviewIds,
- isExpanded: false,
- onExpandChange: handleExpandChange,
- isEditMode: isEditMode,
- workerInfo: agent?.workerInfo,
- },
- position: { x: index * (342 + 20) + 8, y: 16 },
- type: "node",
- };
- }
- });
- return targetData;
- });
- if (!isEditMode) {
- reSetNodePosition();
- }
- }, [taskAssigning, isEditMode, workerList]);
+ return prev.map((node) => {
+ // update current node expansion state
+ const updatedNode = {
+ ...node,
+ data: {
+ ...node.data,
+ isExpanded:
+ node.id === nodeId ? isExpanded : node.data.isExpanded,
+ isEditMode: isEditMode,
+ },
+ };
- const { setViewport, getViewport } = useReactFlow();
- const [isAnimating, setIsAnimating] = useState(false);
- const totalNodesWidth = useMemo(() => {
- if (!nodes.length) return 0;
+ // calculate node width and position based on expansion state
+ const nodeWidth = updatedNode.data.isExpanded ? 684 : 342;
+ const newPosition = { x: currentX, y: node.position.y };
+ currentX += nodeWidth + 20; // 20 is the spacing between nodes
- const widths = nodes.map((node) => (node.data.isExpanded ? 684 : 342));
- const spacing = Math.max(nodes.length - 1, 0) * 20;
+ return {
+ ...updatedNode,
+ position: newPosition,
+ };
+ });
+ });
+ }
+ },
+ [setNodes, isEditMode]
+ );
- return widths.reduce((sum, width) => sum + width, 0) + spacing + 16; // padding buffer
- }, [nodes]);
+ useEffect(() => {
+ // console.log("workerList ", workerList);
+ setNodes((prev: CustomNode[]) => {
+ if (!taskAssigning) return prev;
+ // Agents not yet in taskAssigning (from baseWorker or workerList)
+ const base = [...baseWorker, ...workerList].filter(
+ (worker) => !taskAssigning.find((agent) => agent.type === worker.type)
+ );
+ let targetData = [...prev];
+ // Merge all agents
+ const allAgents = [...taskAssigning, ...base];
+ // Sort: agents with tasks come first, then agents without tasks
+ const sortedAgents = allAgents.sort((a, b) => {
+ const aHasTasks = a.tasks && a.tasks.length > 0;
+ const bHasTasks = b.tasks && b.tasks.length > 0;
+ if (aHasTasks && !bHasTasks) return -1;
+ if (!aHasTasks && bHasTasks) return 1;
+ return 0;
+ });
+ targetData = sortedAgents.map((agent, index) => {
+ const node = targetData.find((node) => node.id === agent.agent_id);
+ if (node) {
+ return {
+ ...node,
+ data: {
+ ...node.data,
+ img: agent?.activeWebviewIds,
+ agent: agent,
+ onExpandChange: handleExpandChange,
+ isEditMode: isEditMode,
+ workerInfo: agent?.workerInfo,
+ },
+ position: isEditMode
+ ? node.position
+ : { x: index * (342 + 20) + 8, y: 16 },
+ };
+ } else {
+ return {
+ id: agent.agent_id,
+ data: {
+ type: agent.type,
+ agent: agent,
+ img: agent?.activeWebviewIds,
+ isExpanded: false,
+ onExpandChange: handleExpandChange,
+ isEditMode: isEditMode,
+ workerInfo: agent?.workerInfo,
+ },
+ position: { x: index * (342 + 20) + 8, y: 16 },
+ type: 'node',
+ };
+ }
+ });
+ return targetData;
+ });
+ if (!isEditMode) {
+ reSetNodePosition();
+ }
+ }, [
+ taskAssigning,
+ isEditMode,
+ workerList,
+ baseWorker,
+ handleExpandChange,
+ reSetNodePosition,
+ setNodes,
+ ]);
- const minViewportX = useMemo(() => {
- if (!containerWidth) return 0;
- const contentWidth = Math.max(totalNodesWidth, containerWidth);
- return Math.min(0, containerWidth - contentWidth);
- }, [containerWidth, totalNodesWidth]);
+ useEffect(() => {
+ const updateWidth = () => {
+ if (containerRef.current) {
+ setContainerWidth(containerRef.current.clientWidth);
+ }
+ };
- const clampViewportX = useCallback(
- (x: number) => Math.min(0, Math.max(minViewportX, x)),
- [minViewportX]
- );
+ updateWidth();
+ window.addEventListener('resize', updateWidth);
- useEffect(() => {
- const updateWidth = () => {
- if (containerRef.current) {
- setContainerWidth(containerRef.current.clientWidth);
- }
- };
+ return () => {
+ window.removeEventListener('resize', updateWidth);
+ };
+ }, []);
- updateWidth();
- window.addEventListener("resize", updateWidth);
+ const moveViewport = (dx: number) => {
+ if (isAnimating) return;
+ const viewport = getViewport();
+ setIsAnimating(true);
+ const newX = clampViewportX(viewport.x + dx);
+ setViewport(
+ { x: newX, y: viewport.y, zoom: viewport.zoom },
+ {
+ duration: VIEWPORT_ANIMATION_DURATION,
+ }
+ );
+ setTimeout(() => {
+ setIsAnimating(false);
+ }, VIEWPORT_ANIMATION_DURATION);
+ };
- return () => {
- window.removeEventListener("resize", updateWidth);
- };
- }, []);
+ const _handleShare = async (taskId: string) => {
+ share(taskId);
+ };
- const moveViewport = (dx: number) => {
- if (isAnimating) return;
- const viewport = getViewport();
- setIsAnimating(true);
- const newX = clampViewportX(viewport.x + dx);
- setViewport(
- { x: newX, y: viewport.y, zoom: viewport.zoom },
- {
- duration: VIEWPORT_ANIMATION_DURATION,
- }
- );
- setTimeout(() => {
- setIsAnimating(false);
- }, VIEWPORT_ANIMATION_DURATION);
- };
+ useEffect(() => {
+ const container: HTMLElement | null =
+ document.querySelector('.react-flow__pane');
+ if (!container) return;
- const handleShare = async (taskId: string) => {
- share(taskId);
- };
+ const onWheel = (e: WheelEvent) => {
+ if (e.deltaY !== 0 && !isEditMode) {
+ e.preventDefault();
- useEffect(() => {
- const container: HTMLElement | null =
- document.querySelector(".react-flow__pane");
- if (!container) return;
+ const { x, y, zoom } = getViewport();
+ const nextX = clampViewportX(x - e.deltaY);
+ setViewport({ x: nextX, y, zoom }, { duration: 0 });
+ }
+ };
- const onWheel = (e: WheelEvent) => {
- if (e.deltaY !== 0 && !isEditMode) {
- e.preventDefault();
+ container.addEventListener('wheel', onWheel, { passive: false });
- const { x, y, zoom } = getViewport();
- const nextX = clampViewportX(x - e.deltaY);
- setViewport({ x: nextX, y, zoom }, { duration: 0 });
- }
- };
+ return () => {
+ container.removeEventListener('wheel', onWheel);
+ };
+ }, [getViewport, setViewport, isEditMode, clampViewportX]);
- container.addEventListener("wheel", onWheel, { passive: false });
+ if (!chatStore) {
+ return Loading...
;
+ }
- return () => {
- container.removeEventListener("wheel", onWheel);
- };
- }, [getViewport, setViewport, isEditMode, clampViewportX]);
-
- return (
-
-
-
- {t("workforce.your-ai-workforce")}
-
-
- {/*
- );
-}
\ No newline at end of file
+
+ {
+ moveViewport(200);
+ }}
+ >
+
+
+ moveViewport(-200)}
+ >
+
+
+
+
+
+
+ {
+ const clampedX = clampViewportX(viewport.x);
+ if (clampedX !== viewport.x) {
+ setViewport({ ...viewport, x: clampedX });
+ return;
+ }
+ if (isEditMode) {
+ setLastViewport(viewport);
+ }
+ }}
+ >
+ {/* */}
+
+
+
+ );
+}
diff --git a/src/components/WorkFlow/node.tsx b/src/components/WorkFlow/node.tsx
index c9fec3aa8..b7f90e6d5 100644
--- a/src/components/WorkFlow/node.tsx
+++ b/src/components/WorkFlow/node.tsx
@@ -12,926 +12,942 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { Handle, Position, useReactFlow, NodeResizer } from "@xyflow/react";
-import { useEffect, useRef, useState, useCallback } from "react";
+import { AddWorker } from '@/components/AddWorker';
+import { Button } from '@/components/ui/button';
+import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
+import { useAuthStore, useWorkerList } from '@/store/authStore';
+import { TooltipContent } from '@radix-ui/react-tooltip';
+import { Handle, NodeResizer, Position, useReactFlow } from '@xyflow/react';
import {
- FileText,
- Globe,
- Bird,
- CodeXml,
- Image,
- Bot,
- SquareCode,
- Ellipsis,
- LoaderCircle,
- CircleCheckBig,
- Circle,
- CircleSlash,
- TriangleAlert,
- Trash2,
- SquareChevronLeft,
- CircleSlash2,
-} from "lucide-react";
-import { Button } from "@/components/ui/button";
-import Folder from "../Folder";
-import Terminal from "../Terminal";
-import { useAuthStore, useWorkerList } from "@/store/authStore";
-import ShinyText from "../ui/ShinyText/ShinyText";
-import { MarkDown } from "./MarkDown";
-import { Tooltip, TooltipTrigger } from "../ui/tooltip";
-import { TooltipContent } from "@radix-ui/react-tooltip";
-import { TaskState, TaskStateType } from "../TaskState";
+ Bird,
+ Bot,
+ Circle,
+ CircleCheckBig,
+ CircleSlash,
+ CircleSlash2,
+ CodeXml,
+ Ellipsis,
+ FileText,
+ Globe,
+ Image,
+ LoaderCircle,
+ SquareChevronLeft,
+ SquareCode,
+ Trash2,
+ TriangleAlert,
+} from 'lucide-react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import Folder from '../Folder';
+import { TaskState, TaskStateType } from '../TaskState';
+import Terminal from '../Terminal';
import {
- Popover,
- PopoverClose,
- PopoverContent,
- PopoverTrigger,
-} from "../ui/popover";
-import { AddWorker } from "@/components/AddWorker";
-import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
+ Popover,
+ PopoverClose,
+ PopoverContent,
+ PopoverTrigger,
+} from '../ui/popover';
+import ShinyText from '../ui/ShinyText/ShinyText';
+import { Tooltip, TooltipTrigger } from '../ui/tooltip';
+import { MarkDown } from './MarkDown';
interface NodeProps {
- id: string;
- data: {
- img: ActiveWebView[];
- agent?: Agent;
- type: AgentNameType;
- isExpanded: boolean;
- onExpandChange: (nodeId: string, isExpanded: boolean) => void;
- isEditMode: boolean;
- workerInfo: {
- name: string;
- description: string;
- tools: any;
- mcp_tools: any;
- selectedTools: any;
- };
- };
+ id: string;
+ data: {
+ img: ActiveWebView[];
+ agent?: Agent;
+ type: AgentNameType;
+ isExpanded: boolean;
+ onExpandChange: (nodeId: string, isExpanded: boolean) => void;
+ isEditMode: boolean;
+ workerInfo: {
+ name: string;
+ description: string;
+ tools: any;
+ mcp_tools: any;
+ selectedTools: any;
+ };
+ };
}
export function Node({ id, data }: NodeProps) {
- const [isExpanded, setIsExpanded] = useState(data.isExpanded);
- const [selectedTask, setSelectedTask] = useState
(null);
- const [selectedState, setSelectedState] = useState("all");
+ const [isExpanded, setIsExpanded] = useState(data.isExpanded);
+ const [selectedTask, setSelectedTask] = useState(null);
+ const [selectedState, setSelectedState] = useState('all');
- const [filterTasks, setFilterTasks] = useState([]);
- useEffect(() => {
- const tasks = data.agent?.tasks || [];
+ const filterTasks = useMemo(() => {
+ const tasks = data.agent?.tasks || [];
- if (selectedState === "all") {
- setFilterTasks(tasks);
- } else {
- const newFiltered = tasks.filter((task) => {
- switch (selectedState) {
- case "done":
- return task.status === "completed" && !task.reAssignTo;
- case "reassigned":
- return !!task.reAssignTo;
- case "ongoing":
- return (
- task.status !== "failed" &&
- task.status !== "completed" &&
- task.status !== "skipped" &&
- task.status !== "waiting" &&
- task.status !== "" &&
- !task.reAssignTo
- );
- case "pending":
- return (
- (task.status === "skipped" ||
- task.status === "waiting" ||
- task.status === "") &&
- !task.reAssignTo
- );
- case "failed":
- return task.status === "failed";
- default:
- return false;
- }
- });
- setFilterTasks(newFiltered);
- }
- }, [selectedState, data.agent?.tasks]);
+ if (selectedState === 'all') {
+ return tasks;
+ } else {
+ return tasks.filter((task) => {
+ switch (selectedState) {
+ case 'done':
+ return task.status === 'completed' && !task.reAssignTo;
+ case 'reassigned':
+ return !!task.reAssignTo;
+ case 'ongoing':
+ return (
+ task.status !== 'failed' &&
+ task.status !== 'completed' &&
+ task.status !== 'skipped' &&
+ task.status !== 'waiting' &&
+ task.status !== '' &&
+ !task.reAssignTo
+ );
+ case 'pending':
+ return (
+ (task.status === 'skipped' ||
+ task.status === 'waiting' ||
+ task.status === '') &&
+ !task.reAssignTo
+ );
+ case 'failed':
+ return task.status === 'failed';
+ default:
+ return false;
+ }
+ });
+ }
+ }, [selectedState, data.agent?.tasks]);
- //Get Chatstore for the active project's task
- const { chatStore } = useChatStoreAdapter();
- if (!chatStore) {
- return Loading...
;
- }
+ //Get Chatstore for the active project's task
+ const { chatStore } = useChatStoreAdapter();
+ const { getNode, setViewport, setNodes } = useReactFlow();
+ const workerList = useWorkerList();
+ const { setWorkerList } = useAuthStore();
+ const nodeRef = useRef(null);
+ const lastAutoExpandedTaskIdRef = useRef(null);
+ const wrapperRef = useRef(null);
+ const toolsRef = useRef(null);
+ const logRef = useRef(null);
+ const rePortRef = useRef(null);
+ const [shouldScroll, setShouldScroll] = useState(false);
+ const [toolsHeight, setToolsHeight] = useState(0);
- const { setCenter, getNode, setViewport, setNodes } = useReactFlow();
- const workerList = useWorkerList();
- const { setWorkerList } = useAuthStore();
- const nodeRef = useRef(null);
- const lastAutoExpandedTaskIdRef = useRef(null);
+ // Sync isExpanded from props, but allow local modifications
+ // Use a ref to track the last synced value to avoid unnecessary updates
+ const lastSyncedIsExpandedRef = useRef(data.isExpanded);
- useEffect(() => {
- setIsExpanded(data.isExpanded);
- }, [data.isExpanded]);
+ useEffect(() => {
+ // Only sync if the prop actually changed and differs from current state
+ if (
+ data.isExpanded !== lastSyncedIsExpandedRef.current &&
+ data.isExpanded !== isExpanded
+ ) {
+ lastSyncedIsExpandedRef.current = data.isExpanded;
+ // Use setTimeout to defer the state update and avoid cascading renders
+ setTimeout(() => {
+ setIsExpanded(data.isExpanded);
+ }, 0);
+ }
+ }, [data.isExpanded, isExpanded]);
- // Auto-expand when a task is running with toolkits
- useEffect(() => {
- const tasks = data.agent?.tasks || [];
+ // Auto-expand when a task is running with toolkits
+ const tasks = useMemo(() => data.agent?.tasks || [], [data.agent?.tasks]);
+ const runningTask = tasks.find((t) => t.status === 'running');
+ const runningTaskId = runningTask?.id;
+ const runningTaskToolkitsLength = runningTask?.toolkits?.length;
- // Find running task with active toolkits
- const runningTaskWithToolkits = tasks.find(
- (task) =>
- task.status === "running" &&
- task.toolkits &&
- task.toolkits.length > 0
- );
+ useEffect(() => {
+ // Find running task with active toolkits
+ const runningTaskWithToolkits = tasks.find(
+ (task) =>
+ task.status === 'running' && task.toolkits && task.toolkits.length > 0
+ );
- // Reset tracking when no tasks are running
- const hasRunningTasks = tasks.some((task) => task.status === "running");
- if (!hasRunningTasks && lastAutoExpandedTaskIdRef.current) {
- lastAutoExpandedTaskIdRef.current = null;
- }
+ // Reset tracking when no tasks are running
+ const hasRunningTasks = tasks.some((task) => task.status === 'running');
+ if (!hasRunningTasks && lastAutoExpandedTaskIdRef.current) {
+ lastAutoExpandedTaskIdRef.current = null;
+ }
- // Auto-expand for new running task
- if (runningTaskWithToolkits && runningTaskWithToolkits.id !== lastAutoExpandedTaskIdRef.current) {
- // Always select the new task
- setSelectedTask(runningTaskWithToolkits);
+ // Auto-expand for new running task
+ if (
+ runningTaskWithToolkits &&
+ runningTaskWithToolkits.id !== lastAutoExpandedTaskIdRef.current
+ ) {
+ // Use setTimeout to defer state updates and avoid cascading renders
+ setTimeout(() => {
+ // Always select the new task
+ setSelectedTask(runningTaskWithToolkits);
- // Expand if not already expanded
- if (!isExpanded) {
- setIsExpanded(true);
- data.onExpandChange(id, true);
- }
+ // Expand if not already expanded
+ if (!isExpanded) {
+ setIsExpanded(true);
+ data.onExpandChange(id, true);
+ }
+ }, 0);
- lastAutoExpandedTaskIdRef.current = runningTaskWithToolkits.id;
- }
- }, [
- data.agent?.tasks,
- // Add specific dependencies that actually change
- data.agent?.tasks?.length,
- data.agent?.tasks?.find((t) => t.status === "running")?.id,
- data.agent?.tasks?.find((t) => t.status === "running")?.toolkits?.length,
- id,
- data.onExpandChange,
- isExpanded,
- ]);
+ lastAutoExpandedTaskIdRef.current = runningTaskWithToolkits.id;
+ }
+ }, [
+ tasks,
+ tasks.length,
+ runningTaskId,
+ runningTaskToolkitsLength,
+ id,
+ data.onExpandChange,
+ isExpanded,
+ data,
+ ]);
- // manually control node size
- useEffect(() => {
- if (data.isEditMode) {
- const targetWidth = isExpanded ? 684 : 342;
- const targetHeight = 600;
+ // manually control node size
+ useEffect(() => {
+ if (data.isEditMode) {
+ const targetWidth = isExpanded ? 684 : 342;
+ const targetHeight = 600;
- setNodes((nodes) =>
- nodes.map((node) => {
- if (node.id === id) {
- return {
- ...node,
- style: {
- ...node.style,
- width: targetWidth,
- height: targetHeight,
- },
- };
- }
- return node;
- })
- );
- }
- }, [isExpanded, data.isEditMode, id, setNodes]);
+ setNodes((nodes) =>
+ nodes.map((node) => {
+ if (node.id === id) {
+ return {
+ ...node,
+ style: {
+ ...node.style,
+ width: targetWidth,
+ height: targetHeight,
+ },
+ };
+ }
+ return node;
+ })
+ );
+ }
+ }, [isExpanded, data.isEditMode, id, setNodes]);
- const handleShowLog = () => {
- if (!isExpanded) {
- setSelectedTask(
- data.agent?.tasks.find((task) => task.status === "running") ||
- data.agent?.tasks[0]
- );
- }
- setIsExpanded(!isExpanded);
- data.onExpandChange(id, !isExpanded);
- };
+ const activeTaskId = chatStore?.activeTaskId as string;
+ const activeAgent = chatStore?.tasks[activeTaskId]?.activeAgent;
- useEffect(() => {
- if (chatStore.tasks[chatStore.activeTaskId as string]?.activeAgent === id) {
- const node = getNode(id);
- if (node) {
- setTimeout(() => {
- setViewport(
- { x: -node.position.x, y: 0, zoom: 1 },
- {
- duration: 500,
- }
- );
- }, 100);
- }
- }
- }, [
- chatStore.tasks[chatStore.activeTaskId as string]?.activeAgent,
- id,
- setCenter,
- getNode,
- ]);
+ useEffect(() => {
+ if (!chatStore) return;
+ if (activeAgent === id) {
+ const node = getNode(id);
+ if (node) {
+ setTimeout(() => {
+ setViewport(
+ { x: -node.position.x, y: 0, zoom: 1 },
+ {
+ duration: 500,
+ }
+ );
+ }, 100);
+ }
+ }
+ }, [chatStore, activeTaskId, activeAgent, id, getNode, setViewport]);
- const wrapperRef = useRef(null);
- const toolsRef = useRef(null);
- const [shouldScroll, setShouldScroll] = useState(false);
- const [toolsHeight, setToolsHeight] = useState(0);
+ useEffect(() => {
+ if (wrapperRef.current) {
+ const { scrollHeight, clientHeight } = wrapperRef.current;
+ setShouldScroll(scrollHeight > clientHeight);
+ }
+ }, [data.agent?.tasks, toolsHeight]);
- useEffect(() => {
- if (wrapperRef.current) {
- const { scrollHeight, clientHeight } = wrapperRef.current;
- setShouldScroll(scrollHeight > clientHeight);
- }
- }, [data.agent?.tasks, toolsHeight]);
+ // dynamically calculate tool label height
+ useEffect(() => {
+ if (toolsRef.current) {
+ const height = toolsRef.current.offsetHeight;
+ setToolsHeight(height);
+ }
+ }, [data.agent?.tools]);
- // dynamically calculate tool label height
- useEffect(() => {
- if (toolsRef.current) {
- const height = toolsRef.current.offsetHeight;
- setToolsHeight(height);
- }
- }, [data.agent?.tools]);
+ const wheelHandler = useCallback((e: WheelEvent) => {
+ e.stopPropagation();
+ }, []);
- const logRef = useRef(null);
- const rePortRef = useRef(null);
+ useEffect(() => {
+ const wrapper = wrapperRef.current;
+ const log = logRef.current;
- const wheelHandler = useCallback((e: WheelEvent) => {
- e.stopPropagation();
- }, []);
+ if (wrapper) {
+ wrapper.addEventListener('wheel', wheelHandler, { passive: false });
+ }
- useEffect(() => {
- const wrapper = wrapperRef.current;
- const log = logRef.current;
+ if (log) {
+ log.addEventListener('wheel', wheelHandler, { passive: false });
+ }
- if (wrapper) {
- wrapper.addEventListener("wheel", wheelHandler, { passive: false });
- }
+ return () => {
+ if (wrapper) {
+ wrapper.removeEventListener('wheel', wheelHandler);
+ }
+ if (log) {
+ log.removeEventListener('wheel', wheelHandler);
+ }
+ };
+ }, [
+ wheelHandler,
+ isExpanded,
+ selectedTask,
+ selectedTask?.report?.rePort?.content,
+ ]);
- if (log) {
- log.addEventListener("wheel", wheelHandler, { passive: false });
- }
+ if (!chatStore) {
+ return Loading...
;
+ }
- return () => {
- if (wrapper) {
- wrapper.removeEventListener("wheel", wheelHandler);
- }
- if (log) {
- log.removeEventListener("wheel", wheelHandler);
- }
- };
- }, [
- wheelHandler,
- isExpanded,
- selectedTask,
- selectedTask?.report?.rePort?.content,
- ]);
+ const handleShowLog = () => {
+ if (!isExpanded) {
+ setSelectedTask(
+ data.agent?.tasks.find((task) => task.status === 'running') ||
+ data.agent?.tasks[0]
+ );
+ }
+ setIsExpanded(!isExpanded);
+ data.onExpandChange(id, !isExpanded);
+ };
- const agentMap = {
- developer_agent: {
- name: "Developer Agent",
- icon: ,
- textColor: "text-text-developer",
- bgColor: "bg-bg-fill-coding-active",
- shapeColor: "bg-bg-fill-coding-default",
- borderColor: "border-bg-fill-coding-active",
- bgColorLight: "bg-emerald-200",
- },
- browser_agent: {
- name: "Browser Agent",
- icon: ,
- textColor: "text-blue-700",
- bgColor: "bg-bg-fill-browser-active",
- shapeColor: "bg-bg-fill-browser-default",
- borderColor: "border-bg-fill-browser-active",
- bgColorLight: "bg-blue-200",
- },
- document_agent: {
- name: "Document Agent",
- icon: ,
- textColor: "text-yellow-700",
- bgColor: "bg-bg-fill-writing-active",
- shapeColor: "bg-bg-fill-writing-default",
- borderColor: "border-bg-fill-writing-active",
- bgColorLight: "bg-yellow-200",
- },
- multi_modal_agent: {
- name: "Multi Modal Agent",
- icon: ,
- textColor: "text-fuchsia-700",
- bgColor: "bg-bg-fill-multimodal-active",
- shapeColor: "bg-bg-fill-multimodal-default",
- borderColor: "border-bg-fill-multimodal-active",
- bgColorLight: "bg-fuchsia-200",
- },
- social_medium_agent: {
- name: "Social Media Agent",
- icon: ,
- textColor: "text-purple-700",
- bgColor: "bg-violet-700",
- shapeColor: "bg-violet-300",
- borderColor: "border-violet-700",
- bgColorLight: "bg-purple-50",
- },
- };
+ const agentMap = {
+ developer_agent: {
+ name: 'Developer Agent',
+ icon: ,
+ textColor: 'text-text-developer',
+ bgColor: 'bg-bg-fill-coding-active',
+ shapeColor: 'bg-bg-fill-coding-default',
+ borderColor: 'border-bg-fill-coding-active',
+ bgColorLight: 'bg-emerald-200',
+ },
+ browser_agent: {
+ name: 'Browser Agent',
+ icon: ,
+ textColor: 'text-blue-700',
+ bgColor: 'bg-bg-fill-browser-active',
+ shapeColor: 'bg-bg-fill-browser-default',
+ borderColor: 'border-bg-fill-browser-active',
+ bgColorLight: 'bg-blue-200',
+ },
+ document_agent: {
+ name: 'Document Agent',
+ icon: ,
+ textColor: 'text-yellow-700',
+ bgColor: 'bg-bg-fill-writing-active',
+ shapeColor: 'bg-bg-fill-writing-default',
+ borderColor: 'border-bg-fill-writing-active',
+ bgColorLight: 'bg-yellow-200',
+ },
+ multi_modal_agent: {
+ name: 'Multi Modal Agent',
+ icon: ,
+ textColor: 'text-fuchsia-700',
+ bgColor: 'bg-bg-fill-multimodal-active',
+ shapeColor: 'bg-bg-fill-multimodal-default',
+ borderColor: 'border-bg-fill-multimodal-active',
+ bgColorLight: 'bg-fuchsia-200',
+ },
+ social_medium_agent: {
+ name: 'Social Media Agent',
+ icon: ,
+ textColor: 'text-purple-700',
+ bgColor: 'bg-violet-700',
+ shapeColor: 'bg-violet-300',
+ borderColor: 'border-violet-700',
+ bgColorLight: 'bg-purple-50',
+ },
+ };
- const agentToolkits = {
- developer_agent: [
- "# Terminal & Shell ",
- "# Web Deployment ",
- "# Screen Capture ",
- ],
- browser_agent: ["# Web Browser ", "# Search Engines "],
- multi_modal_agent: [
- "# Image Analysis ",
- "# Video Processing ",
- "# Audio Processing ",
- "# Image Generation ",
- ],
- document_agent: [
- "# File Management ",
- "# Data Processing ",
- "# Document Creation ",
- ],
- };
+ const agentToolkits = {
+ developer_agent: [
+ '# Terminal & Shell ',
+ '# Web Deployment ',
+ '# Screen Capture ',
+ ],
+ browser_agent: ['# Web Browser ', '# Search Engines '],
+ multi_modal_agent: [
+ '# Image Analysis ',
+ '# Video Processing ',
+ '# Audio Processing ',
+ '# Image Generation ',
+ ],
+ document_agent: [
+ '# File Management ',
+ '# Data Processing ',
+ '# Document Creation ',
+ ],
+ };
- const getTaskId = (taskId: string) => {
- const list = taskId.split(".");
- let idStr = "";
- list.shift();
- list.map((i: string, index: number) => {
- idStr += Number(i) + (index === list.length - 1 ? "" : ".");
- });
- return idStr;
- };
- return (
- <>
-
-
-
-
-
-
-
- {agentMap[data.type]?.name || data.agent?.name}
-
-
-
-
- {isExpanded ? : }
-
- {!Object.keys(agentMap).find((key) => key === data.type) &&
- chatStore.tasks[chatStore.activeTaskId as string].messages
- .length === 0 && (
-
-
- e.stopPropagation()}
- variant="ghost"
- size="icon"
- >
-
-
-
-
-
-
-
-
-
- {
- e.stopPropagation();
- const newWorkerList = workerList.filter(
- (worker) => worker.type !== data.workerInfo.name
- );
- setWorkerList(newWorkerList);
- }}
- >
-
- Delete
-
-
-
-
-
- )}
-
-
-
- {/* {JSON.stringify(data.agent)} */}
- {agentToolkits[
- data.agent?.type as keyof typeof agentToolkits
- ]?.join(" ") ||
- data.agent?.tools
- ?.map((tool) => (tool ? "# " + tool.replace(/_/g, " ") : ""))
- .filter(Boolean)
- .join(" ") ||
- "No Toolkits"}
-
-
{
- chatStore.setActiveWorkSpace(
- chatStore.activeTaskId as string,
- data.agent?.agent_id as string
- );
+ const getTaskId = (taskId: string) => {
+ const list = taskId.split('.');
+ let idStr = '';
+ list.shift();
+ list.map((i: string, index: number) => {
+ idStr += Number(i) + (index === list.length - 1 ? '' : '.');
+ });
+ return idStr;
+ };
+ return (
+ <>
+
+
+
+
+
+
+
+ {agentMap[data.type]?.name || data.agent?.name}
+
+
+
+
+ {isExpanded ? : }
+
+ {!Object.keys(agentMap).find((key) => key === data.type) &&
+ chatStore.tasks[chatStore.activeTaskId as string].messages
+ .length === 0 && (
+
+
+ e.stopPropagation()}
+ variant="ghost"
+ size="icon"
+ >
+
+
+
+
+
+
+
+
+
+ {
+ e.stopPropagation();
+ const newWorkerList = workerList.filter(
+ (worker) => worker.type !== data.workerInfo.name
+ );
+ setWorkerList(newWorkerList);
+ }}
+ >
+
+ Delete
+
+
+
+
+
+ )}
+
+
+
+ {/* {JSON.stringify(data.agent)} */}
+ {agentToolkits[
+ data.agent?.type as keyof typeof agentToolkits
+ ]?.join(' ') ||
+ data.agent?.tools
+ ?.map((tool) => (tool ? '# ' + tool.replace(/_/g, ' ') : ''))
+ .filter(Boolean)
+ .join(' ') ||
+ 'No Toolkits'}
+
+
{
+ chatStore.setActiveWorkSpace(
+ chatStore.activeTaskId as string,
+ data.agent?.agent_id as string
+ );
- window.electronAPI.hideAllWebview();
- }}
- >
- {/* {data.img.length} */}
- {data.img && data.img.filter((img) => img?.img).length > 0 && (
-
- {data.img
- .filter((img) => img?.img)
- .slice(0, 4)
- .map(
- (img, index) =>
- img.img && (
-

- )
- )}
-
- )}
- {data.type === "document_agent" &&
- data?.agent?.tasks &&
- data.agent.tasks.length > 0 && (
-
- )}
+ window.electronAPI.hideAllWebview();
+ }}
+ >
+ {/* {data.img.length} */}
+ {data.img && data.img.filter((img) => img?.img).length > 0 && (
+
+ {data.img
+ .filter((img) => img?.img)
+ .slice(0, 4)
+ .map(
+ (img, index) =>
+ img.img && (
+

+ )
+ )}
+
+ )}
+ {data.type === 'document_agent' &&
+ data?.agent?.tasks &&
+ data.agent.tasks.length > 0 && (
+
+ )}
- {data.type === "developer_agent" &&
- data?.agent?.tasks &&
- data?.agent?.tasks?.filter(
- (task) => task.terminal && task.terminal.length > 0
- )?.length > 0 && (
-
- {data.agent?.tasks
- .filter((task) => task.terminal && task.terminal.length > 0)
- .slice(0, 4)
- .map((task) => {
- return (
-
- task.terminal && task.terminal.length > 0
- ).length === 1
- ? "min-w-full h-full"
- : "min-w-[calc(50%-8px)] h-[calc(50%-8px)]"
- } flex-1 rounded-sm object-cover relative overflow-hidden`}
- >
-
-
-
-
- );
- })}
-
- )}
-
- {data.agent?.tasks && data.agent?.tasks.length > 0 && (
-
- {/*
Subtasks
*/}
-
- task.status === "completed" && !task.reAssignTo
- ).length || 0
- }
- reAssignTo={
- data.agent.tasks?.filter((task) => task.reAssignTo)
- ?.length || 0
- }
- progress={
- data.agent?.tasks?.filter(
- (task) =>
- task.status !== "failed" &&
- task.status !== "completed" &&
- task.status !== "skipped" &&
- task.status !== "waiting" &&
- task.status !== "" &&
- !task.reAssignTo
- ).length || 0
- }
- skipped={
- data.agent?.tasks?.filter(
- (task) =>
- (task.status === "skipped" ||
- task.status === "waiting" ||
- task.status === "") &&
- !task.reAssignTo
- ).length || 0
- }
- failed={
- data.agent?.tasks?.filter(
- (task) => task.status === "failed"
- ).length || 0
- }
- selectedState={selectedState}
- onStateChange={setSelectedState}
- clickable={true}
- />
-
-
- )}
-
{
- e.stopPropagation();
- }}
- className={`mt-sm flex flex-col gap-2 animate-in fade-in-0 slide-in-from-bottom-4 duration-500 ease-out overflow-y-auto scrollbar pr-3 ${
- shouldScroll && "!overflow-y-scroll scrollbar "
- }`}
- style={{
- maxHeight:
- data.img && data.img.length > 0
- ? `calc(100vh - 200px - 180px - 60px - ${toolsHeight}px)`
- : `calc(100vh - 200px - 60px - ${toolsHeight}px)`,
- }}
- >
- {data.agent?.tasks &&
- filterTasks.map((task, index) => {
- return (
-
{
- setSelectedTask(task);
- setIsExpanded(true);
- data.onExpandChange(id, true);
- if (task.agent) {
- chatStore.setActiveWorkSpace(
- chatStore.activeTaskId as string,
- "workflow"
- );
- chatStore.setActiveAgent(
- chatStore.activeTaskId as string,
- task.agent?.agent_id
- );
- window.electronAPI.hideAllWebview();
- }
- }}
- key={`taskList-${task.id}-${task.failure_count}`}
- className={`rounded-lg flex gap-2 py-sm px-sm transition-all duration-300 ease-in-out animate-in fade-in-0 slide-in-from-left-2 ${
- task.reAssignTo
- ? "bg-task-fill-warning"
- : task.status === "completed"
- ? "bg-green-50"
- : task.status === "failed"
- ? "bg-task-fill-error"
- : task.status === "running"
- ? "bg-zinc-50"
- : task.status === "blocked"
- ? "bg-task-fill-warning"
- : "bg-zinc-50"
- } border border-solid border-transparent cursor-pointer ${
- task.status === "completed"
- ? "hover:border-bg-fill-success-primary"
- : task.status === "failed"
- ? "hover:border-task-border-focus-error"
- : task.status === "running"
- ? "hover:border-border-primary"
- : task.status === "blocked"
- ? "hover:border-task-border-focus-warning"
- : "border-transparent"
- } ${
- selectedTask?.id === task.id
- ? task.status === "completed"
- ? "!border-bg-fill-success-primary"
- : task.status === "failed"
- ? "!border-text-cuation-primary"
- : task.status === "running"
- ? "!border-border-primary"
- : task.status === "blocked"
- ? "!border-text-warning-primary"
- : "border-transparent"
- : "border-transparent"
- }`}
- >
-
- {task.reAssignTo ? (
- // reassign to other agent
-
- ) : (
- // normal task
- <>
- {task.status === "running" && (
-
- )}
- {task.status === "skipped" && (
-
- )}
- {task.status === "completed" && (
-
- )}
- {task.status === "failed" && (
-
- )}
- {task.status === "blocked" && (
-
- )}
- {(task.status === "" ||
- task.status === "waiting") && (
-
- )}
- >
- )}
-
-
-
-
-
- No. {getTaskId(task.id)}
-
- {task.reAssignTo ? (
-
- Reassigned to {task.reAssignTo}
-
- ) : (
- (task.failure_count ?? 0) > 0 && (
-
- Attempt {task.failure_count}
-
- )
- )}
-
-
{task.content}
-
- {task?.status === "running" && (
-
- {/* active toolkit */}
- {task.toolkits &&
- task.toolkits.length > 0 &&
- task.toolkits
- .filter(
- (tool: any) => tool.toolkitName !== "notice"
- )
- .at(-1)?.toolkitStatus === "running" && (
-
- {agentMap[data.type]?.icon ?? (
-
- )}
-
-
-
-
- )}
-
- )}
-
-
- );
- })}
-
-
- {isExpanded && (
-
-
{
- e.stopPropagation();
- }}
- className=" flex flex-col gap-sm my-2 scrollbar max-h-[calc(100vh-200px)] scrollbar-gutter-stable overflow-y-auto pr-sm"
- >
- {selectedTask &&
- selectedTask.toolkits &&
- selectedTask.toolkits.length > 0 &&
- selectedTask.toolkits.map((toolkit: any, index: number) => (
-
- {toolkit.toolkitName === "notice" ? (
-
-
-
- ) : (
-
-
- {
- e.stopPropagation();
- if (toolkit.toolkitMethods === "write to file") {
- chatStore.tasks[
- chatStore.activeTaskId as string
- ].activeWorkSpace = "documentWorkSpace";
- } else if (
- toolkit.toolkitMethods === "visit page"
- ) {
- const parts = toolkit.message.split("\n");
- const url = parts[0]; // the first line is the URL
- window.location.href = url;
- } else if (toolkit.toolkitMethods === "scrape") {
- window.location.href = toolkit.message;
- }
- }}
- className="py-0.5 px-xs bg-log-default rounded-sm flex gap-xs items-start hover:opacity-50 transition-all duration-300"
- >
- {/* {toolkit.toolkitStatus} */}
-
- {toolkit.toolkitStatus === "running" ? (
-
- ) : (
- agentMap[data.type]?.icon
- )}
-
-
-
- {toolkit.toolkitName}
-
-
-
-
- {toolkit.toolkitMethods}
-
+ {data.type === 'developer_agent' &&
+ data?.agent?.tasks &&
+ data?.agent?.tasks?.filter(
+ (task) => task.terminal && task.terminal.length > 0
+ )?.length > 0 && (
+
+ {data.agent?.tasks
+ .filter((task) => task.terminal && task.terminal.length > 0)
+ .slice(0, 4)
+ .map((task) => {
+ return (
+
+ task.terminal && task.terminal.length > 0
+ ).length === 1
+ ? 'h-full min-w-full'
+ : 'h-[calc(50%-8px)] min-w-[calc(50%-8px)]'
+ } relative flex-1 overflow-hidden rounded-sm object-cover`}
+ >
+
+
+
+
+ );
+ })}
+
+ )}
+
+ {data.agent?.tasks && data.agent?.tasks.length > 0 && (
+
+ {/*
Subtasks
*/}
+
+ task.status === 'completed' && !task.reAssignTo
+ ).length || 0
+ }
+ reAssignTo={
+ data.agent.tasks?.filter((task) => task.reAssignTo)
+ ?.length || 0
+ }
+ progress={
+ data.agent?.tasks?.filter(
+ (task) =>
+ task.status !== 'failed' &&
+ task.status !== 'completed' &&
+ task.status !== 'skipped' &&
+ task.status !== 'waiting' &&
+ task.status !== '' &&
+ !task.reAssignTo
+ ).length || 0
+ }
+ skipped={
+ data.agent?.tasks?.filter(
+ (task) =>
+ (task.status === 'skipped' ||
+ task.status === 'waiting' ||
+ task.status === '') &&
+ !task.reAssignTo
+ ).length || 0
+ }
+ failed={
+ data.agent?.tasks?.filter(
+ (task) => task.status === 'failed'
+ ).length || 0
+ }
+ selectedState={selectedState}
+ onStateChange={setSelectedState}
+ clickable={true}
+ />
+
+
+ )}
+
{
+ e.stopPropagation();
+ }}
+ className={`scrollbar mt-sm flex flex-col gap-2 overflow-y-auto pr-3 duration-500 ease-out animate-in fade-in-0 slide-in-from-bottom-4 ${
+ shouldScroll && 'scrollbar !overflow-y-scroll'
+ }`}
+ style={{
+ maxHeight:
+ data.img && data.img.length > 0
+ ? `calc(100vh - 200px - 180px - 60px - ${toolsHeight}px)`
+ : `calc(100vh - 200px - 60px - ${toolsHeight}px)`,
+ }}
+ >
+ {data.agent?.tasks &&
+ filterTasks.map((task) => {
+ return (
+
{
+ setSelectedTask(task);
+ setIsExpanded(true);
+ data.onExpandChange(id, true);
+ if (task.agent) {
+ chatStore.setActiveWorkSpace(
+ chatStore.activeTaskId as string,
+ 'workflow'
+ );
+ chatStore.setActiveAgent(
+ chatStore.activeTaskId as string,
+ task.agent?.agent_id
+ );
+ window.electronAPI.hideAllWebview();
+ }
+ }}
+ key={`taskList-${task.id}-${task.failure_count}`}
+ className={`flex gap-2 rounded-lg px-sm py-sm transition-all duration-300 ease-in-out animate-in fade-in-0 slide-in-from-left-2 ${
+ task.reAssignTo
+ ? 'bg-task-fill-warning'
+ : task.status === 'completed'
+ ? 'bg-green-50'
+ : task.status === 'failed'
+ ? 'bg-task-fill-error'
+ : task.status === 'running'
+ ? 'bg-zinc-50'
+ : task.status === 'blocked'
+ ? 'bg-task-fill-warning'
+ : 'bg-zinc-50'
+ } cursor-pointer border border-solid border-transparent ${
+ task.status === 'completed'
+ ? 'hover:border-bg-fill-success-primary'
+ : task.status === 'failed'
+ ? 'hover:border-task-border-focus-error'
+ : task.status === 'running'
+ ? 'hover:border-border-primary'
+ : task.status === 'blocked'
+ ? 'hover:border-task-border-focus-warning'
+ : 'border-transparent'
+ } ${
+ selectedTask?.id === task.id
+ ? task.status === 'completed'
+ ? '!border-bg-fill-success-primary'
+ : task.status === 'failed'
+ ? '!border-text-cuation-primary'
+ : task.status === 'running'
+ ? '!border-border-primary'
+ : task.status === 'blocked'
+ ? '!border-text-warning-primary'
+ : 'border-transparent'
+ : 'border-transparent'
+ }`}
+ >
+
+ {task.reAssignTo ? (
+ // reassign to other agent
+
+ ) : (
+ // normal task
+ <>
+ {task.status === 'running' && (
+
+ )}
+ {task.status === 'skipped' && (
+
+ )}
+ {task.status === 'completed' && (
+
+ )}
+ {task.status === 'failed' && (
+
+ )}
+ {task.status === 'blocked' && (
+
+ )}
+ {(task.status === '' ||
+ task.status === 'waiting') && (
+
+ )}
+ >
+ )}
+
+
+
+
+
+ No. {getTaskId(task.id)}
+
+ {task.reAssignTo ? (
+
+ Reassigned to {task.reAssignTo}
+
+ ) : (
+ (task.failure_count ?? 0) > 0 && (
+
+ Attempt {task.failure_count}
+
+ )
+ )}
+
+
{task.content}
+
+ {task?.status === 'running' && (
+
+ {/* active toolkit */}
+ {task.toolkits &&
+ task.toolkits.length > 0 &&
+ task.toolkits
+ .filter(
+ (tool: any) => tool.toolkitName !== 'notice'
+ )
+ .at(-1)?.toolkitStatus === 'running' && (
+
+ {agentMap[data.type]?.icon ?? (
+
+ )}
+
+
+
+
+ )}
+
+ )}
+
+
+ );
+ })}
+
+
+ {isExpanded && (
+
+
{
+ e.stopPropagation();
+ }}
+ className="scrollbar scrollbar-gutter-stable my-2 flex max-h-[calc(100vh-200px)] flex-col gap-sm overflow-y-auto pr-sm"
+ >
+ {selectedTask &&
+ selectedTask.toolkits &&
+ selectedTask.toolkits.length > 0 &&
+ selectedTask.toolkits.map((toolkit: any, index: number) => (
+
+ {toolkit.toolkitName === 'notice' ? (
+
+
+
+ ) : (
+
+
+ {
+ e.stopPropagation();
+ if (toolkit.toolkitMethods === 'write to file') {
+ chatStore.tasks[
+ chatStore.activeTaskId as string
+ ].activeWorkSpace = 'documentWorkSpace';
+ } else if (
+ toolkit.toolkitMethods === 'visit page'
+ ) {
+ const parts = toolkit.message.split('\n');
+ const url = parts[0]; // the first line is the URL
+ window.location.href = url;
+ } else if (toolkit.toolkitMethods === 'scrape') {
+ window.location.href = toolkit.message;
+ }
+ }}
+ className="flex items-start gap-xs rounded-sm bg-log-default px-xs py-0.5 transition-all duration-300 hover:opacity-50"
+ >
+ {/* {toolkit.toolkitStatus} */}
+
+ {toolkit.toolkitStatus === 'running' ? (
+
+ ) : (
+ agentMap[data.type]?.icon
+ )}
+
+
+
+ {toolkit.toolkitName}
+
+
+
+
+ {toolkit.toolkitMethods}
+
-
- {toolkit.message}
-
-
-
-
-
-
- {toolkit.message && (
-
-
-
- )}
-
- )}
-
- ))}
- {selectedTask?.report && (
-
{
- e.stopPropagation();
- }}
- className="w-full flex flex-col gap-sm my-2 "
- >
-
- Completion Report
-
-
-
- )}
-
-
- )}
-
-
- >
- );
-}
\ No newline at end of file
+
+ {toolkit.message}
+
+
+
+
+
+
+ {toolkit.message && (
+
+
+
+ )}
+
+ )}
+
+ ))}
+ {selectedTask?.report && (
+
{
+ e.stopPropagation();
+ }}
+ className="my-2 flex w-full flex-col gap-sm"
+ >
+
+ Completion Report
+
+
+
+ )}
+
+
+ )}
+
+
+ >
+ );
+}
diff --git a/src/components/WorkSpaceMenu/index.tsx b/src/components/WorkSpaceMenu/index.tsx
index fe846069d..a277e72d0 100644
--- a/src/components/WorkSpaceMenu/index.tsx
+++ b/src/components/WorkSpaceMenu/index.tsx
@@ -12,414 +12,423 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
-import { useWorkerList } from "@/store/authStore";
+import { AddWorker } from '@/components/AddWorker';
+import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
+import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
+import { useWorkerList } from '@/store/authStore';
+import { AnimatePresence, motion } from 'framer-motion';
import {
- Bot,
- FileText,
- Globe,
- Image,
- Inbox,
- CodeXml,
- Bird,
- LayoutGrid,
-} from "lucide-react";
-import { motion, AnimatePresence } from "framer-motion";
-import { useEffect, useState } from "react";
-import { AddWorker } from "@/components/AddWorker";
-import { Badge } from "../ui/badge";
-import { useTranslation } from "react-i18next";
-import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
+ Bird,
+ Bot,
+ CodeXml,
+ FileText,
+ Globe,
+ Image,
+ Inbox,
+ LayoutGrid,
+} from 'lucide-react';
+import { useEffect, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Badge } from '../ui/badge';
export function WorkSpaceMenu() {
- const { t } = useTranslation();
- const { chatStore } = useChatStoreAdapter();
- if (!chatStore) {
- return Loading...
;
- }
- const workerList = useWorkerList();
- const baseWorker: Agent[] = [
- {
- tasks: [],
- agent_id: "developer_agent",
- name: t("layout.developer-agent"),
- type: "developer_agent",
- log: [],
- activeWebviewIds: [],
- },
- {
- tasks: [],
- agent_id: "browser_agent",
- name: t("layout.browser-agent"),
- type: "browser_agent",
- log: [],
- activeWebviewIds: [],
- },
- {
- tasks: [],
- agent_id: "multi_modal_agent",
- name: t("layout.multi-modal-agent"),
- type: "multi_modal_agent",
- log: [],
- activeWebviewIds: [],
- },
- // {
- // tasks: [],
- // agent_id: "social_medium_agent",
- // name: "Social Medium Agent",
- // type: "social_medium_agent",
- // log: [],
- // activeWebviewIds: [],
- // },
- {
- tasks: [],
- agent_id: "document_agent",
- name: t("layout.document-agent"),
- type: "document_agent",
- log: [],
- activeWebviewIds: [],
- },
- ];
- const [agentList, setAgentList] = useState([]);
- useEffect(() => {
- const taskAssigning =
- chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning;
- const base = [...baseWorker, ...workerList].filter(
- (worker) => !taskAssigning.find((agent) => agent.type === worker.type)
- );
- setAgentList([...base, ...taskAssigning]);
- }, [
- chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning,
- workerList,
- ]);
+ const { t } = useTranslation();
+ const { chatStore } = useChatStoreAdapter();
+ const workerList = useWorkerList();
- useEffect(() => {
- const cleanup = window.electronAPI.onWebviewNavigated((id: string, url: string) => {
- let webViewUrls = [
- ...chatStore.tasks[chatStore.activeTaskId as string].webViewUrls,
- ];
- let taskAssigning = [
- ...chatStore.tasks[chatStore.activeTaskId as string].taskAssigning,
- ];
- const hasId = taskAssigning.find((item) =>
- item.activeWebviewIds?.find((webview) => webview.id === id)
- );
- if (!hasId) {
- const hasUrl = webViewUrls.find(
- (item) => new URL(item.url).hostname === new URL(url).hostname
- );
+ const baseWorker: Agent[] = useMemo(
+ () => [
+ {
+ tasks: [],
+ agent_id: 'developer_agent',
+ name: t('layout.developer-agent'),
+ type: 'developer_agent',
+ log: [],
+ activeWebviewIds: [],
+ },
+ {
+ tasks: [],
+ agent_id: 'browser_agent',
+ name: t('layout.browser-agent'),
+ type: 'browser_agent',
+ log: [],
+ activeWebviewIds: [],
+ },
+ {
+ tasks: [],
+ agent_id: 'multi_modal_agent',
+ name: t('layout.multi-modal-agent'),
+ type: 'multi_modal_agent',
+ log: [],
+ activeWebviewIds: [],
+ },
+ // {
+ // tasks: [],
+ // agent_id: "social_medium_agent",
+ // name: "Social Medium Agent",
+ // type: "social_medium_agent",
+ // log: [],
+ // activeWebviewIds: [],
+ // },
+ {
+ tasks: [],
+ agent_id: 'document_agent',
+ name: t('layout.document-agent'),
+ type: 'document_agent',
+ log: [],
+ activeWebviewIds: [],
+ },
+ ],
+ [t]
+ );
- if (hasUrl) {
- const activeAgentIndex = taskAssigning.findIndex((item) =>
- item.tasks.find((task) => task.id === hasUrl?.processTaskId)
- );
+ const activeTaskId = chatStore?.activeTaskId as string;
+ const taskAssigning = chatStore?.tasks[activeTaskId]?.taskAssigning;
+ const webViewUrls = chatStore?.tasks[activeTaskId]?.webViewUrls;
- if (activeAgentIndex === -1) {
- const browserAgentIndex = taskAssigning.findIndex((item) => item.type === 'browser_agent');
- if (browserAgentIndex !== -1) {
- taskAssigning[browserAgentIndex].activeWebviewIds?.push({
- id,
- url,
- img: "",
- processTaskId: hasUrl?.processTaskId || "",
- });
- chatStore.setTaskAssigning(
- chatStore.activeTaskId as string,
- taskAssigning
- );
- }
- } else {
- taskAssigning[activeAgentIndex].activeWebviewIds?.push({
- id,
- url,
- img: "",
- processTaskId: hasUrl?.processTaskId || "",
- });
- chatStore.setTaskAssigning(
- chatStore.activeTaskId as string,
- taskAssigning
- );
- }
- const urlIndex = webViewUrls.findIndex((item) => item.url === url);
- if (urlIndex !== -1) {
- webViewUrls.splice(urlIndex, 1);
- }
- chatStore.setWebViewUrls(chatStore.activeTaskId as string, [
- ...webViewUrls,
- ]);
- } else {
- // If no URL match found, also try to add to browser_agent
- const browserAgentIndex = taskAssigning.findIndex((item) => item.type === 'browser_agent');
- if (browserAgentIndex !== -1 && webViewUrls.length > 0) {
- taskAssigning[browserAgentIndex].activeWebviewIds?.push({
- id,
- url,
- img: "",
- processTaskId: webViewUrls[0]?.processTaskId || "",
- });
- chatStore.setTaskAssigning(
- chatStore.activeTaskId as string,
- taskAssigning
- );
- }
- }
- }
+ const agentList = useMemo(() => {
+ if (!chatStore) return [];
+ const base = [...baseWorker, ...workerList].filter(
+ (worker) => !taskAssigning?.find((agent) => agent.type === worker.type)
+ );
+ return [...base, ...(taskAssigning || [])];
+ }, [chatStore, baseWorker, workerList, taskAssigning]);
- let webviews: { id: string; agent_id: string; index: number }[] = [];
- taskAssigning.map((item) => {
- if (item.type === "browser_agent") {
- item.activeWebviewIds?.map((webview, index) => {
- // console.log("@@@@@@", webview);
- if (webview.id === id) {
- webviews.push({ ...webview, agent_id: item.agent_id, index });
- }
- });
- }
- });
+ useEffect(() => {
+ if (!chatStore) return;
+ const cleanup = window.electronAPI.onWebviewNavigated(
+ (id: string, url: string) => {
+ if (!chatStore.activeTaskId) return;
+ let webViewUrls = [
+ ...chatStore.tasks[chatStore.activeTaskId as string].webViewUrls,
+ ];
+ let taskAssigning = [
+ ...chatStore.tasks[chatStore.activeTaskId as string].taskAssigning,
+ ];
+ const hasId = taskAssigning.find((item) =>
+ item.activeWebviewIds?.find((webview) => webview.id === id)
+ );
+ if (!hasId) {
+ const hasUrl = webViewUrls.find(
+ (item) => new URL(item.url).hostname === new URL(url).hostname
+ );
- if (taskAssigning.length === 0 || webviews.length === 0) return;
+ if (hasUrl) {
+ const activeAgentIndex = taskAssigning.findIndex((item) =>
+ item.tasks.find((task) => task.id === hasUrl?.processTaskId)
+ );
- // capture webview
- const captureWebview = () => {
- webviews.map((webview) => {
- window.ipcRenderer
- .invoke("capture-webview", webview.id)
- .then((base64: string) => {
- let taskAssigning = [
- ...chatStore.tasks[chatStore.activeTaskId as string]
- .taskAssigning,
- ];
- const browserAgentIndex = taskAssigning.findIndex(
- (agent) => agent.agent_id === webview.agent_id
- );
+ if (activeAgentIndex === -1) {
+ const browserAgentIndex = taskAssigning.findIndex(
+ (item) => item.type === 'browser_agent'
+ );
+ if (browserAgentIndex !== -1) {
+ taskAssigning[browserAgentIndex].activeWebviewIds?.push({
+ id,
+ url,
+ img: '',
+ processTaskId: hasUrl?.processTaskId || '',
+ });
+ chatStore.setTaskAssigning(
+ chatStore.activeTaskId as string,
+ taskAssigning
+ );
+ }
+ } else {
+ taskAssigning[activeAgentIndex].activeWebviewIds?.push({
+ id,
+ url,
+ img: '',
+ processTaskId: hasUrl?.processTaskId || '',
+ });
+ chatStore.setTaskAssigning(
+ chatStore.activeTaskId as string,
+ taskAssigning
+ );
+ }
+ const urlIndex = webViewUrls.findIndex((item) => item.url === url);
+ if (urlIndex !== -1) {
+ webViewUrls.splice(urlIndex, 1);
+ }
+ chatStore.setWebViewUrls(chatStore.activeTaskId as string, [
+ ...webViewUrls,
+ ]);
+ } else {
+ // If no URL match found, also try to add to browser_agent
+ const browserAgentIndex = taskAssigning.findIndex(
+ (item) => item.type === 'browser_agent'
+ );
+ if (browserAgentIndex !== -1 && webViewUrls.length > 0) {
+ taskAssigning[browserAgentIndex].activeWebviewIds?.push({
+ id,
+ url,
+ img: '',
+ processTaskId: webViewUrls[0]?.processTaskId || '',
+ });
+ chatStore.setTaskAssigning(
+ chatStore.activeTaskId as string,
+ taskAssigning
+ );
+ }
+ }
+ }
- if (
- browserAgentIndex !== -1 &&
- base64 &&
- base64 !== "data:image/jpeg;base64,"
- ) {
- taskAssigning[browserAgentIndex].activeWebviewIds![
- webview.index
- ].img = base64;
+ let webviews: { id: string; agent_id: string; index: number }[] = [];
+ taskAssigning.map((item) => {
+ if (item.type === 'browser_agent') {
+ item.activeWebviewIds?.map((webview, index) => {
+ // console.log("@@@@@@", webview);
+ if (webview.id === id) {
+ webviews.push({ ...webview, agent_id: item.agent_id, index });
+ }
+ });
+ }
+ });
- chatStore.setTaskAssigning(
- chatStore.activeTaskId as string,
- taskAssigning
- );
- }
- })
- .catch((error) => {
- console.error("capture webview error:", error);
- });
- });
- };
- setTimeout(() => {
- captureWebview();
- }, 200);
- });
+ if (taskAssigning.length === 0 || webviews.length === 0) return;
- // Cleanup function to remove listener when component unmounts or dependencies change
- return cleanup;
- }, [
- chatStore.activeTaskId,
- chatStore.tasks[chatStore.activeTaskId as string]?.webViewUrls,
- chatStore.tasks[chatStore.activeTaskId as string]?.taskAssigning,
- ]);
+ // capture webview
+ const captureWebview = () => {
+ webviews.map((webview) => {
+ window.ipcRenderer
+ .invoke('capture-webview', webview.id)
+ .then((base64: string) => {
+ let taskAssigning = [
+ ...chatStore.tasks[chatStore.activeTaskId as string]
+ .taskAssigning,
+ ];
+ const browserAgentIndex = taskAssigning.findIndex(
+ (agent) => agent.agent_id === webview.agent_id
+ );
- const agentMap = {
- developer_agent: {
- name: t("layout.developer-agent"),
- icon: ,
- textColor: "text-text-developer",
- bgColor: "bg-bg-fill-coding-active",
- shapeColor: "bg-bg-fill-coding-default",
- borderColor: "border-bg-fill-coding-active",
- bgColorLight: "bg-emerald-200",
- },
- browser_agent: {
- name: t("layout.browser-agent"),
- icon: ,
- textColor: "text-blue-700",
- bgColor: "bg-bg-fill-browser-active",
- shapeColor: "bg-bg-fill-browser-default",
- borderColor: "border-bg-fill-browser-active",
- bgColorLight: "bg-blue-200",
- },
- document_agent: {
- name: t("layout.document-agent"),
- icon: ,
- textColor: "text-yellow-700",
- bgColor: "bg-bg-fill-writing-active",
- shapeColor: "bg-bg-fill-writing-default",
- borderColor: "border-bg-fill-writing-active",
- bgColorLight: "bg-yellow-200",
- },
- multi_modal_agent: {
- name: t("layout.multi-modal-agent"),
- icon: ,
- textColor: "text-fuchsia-700",
- bgColor: "bg-bg-fill-multimodal-active",
- shapeColor: "bg-bg-fill-multimodal-default",
- borderColor: "border-bg-fill-multimodal-active",
- bgColorLight: "bg-fuchsia-200",
- },
- social_medium_agent: {
- name: t("layout.social-media-agent"),
- icon: ,
- textColor: "text-purple-700",
- bgColor: "bg-violet-700",
- shapeColor: "bg-violet-300",
- borderColor: "border-violet-700",
- bgColorLight: "bg-purple-50",
- },
- };
- const agentIconMap = {
- developer_agent: (
-
- ),
- browser_agent: (
-
- ),
- document_agent: (
-
- ),
- multi_modal_agent: (
-
- ),
- social_medium_agent: (
-
- ),
- };
+ if (
+ browserAgentIndex !== -1 &&
+ base64 &&
+ base64 !== 'data:image/jpeg;base64,'
+ ) {
+ taskAssigning[browserAgentIndex].activeWebviewIds![
+ webview.index
+ ].img = base64;
- const onValueChange = (val: string) => {
- if (!chatStore.activeTaskId) return;
- if (val === "") {
- chatStore.setActiveWorkSpace(chatStore.activeTaskId, "workflow");
- return;
- }
- if (val === "documentWorkSpace") {
- chatStore.setNuwFileNum(chatStore.activeTaskId, 0);
- }
- chatStore.setActiveWorkSpace(chatStore.activeTaskId, val);
+ chatStore.setTaskAssigning(
+ chatStore.activeTaskId as string,
+ taskAssigning
+ );
+ }
+ })
+ .catch((error: unknown) => {
+ console.error('capture webview error:', error);
+ });
+ });
+ };
+ setTimeout(() => {
+ captureWebview();
+ }, 200);
+ }
+ );
- window.electronAPI.hideAllWebview();
- };
+ // Cleanup function to remove listener when component unmounts or dependencies change
+ return cleanup;
+ }, [chatStore, activeTaskId, webViewUrls, taskAssigning]);
- return (
-
-
-
- {chatStore.activeTaskId && (
-
-
-
-
-
- {chatStore.tasks[chatStore.activeTaskId as string].nuwFileNum >
- 0 && (
-
- {
- chatStore.tasks[chatStore.activeTaskId as string]
- .nuwFileNum
- }
-
- )}
-
-
-
- )}
-
- {/* activeAgent */}
-
- {agentList.length > 0 && (
-
-
-
- {agentList.map((agent) => (
-
-
-
-
- {
- agentIconMap[
- agent.type as keyof typeof agentIconMap
- ]
- }
-
-
-
- ))}
-
-
-
- )}
-
-
-
-
-
- );
+ if (!chatStore) {
+ return Loading...
;
+ }
+
+ const agentMap = {
+ developer_agent: {
+ name: t('layout.developer-agent'),
+ icon: ,
+ textColor: 'text-text-developer',
+ bgColor: 'bg-bg-fill-coding-active',
+ shapeColor: 'bg-bg-fill-coding-default',
+ borderColor: 'border-bg-fill-coding-active',
+ bgColorLight: 'bg-emerald-200',
+ },
+ browser_agent: {
+ name: t('layout.browser-agent'),
+ icon: ,
+ textColor: 'text-blue-700',
+ bgColor: 'bg-bg-fill-browser-active',
+ shapeColor: 'bg-bg-fill-browser-default',
+ borderColor: 'border-bg-fill-browser-active',
+ bgColorLight: 'bg-blue-200',
+ },
+ document_agent: {
+ name: t('layout.document-agent'),
+ icon: ,
+ textColor: 'text-yellow-700',
+ bgColor: 'bg-bg-fill-writing-active',
+ shapeColor: 'bg-bg-fill-writing-default',
+ borderColor: 'border-bg-fill-writing-active',
+ bgColorLight: 'bg-yellow-200',
+ },
+ multi_modal_agent: {
+ name: t('layout.multi-modal-agent'),
+ icon: ,
+ textColor: 'text-fuchsia-700',
+ bgColor: 'bg-bg-fill-multimodal-active',
+ shapeColor: 'bg-bg-fill-multimodal-default',
+ borderColor: 'border-bg-fill-multimodal-active',
+ bgColorLight: 'bg-fuchsia-200',
+ },
+ social_medium_agent: {
+ name: t('layout.social-media-agent'),
+ icon: ,
+ textColor: 'text-purple-700',
+ bgColor: 'bg-violet-700',
+ shapeColor: 'bg-violet-300',
+ borderColor: 'border-violet-700',
+ bgColorLight: 'bg-purple-50',
+ },
+ };
+ const agentIconMap = {
+ developer_agent: (
+
+ ),
+ browser_agent: (
+
+ ),
+ document_agent: (
+
+ ),
+ multi_modal_agent: (
+
+ ),
+ social_medium_agent: (
+
+ ),
+ };
+
+ const onValueChange = (val: string) => {
+ if (!chatStore.activeTaskId) return;
+ if (val === '') {
+ chatStore.setActiveWorkSpace(chatStore.activeTaskId, 'workflow');
+ return;
+ }
+ if (val === 'documentWorkSpace') {
+ chatStore.setNuwFileNum(chatStore.activeTaskId, 0);
+ }
+ chatStore.setActiveWorkSpace(chatStore.activeTaskId, val);
+
+ window.electronAPI.hideAllWebview();
+ };
+
+ return (
+
+
+
+ {chatStore.activeTaskId && (
+
+
+
+
+
+ {chatStore.tasks[chatStore.activeTaskId as string].nuwFileNum >
+ 0 && (
+
+ {
+ chatStore.tasks[chatStore.activeTaskId as string]
+ .nuwFileNum
+ }
+
+ )}
+
+
+
+ )}
+
+ {/* activeAgent */}
+
+ {agentList.length > 0 && (
+
+
+
+ {agentList.map((agent) => (
+
+
+
+
+ {
+ agentIconMap[
+ agent.type as keyof typeof agentIconMap
+ ]
+ }
+
+
+
+ ))}
+
+
+
+ )}
+
+
+
+
+
+ );
}
diff --git a/src/components/animate-ui/icons/alarm-clock.tsx b/src/components/animate-ui/icons/alarm-clock.tsx
index e65d97d7a..85ab1546c 100644
--- a/src/components/animate-ui/icons/alarm-clock.tsx
+++ b/src/components/animate-ui/icons/alarm-clock.tsx
@@ -14,13 +14,12 @@
'use client';
-import * as React from 'react';
import { motion, type Variants } from 'motion/react';
import {
getVariants,
- useAnimateIconContext,
IconWrapper,
+ useAnimateIconContext,
type IconProps,
} from '@/components/animate-ui/icons/icon';
@@ -253,9 +252,9 @@ function AlarmClock(props: AlarmClockProps) {
}
export {
- animations,
AlarmClock,
AlarmClock as AlarmClockIcon,
- type AlarmClockProps,
+ animations,
type AlarmClockProps as AlarmClockIconProps,
+ type AlarmClockProps,
};
diff --git a/src/components/animate-ui/icons/bot.tsx b/src/components/animate-ui/icons/bot.tsx
index c66dc846a..7a52df9d8 100644
--- a/src/components/animate-ui/icons/bot.tsx
+++ b/src/components/animate-ui/icons/bot.tsx
@@ -14,13 +14,12 @@
'use client';
-import * as React from 'react';
import { motion, type Variants } from 'motion/react';
import {
getVariants,
- useAnimateIconContext,
IconWrapper,
+ useAnimateIconContext,
type IconProps,
} from '@/components/animate-ui/icons/icon';
@@ -181,6 +180,6 @@ export {
animations,
Bot,
Bot as BotIcon,
- type BotProps,
type BotProps as BotIconProps,
+ type BotProps,
};
diff --git a/src/components/animate-ui/icons/compass.tsx b/src/components/animate-ui/icons/compass.tsx
index 96895fa9a..fa96a0efd 100644
--- a/src/components/animate-ui/icons/compass.tsx
+++ b/src/components/animate-ui/icons/compass.tsx
@@ -14,13 +14,12 @@
'use client';
-import * as React from 'react';
import { motion, type Variants } from 'motion/react';
import {
getVariants,
- useAnimateIconContext,
IconWrapper,
+ useAnimateIconContext,
type IconProps,
} from '@/components/animate-ui/icons/icon';
@@ -102,6 +101,6 @@ export {
animations,
Compass,
Compass as CompassIcon,
- type CompassProps,
type CompassProps as CompassIconProps,
-};
\ No newline at end of file
+ type CompassProps,
+};
diff --git a/src/components/animate-ui/icons/hammer.tsx b/src/components/animate-ui/icons/hammer.tsx
index 716db2bd2..9735cc936 100644
--- a/src/components/animate-ui/icons/hammer.tsx
+++ b/src/components/animate-ui/icons/hammer.tsx
@@ -14,13 +14,12 @@
'use client';
-import * as React from 'react';
import { motion, type Variants } from 'motion/react';
import {
getVariants,
- useAnimateIconContext,
IconWrapper,
+ useAnimateIconContext,
type IconProps,
} from '@/components/animate-ui/icons/icon';
@@ -93,6 +92,6 @@ export {
animations,
Hammer,
Hammer as HammerIcon,
- type HammerProps,
type HammerProps as HammerIconProps,
+ type HammerProps,
};
diff --git a/src/components/animate-ui/icons/icon.tsx b/src/components/animate-ui/icons/icon.tsx
index 18a8b2c15..05c7e09f0 100644
--- a/src/components/animate-ui/icons/icon.tsx
+++ b/src/components/animate-ui/icons/icon.tsx
@@ -14,20 +14,23 @@
'use client';
-import * as React from 'react';
import {
motion,
useAnimation,
+ type HTMLMotionProps,
+ type LegacyAnimationControls,
type SVGMotionProps,
type UseInViewOptions,
- type LegacyAnimationControls,
type Variants,
- type HTMLMotionProps,
} from 'motion/react';
+import * as React from 'react';
-import { cn } from '@/lib/utils';
+import {
+ Slot,
+ type WithAsChild,
+} from '@/components/animate-ui/primitives/animate/slot';
import { useIsInView } from '@/hooks/use-is-in-view';
-import { Slot, type WithAsChild } from '@/components/animate-ui/primitives/animate/slot';
+import { cn } from '@/lib/utils';
const staticAnimations = {
path: {
@@ -103,7 +106,7 @@ type IconWrapperProps = IconProps & {
};
const AnimateIconContext = React.createContext(
- null,
+ null
);
function useAnimateIconContext() {
@@ -126,7 +129,7 @@ function useAnimateIconContext() {
function composeEventHandlers>(
theirs?: (event: E) => void,
- ours?: (event: E) => void,
+ ours?: (event: E) => void
) {
return (event: E) => {
theirs?.(event);
@@ -134,7 +137,6 @@ function composeEventHandlers>(
};
}
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyProps = Record;
function AnimateIcon({
@@ -199,7 +201,7 @@ function AnimateIcon({
setLocalAnimate(true);
}
},
- [animation, delay, bumpGeneration],
+ [animation, delay, bumpGeneration]
);
const stopAnimation = React.useCallback(() => {
@@ -250,7 +252,7 @@ function AnimateIcon({
return;
}
},
- [controls],
+ [controls]
);
React.useEffect(() => {
@@ -394,14 +396,14 @@ function AnimateIcon({
childProps.onMouseEnter,
() => {
if (animateOnHover) startAnimation(animateOnHover);
- },
+ }
);
const handleMouseLeave = composeEventHandlers>(
childProps.onMouseLeave,
() => {
if (animateOnHover || animateOnTap) stopAnimation();
- },
+ }
);
const handlePointerDown = composeEventHandlers<
@@ -414,7 +416,7 @@ function AnimateIcon({
childProps.onPointerUp,
() => {
if (animateOnTap) stopAnimation();
- },
+ }
);
const content = asChild ? (
@@ -542,7 +544,7 @@ function IconWrapper({
className,
((animationProp ?? parentAnimation) === 'path' ||
(animationProp ?? parentAnimation) === 'path-loop') &&
- pathClassName,
+ pathClassName
)}
{...props}
/>
@@ -573,7 +575,7 @@ function IconWrapper({
className={cn(
className,
(animationToUse === 'path' || animationToUse === 'path-loop') &&
- pathClassName,
+ pathClassName
)}
{...props}
/>
@@ -608,7 +610,7 @@ function IconWrapper({
className={cn(
className,
(animationProp === 'path' || animationProp === 'path-loop') &&
- pathClassName,
+ pathClassName
)}
{...props}
/>
@@ -622,7 +624,7 @@ function IconWrapper({
className={cn(
className,
(animationProp === 'path' || animationProp === 'path-loop') &&
- pathClassName,
+ pathClassName
)}
{...props}
/>
@@ -657,14 +659,14 @@ function getVariants<
}
export {
+ AnimateIcon,
+ getVariants,
+ IconWrapper,
pathClassName,
staticAnimations,
- AnimateIcon,
- IconWrapper,
useAnimateIconContext,
- getVariants,
+ type AnimateIconContextValue,
+ type AnimateIconProps,
type IconProps,
type IconWrapperProps,
- type AnimateIconProps,
- type AnimateIconContextValue,
};
diff --git a/src/components/animate-ui/icons/orbit.tsx b/src/components/animate-ui/icons/orbit.tsx
index bd76ab63e..b8b1e01f1 100644
--- a/src/components/animate-ui/icons/orbit.tsx
+++ b/src/components/animate-ui/icons/orbit.tsx
@@ -14,13 +14,12 @@
'use client';
-import * as React from 'react';
import { motion, type Variants } from 'motion/react';
import {
getVariants,
- useAnimateIconContext,
IconWrapper,
+ useAnimateIconContext,
type IconProps,
} from '@/components/animate-ui/icons/icon';
@@ -115,6 +114,6 @@ export {
animations,
Orbit,
Orbit as OrbitIcon,
- type OrbitProps,
type OrbitProps as OrbitIconProps,
+ type OrbitProps,
};
diff --git a/src/components/animate-ui/icons/pin.tsx b/src/components/animate-ui/icons/pin.tsx
index a5b0dd628..42baafa79 100644
--- a/src/components/animate-ui/icons/pin.tsx
+++ b/src/components/animate-ui/icons/pin.tsx
@@ -14,13 +14,12 @@
'use client';
-import * as React from 'react';
import { motion, Variants } from 'motion/react';
import {
getVariants,
- useAnimateIconContext,
IconWrapper,
+ useAnimateIconContext,
type IconProps,
} from '@/components/animate-ui/icons/icon';
@@ -126,6 +125,6 @@ export {
animations,
Pin,
Pin as PinIcon,
- type PinProps,
type PinProps as PinIconProps,
+ type PinProps,
};
diff --git a/src/components/animate-ui/icons/settings.tsx b/src/components/animate-ui/icons/settings.tsx
index 12123de2d..07b34c683 100644
--- a/src/components/animate-ui/icons/settings.tsx
+++ b/src/components/animate-ui/icons/settings.tsx
@@ -14,13 +14,12 @@
'use client';
-import * as React from 'react';
import { motion, type Variants } from 'motion/react';
import {
getVariants,
- useAnimateIconContext,
IconWrapper,
+ useAnimateIconContext,
type IconProps,
} from '@/components/animate-ui/icons/icon';
@@ -106,6 +105,6 @@ export {
animations,
Settings,
Settings as SettingsIcon,
- type SettingsProps,
type SettingsProps as SettingsIconProps,
+ type SettingsProps,
};
diff --git a/src/components/animate-ui/icons/sparkle.tsx b/src/components/animate-ui/icons/sparkle.tsx
index 8c7952f62..e0d638ed8 100644
--- a/src/components/animate-ui/icons/sparkle.tsx
+++ b/src/components/animate-ui/icons/sparkle.tsx
@@ -14,13 +14,12 @@
'use client';
-import * as React from 'react';
import { motion, type Variants } from 'motion/react';
import {
getVariants,
- useAnimateIconContext,
IconWrapper,
+ useAnimateIconContext,
type IconProps,
} from '@/components/animate-ui/icons/icon';
@@ -100,6 +99,6 @@ export {
animations,
Sparkle,
Sparkle as SparkleIcon,
- type SparkleProps,
type SparkleProps as SparkleIconProps,
-};
\ No newline at end of file
+ type SparkleProps,
+};
diff --git a/src/components/animate-ui/primitives/animate/slot.tsx b/src/components/animate-ui/primitives/animate/slot.tsx
index f00a30a0c..cd9e1921f 100644
--- a/src/components/animate-ui/primitives/animate/slot.tsx
+++ b/src/components/animate-ui/primitives/animate/slot.tsx
@@ -14,9 +14,9 @@
'use client';
-import * as React from 'react';
-import { motion, isMotionComponent, type HTMLMotionProps } from 'motion/react';
import { cn } from '@/lib/utils';
+import { isMotionComponent, motion, type HTMLMotionProps } from 'motion/react';
+import * as React from 'react';
type AnyProps = Record;
@@ -30,7 +30,6 @@ type WithAsChild =
| (Base & { asChild?: false | undefined });
type SlotProps = {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
children?: any;
} & DOMMotionProps;
@@ -52,14 +51,14 @@ function mergeRefs(
function mergeProps(
childProps: AnyProps,
- slotProps: DOMMotionProps,
+ slotProps: DOMMotionProps
): AnyProps {
const merged: AnyProps = { ...childProps, ...slotProps };
if (childProps.className || slotProps.className) {
merged.className = cn(
childProps.className as string,
- slotProps.className as string,
+ slotProps.className as string
);
}
@@ -73,42 +72,39 @@ function mergeProps(
return merged;
}
-const Slot = React.forwardRef(
- function Slot(
- { children, ...props }: Omit, 'ref'>,
- ref: React.Ref
- ) {
- const isAlreadyMotion =
- typeof children.type === 'object' &&
- children.type !== null &&
- isMotionComponent(children.type);
+const Slot = React.forwardRef(function Slot<
+ T extends HTMLElement = HTMLElement,
+>({ children, ...props }: Omit, 'ref'>, ref: React.Ref) {
+ const isAlreadyMotion =
+ typeof children.type === 'object' &&
+ children.type !== null &&
+ isMotionComponent(children.type);
- const Base = React.useMemo(
- () =>
- isAlreadyMotion
- ? (children.type as React.ElementType)
- : motion.create(children.type as React.ElementType),
- [isAlreadyMotion, children.type],
- );
+ const Base = React.useMemo(
+ () =>
+ isAlreadyMotion
+ ? (children.type as React.ElementType)
+ : motion.create(children.type as React.ElementType),
+ [isAlreadyMotion, children.type]
+ );
- if (!React.isValidElement(children)) return null;
+ if (!React.isValidElement(children)) return null;
- const { ref: childRef, ...childProps } = children.props as AnyProps;
+ const { ref: childRef, ...childProps } = children.props as AnyProps;
- const mergedProps = mergeProps(childProps, props);
+ const mergedProps = mergeProps(childProps, props);
- return (
- , ref)} />
- );
- }
-) as (
+ return (
+ , ref)} />
+ );
+}) as (
props: SlotProps & { ref?: React.Ref }
) => React.ReactElement | null;
export {
Slot,
+ type AnyProps,
+ type DOMMotionProps,
type SlotProps,
type WithAsChild,
- type DOMMotionProps,
- type AnyProps,
};
diff --git a/src/components/ui/ShinyText/ShinyText.css b/src/components/ui/ShinyText/ShinyText.css
index 2296979d7..7c5803074 100644
--- a/src/components/ui/ShinyText/ShinyText.css
+++ b/src/components/ui/ShinyText/ShinyText.css
@@ -1,11 +1,6 @@
.shiny-text {
color: transparent;
- background: linear-gradient(
- 120deg,
- #71717b 30%,
- #d4d4d8 50%,
- #71717b 70%
- );
+ background: linear-gradient(120deg, #71717b 30%, #d4d4d8 50%, #71717b 70%);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
diff --git a/src/components/ui/ShinyText/ShinyText.tsx b/src/components/ui/ShinyText/ShinyText.tsx
index f55ed4c56..551b5561a 100644
--- a/src/components/ui/ShinyText/ShinyText.tsx
+++ b/src/components/ui/ShinyText/ShinyText.tsx
@@ -15,23 +15,28 @@
import './ShinyText.css';
interface ShinyTextProps {
- text: string;
- disabled?: boolean;
- speed?: number;
- className?: string;
+ text: string;
+ disabled?: boolean;
+ speed?: number;
+ className?: string;
}
-const ShinyText: React.FC = ({ text, disabled = false, speed = 3, className = '' }) => {
- const animationDuration = `${speed}s`;
+const ShinyText: React.FC = ({
+ text,
+ disabled = false,
+ speed = 3,
+ className = '',
+}) => {
+ const animationDuration = `${speed}s`;
- return (
-
- {text}
-
- );
+ return (
+
+ {text}
+
+ );
};
-export default ShinyText;
\ No newline at end of file
+export default ShinyText;
diff --git a/src/components/ui/SplitText/SplitText.d.ts b/src/components/ui/SplitText/SplitText.d.ts
index 250589e33..24fcb1e88 100644
--- a/src/components/ui/SplitText/SplitText.d.ts
+++ b/src/components/ui/SplitText/SplitText.d.ts
@@ -12,8 +12,8 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-declare module "@/components/ui/SplitText/SplitText" {
- import type { FC } from "react";
+declare module '@/components/ui/SplitText/SplitText' {
+ import type { FC } from 'react';
export interface SplitTextProps {
text: string;
@@ -34,8 +34,8 @@ declare module "@/components/ui/SplitText/SplitText" {
const SplitText: FC;
export default SplitText;
}
-declare module "@/components/SplitText" {
- import type { FC } from "react";
+declare module '@/components/SplitText' {
+ import type { FC } from 'react';
type SplitType = 'chars' | 'words' | 'lines' | 'chars,words,lines' | string;
@@ -58,5 +58,3 @@ declare module "@/components/SplitText" {
const SplitText: FC;
export default SplitText;
}
-
-
diff --git a/src/components/ui/SplitText/SplitText.jsx b/src/components/ui/SplitText/SplitText.jsx
index 92aa89d8c..421e02c11 100644
--- a/src/components/ui/SplitText/SplitText.jsx
+++ b/src/components/ui/SplitText/SplitText.jsx
@@ -12,11 +12,11 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { useRef, useEffect, useState } from 'react';
+import { useGSAP } from '@gsap/react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { SplitText as GSAPSplitText } from 'gsap/SplitText';
-import { useGSAP } from '@gsap/react';
+import { useEffect, useRef, useState } from 'react';
gsap.registerPlugin(ScrollTrigger, GSAPSplitText, useGSAP);
@@ -33,16 +33,18 @@ const SplitText = ({
rootMargin = '-100px',
textAlign = 'center',
tag = 'p',
- onLetterAnimationComplete
+ onLetterAnimationComplete,
}) => {
const ref = useRef(null);
const animationCompletedRef = useRef(false);
- const [fontsLoaded, setFontsLoaded] = useState(false);
+ // Initialize state based on current font loading status to avoid setState in effect
+ const [fontsLoaded, setFontsLoaded] = useState(
+ () => document.fonts.status === 'loaded'
+ );
useEffect(() => {
- if (document.fonts.status === 'loaded') {
- setFontsLoaded(true);
- } else {
+ // Only set up listener if fonts are not already loaded
+ if (document.fonts.status !== 'loaded') {
document.fonts.ready.then(() => {
setFontsLoaded(true);
});
@@ -57,7 +59,7 @@ const SplitText = ({
if (el._rbsplitInstance) {
try {
el._rbsplitInstance.revert();
- } catch (_) {
+ } catch {
/* noop */
}
el._rbsplitInstance = null;
@@ -76,10 +78,13 @@ const SplitText = ({
const start = `top ${startPct}%${sign}`;
let targets;
- const assignTargets = self => {
- if (splitType.includes('chars') && self.chars.length) targets = self.chars;
- if (!targets && splitType.includes('words') && self.words.length) targets = self.words;
- if (!targets && splitType.includes('lines') && self.lines.length) targets = self.lines;
+ const assignTargets = (self) => {
+ if (splitType.includes('chars') && self.chars.length)
+ targets = self.chars;
+ if (!targets && splitType.includes('words') && self.words.length)
+ targets = self.words;
+ if (!targets && splitType.includes('lines') && self.lines.length)
+ targets = self.lines;
if (!targets) targets = self.chars || self.words || self.lines;
};
@@ -91,7 +96,7 @@ const SplitText = ({
wordsClass: 'split-word',
charsClass: 'split-char',
reduceWhiteSpace: false,
- onSplit: self => {
+ onSplit: (self) => {
assignTargets(self);
const tween = gsap.fromTo(
targets,
@@ -106,29 +111,29 @@ const SplitText = ({
start,
once: true,
fastScrollEnd: true,
- anticipatePin: 0.4
+ anticipatePin: 0.4,
},
onComplete: () => {
animationCompletedRef.current = true;
onLetterAnimationComplete?.();
},
willChange: 'transform, opacity',
- force3D: true
+ force3D: true,
}
);
return tween;
- }
+ },
});
el._rbsplitInstance = splitInstance;
return () => {
- ScrollTrigger.getAll().forEach(st => {
+ ScrollTrigger.getAll().forEach((st) => {
if (st.trigger === el) st.kill();
});
try {
splitInstance.revert();
- } catch (_) {
+ } catch {
/* noop */
}
el._rbsplitInstance = null;
@@ -146,9 +151,9 @@ const SplitText = ({
threshold,
rootMargin,
fontsLoaded,
- onLetterAnimationComplete
+ onLetterAnimationComplete,
],
- scope: ref
+ scope: ref,
}
);
@@ -159,7 +164,7 @@ const SplitText = ({
display: 'inline-block',
whiteSpace: 'normal',
wordWrap: 'break-word',
- willChange: 'transform, opacity'
+ willChange: 'transform, opacity',
};
const classes = `split-parent ${className}`;
switch (tag) {
diff --git a/src/components/ui/WordCarousel/WordCarousel.css b/src/components/ui/WordCarousel/WordCarousel.css
index b0f7029c1..1d4ef4cbb 100644
--- a/src/components/ui/WordCarousel/WordCarousel.css
+++ b/src/components/ui/WordCarousel/WordCarousel.css
@@ -32,5 +32,3 @@
background-position: 100% 50%;
}
}
-
-
diff --git a/src/components/ui/WordCarousel/WordCarousel.tsx b/src/components/ui/WordCarousel/WordCarousel.tsx
index ba7980429..1b6ad5613 100644
--- a/src/components/ui/WordCarousel/WordCarousel.tsx
+++ b/src/components/ui/WordCarousel/WordCarousel.tsx
@@ -12,8 +12,8 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import React, { useEffect, useMemo, useState } from "react";
-import "./WordCarousel.css";
+import React, { useEffect, useMemo, useState } from 'react';
+import './WordCarousel.css';
export type WordCarouselProps = {
words: string[];
@@ -37,16 +37,22 @@ export function WordCarousel({
const [index, setIndex] = useState(0);
const [fading, setFading] = useState(false);
- const safeWords = useMemo(() => (words && words.length > 0 ? words : [""]), [words]);
+ const safeWords = useMemo(
+ () => (words && words.length > 0 ? words : ['']),
+ [words]
+ );
useEffect(() => {
if (safeWords.length <= 1) return;
const interval = setInterval(() => {
setFading(true);
- const timeout = setTimeout(() => {
- setIndex((prev) => (prev + 1) % safeWords.length);
- setFading(false);
- }, Math.min(300, Math.max(150, rotateIntervalMs * 0.25)));
+ const timeout = setTimeout(
+ () => {
+ setIndex((prev) => (prev + 1) % safeWords.length);
+ setFading(false);
+ },
+ Math.min(300, Math.max(150, rotateIntervalMs * 0.25))
+ );
return () => clearTimeout(timeout);
}, rotateIntervalMs);
return () => clearInterval(interval);
@@ -55,22 +61,28 @@ export function WordCarousel({
const gradientValue = useMemo(
() =>
gradient ??
- "linear-gradient(in oklch 90deg, #1d1d1d 0%, #1d1d1d 30%, #FF0099 35%, #FF0000 45%, #FF4F04 50%, #FFA600 55%, #F8F8F8 60%, #0056FF 65%, #f9f8f6 70%, #f9f8f6 100%)",
+ 'linear-gradient(in oklch 90deg, #1d1d1d 0%, #1d1d1d 30%, #FF0099 35%, #FF0000 45%, #FF4F04 50%, #FFA600 55%, #F8F8F8 60%, #0056FF 65%, #f9f8f6 70%, #f9f8f6 100%)',
[gradient]
);
const style: React.CSSProperties = {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore -- CSS var is fine here
- "--word-gradient": gradientValue,
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ '--word-gradient': gradientValue,
+
// @ts-ignore -- CSS var is fine here
- "--sweep-duration": `${sweepDurationMs}ms`,
+ '--sweep-duration': `${sweepDurationMs}ms`,
} as React.CSSProperties;
return (
@@ -80,4 +92,3 @@ export function WordCarousel({
}
export default WordCarousel;
-
diff --git a/src/components/ui/WordCarousel/index.ts b/src/components/ui/WordCarousel/index.ts
index 040ae6230..3a0427f91 100644
--- a/src/components/ui/WordCarousel/index.ts
+++ b/src/components/ui/WordCarousel/index.ts
@@ -12,6 +12,5 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-export * from "./WordCarousel";
-export { default } from "./WordCarousel";
-
+export * from './WordCarousel';
+export { default } from './WordCarousel';
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx
index 2bace158b..59be1ff1b 100644
--- a/src/components/ui/accordion.tsx
+++ b/src/components/ui/accordion.tsx
@@ -12,13 +12,13 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
-import * as AccordionPrimitive from "@radix-ui/react-accordion"
-import { ChevronDown } from "lucide-react"
+import * as AccordionPrimitive from '@radix-ui/react-accordion';
+import { ChevronDown } from 'lucide-react';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
-const Accordion = AccordionPrimitive.Root
+const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef,
@@ -26,31 +26,31 @@ const AccordionItem = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-AccordionItem.displayName = "AccordionItem"
+));
+AccordionItem.displayName = 'AccordionItem';
const AccordionTrigger = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
-
+
svg]:rotate-180",
+ 'flex flex-1 items-center justify-between py-4 text-left text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
className
)}
{...props}
>
{children}
-
+
-))
-AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+));
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef,
@@ -58,12 +58,12 @@ const AccordionContent = React.forwardRef<
>(({ className, children, ...props }, ref) => (
- {children}
+ {children}
-))
-AccordionContent.displayName = AccordionPrimitive.Content.displayName
+));
+AccordionContent.displayName = AccordionPrimitive.Content.displayName;
-export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
+export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
index 71ff90c1d..52a0b6cbc 100644
--- a/src/components/ui/alert.tsx
+++ b/src/components/ui/alert.tsx
@@ -12,26 +12,26 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
+import { cva, type VariantProps } from 'class-variance-authority';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
const alertVariants = cva(
- "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
+ 'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
{
variants: {
variant: {
- default: "bg-background text-foreground",
+ default: 'bg-background text-foreground',
destructive:
- "border-destructive/50 text-text-cuation dark:border-destructive [&>svg]:text-destructive",
+ 'border-destructive/50 text-text-cuation dark:border-destructive [&>svg]:text-destructive',
},
},
defaultVariants: {
- variant: "default",
+ variant: 'default',
},
}
-)
+);
const Alert = React.forwardRef<
HTMLDivElement,
@@ -43,8 +43,8 @@ const Alert = React.forwardRef<
className={cn(alertVariants({ variant }), className)}
{...props}
/>
-))
-Alert.displayName = "Alert"
+));
+Alert.displayName = 'Alert';
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
@@ -52,11 +52,11 @@ const AlertTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-AlertTitle.displayName = "AlertTitle"
+));
+AlertTitle.displayName = 'AlertTitle';
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
@@ -64,10 +64,10 @@ const AlertDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-AlertDescription.displayName = "AlertDescription"
+));
+AlertDescription.displayName = 'AlertDescription';
-export { Alert, AlertTitle, AlertDescription }
+export { Alert, AlertDescription, AlertTitle };
diff --git a/src/components/ui/alertDialog.tsx b/src/components/ui/alertDialog.tsx
index 3c8a5da6c..d3c79b630 100644
--- a/src/components/ui/alertDialog.tsx
+++ b/src/components/ui/alertDialog.tsx
@@ -12,76 +12,84 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { Button } from "@/components/ui/button";
-import { motion, AnimatePresence } from "framer-motion";
+import { Button } from '@/components/ui/button';
+import { AnimatePresence, motion } from 'framer-motion';
-type ButtonVariant = "primary" | "secondary" | "outline" | "ghost" | "success" | "cuation" | "information" | "warning";
+type ButtonVariant =
+ | 'primary'
+ | 'secondary'
+ | 'outline'
+ | 'ghost'
+ | 'success'
+ | 'cuation'
+ | 'information'
+ | 'warning';
interface ConfirmModalProps {
- isOpen: boolean;
- onClose: () => void;
- onConfirm: () => void;
- title?: string;
- message?: string;
- confirmText?: string;
- cancelText?: string;
- confirmVariant?: ButtonVariant;
+ isOpen: boolean;
+ onClose: () => void;
+ onConfirm: () => void;
+ title?: string;
+ message?: string;
+ confirmText?: string;
+ cancelText?: string;
+ confirmVariant?: ButtonVariant;
}
export default function ConfirmModal({
- isOpen,
- onClose,
- onConfirm,
- title = "Confirm Title",
- message = "Confirm content?",
- confirmText = "Confirm",
- cancelText = "Cancel",
- confirmVariant = "cuation",
+ isOpen,
+ onClose,
+ onConfirm,
+ title = 'Confirm Title',
+ message = 'Confirm content?',
+ confirmText = 'Confirm',
+ cancelText = 'Cancel',
+ confirmVariant = 'cuation',
}: ConfirmModalProps) {
- return (
-
- {isOpen && (
- <>
- {/* Background overlay */}
-
+ return (
+
+ {isOpen && (
+ <>
+ {/* Background overlay */}
+
- {/* Modal */}
-
-
-
- {title}
-
-
{message}
+ {/* Modal */}
+
+
+
+ {title}
+
+
{message}
-
-
- {cancelText}
-
- {
- onConfirm();
- onClose();
- }}
- >
- {confirmText}
-
-
-
-
- >
- )}
-
- );
+
+
+ {cancelText}
+
+ {
+ onConfirm();
+ onClose();
+ }}
+ >
+ {confirmText}
+
+
+
+
+ >
+ )}
+
+ );
}
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
index 4829f7b71..7cedad7da 100644
--- a/src/components/ui/badge.tsx
+++ b/src/components/ui/badge.tsx
@@ -12,39 +12,40 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
+import { cva, type VariantProps } from 'class-variance-authority';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
const badgeVariants = cva(
- "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default:
- "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
+ 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
secondary:
- "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
- "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
- outline: "text-foreground",
+ 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
+ outline: 'text-foreground',
},
},
defaultVariants: {
- variant: "default",
+ variant: 'default',
},
}
-)
+);
export interface BadgeProps
- extends React.HTMLAttributes,
+ extends
+ React.HTMLAttributes,
VariantProps {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
- )
+ );
}
-export { Badge, badgeVariants }
+export { Badge, badgeVariants };
diff --git a/src/components/ui/button.stories.tsx b/src/components/ui/button.stories.tsx
index 29e28bb4b..a0e91af11 100644
--- a/src/components/ui/button.stories.tsx
+++ b/src/components/ui/button.stories.tsx
@@ -12,10 +12,10 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import type { Meta, StoryObj } from '@storybook/react-vite'
-import { Button } from './button'
-import { Plus, Download, Trash2 } from 'lucide-react'
-import { expect, fn, userEvent, within } from 'storybook/test'
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { Download, Plus, Trash2 } from 'lucide-react';
+import { expect, fn, userEvent, within } from 'storybook/test';
+import { Button } from './button';
const meta: Meta = {
title: 'UI/Button',
@@ -53,53 +53,53 @@ const meta: Meta = {
variant: 'primary',
size: 'md',
},
-}
+};
-export default meta
+export default meta;
-type Story = StoryObj
+type Story = StoryObj;
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Primary Button',
},
-}
+};
export const Secondary: Story = {
args: {
variant: 'secondary',
children: 'Secondary Button',
},
-}
+};
export const Outline: Story = {
args: {
variant: 'outline',
children: 'Outline Button',
},
-}
+};
export const Ghost: Story = {
args: {
variant: 'ghost',
children: 'Ghost Button',
},
-}
+};
export const Success: Story = {
args: {
variant: 'success',
children: 'Success Button',
},
-}
+};
export const Warning: Story = {
args: {
variant: 'warning',
children: 'Warning Button',
},
-}
+};
export const Disabled: Story = {
args: {
@@ -107,7 +107,7 @@ export const Disabled: Story = {
children: 'Disabled Button',
disabled: true,
},
-}
+};
export const WithIcon: Story = {
render: (args) => (
@@ -118,7 +118,7 @@ export const WithIcon: Story = {
args: {
variant: 'primary',
},
-}
+};
export const IconOnly: Story = {
render: (args) => (
@@ -130,7 +130,7 @@ export const IconOnly: Story = {
variant: 'ghost',
size: 'icon',
},
-}
+};
export const AllVariants: Story = {
render: () => (
@@ -143,7 +143,7 @@ export const AllVariants: Story = {
Warning
),
-}
+};
export const AllSizes: Story = {
render: () => (
@@ -168,7 +168,7 @@ export const AllSizes: Story = {
),
-}
+};
// Interaction test stories
export const ClickInteraction: Story = {
@@ -178,20 +178,20 @@ export const ClickInteraction: Story = {
onClick: fn(),
},
play: async ({ args, canvasElement }) => {
- const canvas = within(canvasElement)
- const button = canvas.getByRole('button', { name: /click me/i })
+ const canvas = within(canvasElement);
+ const button = canvas.getByRole('button', { name: /click me/i });
// Test that button is visible and enabled
- await expect(button).toBeVisible()
- await expect(button).toBeEnabled()
+ await expect(button).toBeVisible();
+ await expect(button).toBeEnabled();
// Click the button
- await userEvent.click(button)
+ await userEvent.click(button);
// Verify the onClick handler was called
- await expect(args.onClick).toHaveBeenCalledTimes(1)
+ await expect(args.onClick).toHaveBeenCalledTimes(1);
},
-}
+};
export const DisabledInteraction: Story = {
args: {
@@ -201,17 +201,17 @@ export const DisabledInteraction: Story = {
onClick: fn(),
},
play: async ({ args, canvasElement }) => {
- const canvas = within(canvasElement)
- const button = canvas.getByRole('button', { name: /disabled button/i })
+ const canvas = within(canvasElement);
+ const button = canvas.getByRole('button', { name: /disabled button/i });
// Test that button is visible but disabled
- await expect(button).toBeVisible()
- await expect(button).toBeDisabled()
+ await expect(button).toBeVisible();
+ await expect(button).toBeDisabled();
// Verify the onClick handler was NOT called (disabled buttons block pointer events)
- await expect(args.onClick).not.toHaveBeenCalled()
+ await expect(args.onClick).not.toHaveBeenCalled();
},
-}
+};
export const HoverInteraction: Story = {
args: {
@@ -219,19 +219,19 @@ export const HoverInteraction: Story = {
children: 'Hover Over Me',
},
play: async ({ canvasElement }) => {
- const canvas = within(canvasElement)
- const button = canvas.getByRole('button', { name: /hover over me/i })
+ const canvas = within(canvasElement);
+ const button = canvas.getByRole('button', { name: /hover over me/i });
// Test initial state
- await expect(button).toBeVisible()
+ await expect(button).toBeVisible();
// Hover over the button
- await userEvent.hover(button)
+ await userEvent.hover(button);
// The button should still be visible after hover
- await expect(button).toBeVisible()
+ await expect(button).toBeVisible();
// Unhover
- await userEvent.unhover(button)
+ await userEvent.unhover(button);
},
-}
+};
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index 55535e25d..e0175999a 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -12,70 +12,70 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react";
-import { Slot } from "@radix-ui/react-slot";
-import { cva, type VariantProps } from "class-variance-authority";
+import { Slot } from '@radix-ui/react-slot';
+import { cva, type VariantProps } from 'class-variance-authority';
+import * as React from 'react';
-import { cn } from "@/lib/utils";
+import { cn } from '@/lib/utils';
const buttonVariants = cva(
- "inline-flex items-center justify-center whitespace-nowrap transition-all duration-200 ease-in-out disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
- {
- variants: {
- variant: {
- primary:
- "bg-button-primary-fill-default !text-button-primary-text-default font-bold rounded-xs shadow-button-shadow hover:bg-button-primary-fill-hover active:bg-button-primary-fill-active focus:bg-button-primary-fill-hover focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer ",
- secondary:
- "bg-button-secondary-fill-default !text-button-secondary-text-default font-bold rounded-xs shadow-button-shadow hover:bg-button-secondary-fill-hover active:bg-button-secondary-fill-active focus:bg-button-secondary-hover focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer",
- outline:
- "bg-button-tertiery-fill-default !text-button-tertiery-text-default font-bold rounded-xs shadow-button-shadow hover:bg-button-tertiery-fill-hover active:bg-button-tertiery-fill-active focus:bg-button-tertiery-hover focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer",
- ghost:
- "bg-button-transparent-fill-default !text-button-transparent-text-default font-bold rounded-xs hover:bg-button-transparent-fill-hover active:bg-button-transparent-fill-active focus:bg-button-transparent-fill-hover focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer",
- success:
- "bg-button-fill-success !text-button-fill-success-foreground font-bold rounded-xs shadow-button-shadow hover:bg-fill-fill-success-hover active:bg-fill-fill-success-active focus:bg-fill-fill-success-hover focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer",
- cuation:
- "bg-button-fill-cuation !text-button-fill-cuation-foreground font-bold rounded-xs shadow-button-shadow focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer",
- information:
- "bg-button-fill-information !text-button-fill-information-foreground font-bold rounded-xs shadow-button-shadow focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer",
- warning:
- "bg-button-fill-warning !text-button-fill-warning-foreground font-bold rounded-xs shadow-button-shadow focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer",
- },
- size: {
- xxs: "inline-flex justify-start items-center gap-1 px-1 py-0.5 rounded-md text-label-xs font-bold [&_svg]:size-16",
- xs: "inline-flex justify-start items-center gap-1 px-2 py-1 rounded-md text-label-xs font-bold [&_svg]:size-10",
- sm: "inline-flex justify-start items-center gap-1 px-2 py-1 rounded-md text-label-sm font-medium [&_svg]:size-[16px]",
- md: "inline-flex justify-start items-center gap-2 px-4 py-2 rounded-md text-label-md font-medium [&_svg]:size-[24px]",
- lg: "inline-flex justify-start items-center gap-sm px-4 py-2 rounded-md text-label-lg font-bold [&_svg]:size-[24px]",
- icon: "inline-flex justify-start items-center gap-1 px-1 py-1 rounded-md [&_svg]:size-10",
- },
- },
- defaultVariants: {
- variant: "primary",
- size: "md",
- },
- }
+ "inline-flex items-center justify-center whitespace-nowrap transition-all duration-200 ease-in-out disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ primary:
+ 'bg-button-primary-fill-default !text-button-primary-text-default font-bold rounded-xs shadow-button-shadow hover:bg-button-primary-fill-hover active:bg-button-primary-fill-active focus:bg-button-primary-fill-hover focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer ',
+ secondary:
+ 'bg-button-secondary-fill-default !text-button-secondary-text-default font-bold rounded-xs shadow-button-shadow hover:bg-button-secondary-fill-hover active:bg-button-secondary-fill-active focus:bg-button-secondary-hover focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer',
+ outline:
+ 'bg-button-tertiery-fill-default !text-button-tertiery-text-default font-bold rounded-xs shadow-button-shadow hover:bg-button-tertiery-fill-hover active:bg-button-tertiery-fill-active focus:bg-button-tertiery-hover focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer',
+ ghost:
+ 'bg-button-transparent-fill-default !text-button-transparent-text-default font-bold rounded-xs hover:bg-button-transparent-fill-hover active:bg-button-transparent-fill-active focus:bg-button-transparent-fill-hover focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer',
+ success:
+ 'bg-button-fill-success !text-button-fill-success-foreground font-bold rounded-xs shadow-button-shadow hover:bg-fill-fill-success-hover active:bg-fill-fill-success-active focus:bg-fill-fill-success-hover focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer',
+ cuation:
+ 'bg-button-fill-cuation !text-button-fill-cuation-foreground font-bold rounded-xs shadow-button-shadow focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer',
+ information:
+ 'bg-button-fill-information !text-button-fill-information-foreground font-bold rounded-xs shadow-button-shadow focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer',
+ warning:
+ 'bg-button-fill-warning !text-button-fill-warning-foreground font-bold rounded-xs shadow-button-shadow focus:ring-2 focus:ring-gray-4 focus:ring-offset-2 cursor-pointer',
+ },
+ size: {
+ xxs: 'inline-flex justify-start items-center gap-1 px-1 py-0.5 rounded-md text-label-xs font-bold [&_svg]:size-16',
+ xs: 'inline-flex justify-start items-center gap-1 px-2 py-1 rounded-md text-label-xs font-bold [&_svg]:size-10',
+ sm: 'inline-flex justify-start items-center gap-1 px-2 py-1 rounded-md text-label-sm font-medium [&_svg]:size-[16px]',
+ md: 'inline-flex justify-start items-center gap-2 px-4 py-2 rounded-md text-label-md font-medium [&_svg]:size-[24px]',
+ lg: 'inline-flex justify-start items-center gap-sm px-4 py-2 rounded-md text-label-lg font-bold [&_svg]:size-[24px]',
+ icon: 'inline-flex justify-start items-center gap-1 px-1 py-1 rounded-md [&_svg]:size-10',
+ },
+ },
+ defaultVariants: {
+ variant: 'primary',
+ size: 'md',
+ },
+ }
);
const Button = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps<"button"> &
- VariantProps & { asChild?: boolean }
+ HTMLButtonElement,
+ React.ComponentProps<'button'> &
+ VariantProps & { asChild?: boolean }
>(({ className, variant, size, asChild = false, children, ...props }, ref) => {
- const Comp = asChild ? Slot : "button";
+ const Comp = asChild ? Slot : 'button';
- return (
-
- {children}
- {/* {variant === "primary" && } */}
-
- );
+ return (
+
+ {children}
+ {/* {variant === "primary" && } */}
+
+ );
});
-Button.displayName = "Button";
+Button.displayName = 'Button';
export { Button, buttonVariants };
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
index 595232f7f..d8057ffaa 100644
--- a/src/components/ui/card.tsx
+++ b/src/components/ui/card.tsx
@@ -12,9 +12,9 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
const Card = React.forwardRef<
HTMLDivElement,
@@ -23,13 +23,13 @@ const Card = React.forwardRef<
-))
-Card.displayName = "Card"
+));
+Card.displayName = 'Card';
const CardHeader = React.forwardRef<
HTMLDivElement,
@@ -37,11 +37,11 @@ const CardHeader = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-CardHeader.displayName = "CardHeader"
+));
+CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef<
HTMLDivElement,
@@ -49,11 +49,11 @@ const CardTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-CardTitle.displayName = "CardTitle"
+));
+CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef<
HTMLDivElement,
@@ -61,19 +61,19 @@ const CardDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-CardDescription.displayName = "CardDescription"
+));
+CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => (
-
-))
-CardContent.displayName = "CardContent"
+
+));
+CardContent.displayName = 'CardContent';
const CardFooter = React.forwardRef<
HTMLDivElement,
@@ -81,10 +81,17 @@ const CardFooter = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-CardFooter.displayName = "CardFooter"
+));
+CardFooter.displayName = 'CardFooter';
-export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
+export {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+};
diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx
index 0747bab6c..3e405b75b 100644
--- a/src/components/ui/carousel.tsx
+++ b/src/components/ui/carousel.tsx
@@ -12,46 +12,46 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
import useEmblaCarousel, {
type UseEmblaCarouselType,
-} from "embla-carousel-react"
-import { ArrowLeft, ArrowRight } from "lucide-react"
+} from 'embla-carousel-react';
+import { ArrowLeft, ArrowRight } from 'lucide-react';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
+import { Button } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
-type CarouselApi = UseEmblaCarouselType[1]
-type UseCarouselParameters = Parameters
-type CarouselOptions = UseCarouselParameters[0]
-type CarouselPlugin = UseCarouselParameters[1]
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = {
- opts?: CarouselOptions
- plugins?: CarouselPlugin
- orientation?: "horizontal" | "vertical"
- setApi?: (api: CarouselApi) => void
-}
+ opts?: CarouselOptions;
+ plugins?: CarouselPlugin;
+ orientation?: 'horizontal' | 'vertical';
+ setApi?: (api: CarouselApi) => void;
+};
type CarouselContextProps = {
- carouselRef: ReturnType[0]
- api: ReturnType[1]
- scrollPrev: () => void
- scrollNext: () => void
- canScrollPrev: boolean
- canScrollNext: boolean
-} & CarouselProps
+ carouselRef: ReturnType[0];
+ api: ReturnType[1];
+ scrollPrev: () => void;
+ scrollNext: () => void;
+ canScrollPrev: boolean;
+ canScrollNext: boolean;
+} & CarouselProps;
-const CarouselContext = React.createContext(null)
+const CarouselContext = React.createContext(null);
function useCarousel() {
- const context = React.useContext(CarouselContext)
+ const context = React.useContext(CarouselContext);
if (!context) {
- throw new Error("useCarousel must be used within a ")
+ throw new Error('useCarousel must be used within a ');
}
- return context
+ return context;
}
const Carousel = React.forwardRef<
@@ -60,7 +60,7 @@ const Carousel = React.forwardRef<
>(
(
{
- orientation = "horizontal",
+ orientation = 'horizontal',
opts,
setApi,
plugins,
@@ -73,64 +73,64 @@ const Carousel = React.forwardRef<
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
- axis: orientation === "horizontal" ? "x" : "y",
+ axis: orientation === 'horizontal' ? 'x' : 'y',
},
plugins
- )
- const [canScrollPrev, setCanScrollPrev] = React.useState(false)
- const [canScrollNext, setCanScrollNext] = React.useState(false)
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
- return
+ return;
}
- setCanScrollPrev(api.canScrollPrev())
- setCanScrollNext(api.canScrollNext())
- }, [])
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
const scrollPrev = React.useCallback(() => {
- api?.scrollPrev()
- }, [api])
+ api?.scrollPrev();
+ }, [api]);
const scrollNext = React.useCallback(() => {
- api?.scrollNext()
- }, [api])
+ api?.scrollNext();
+ }, [api]);
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent) => {
- if (event.key === "ArrowLeft") {
- event.preventDefault()
- scrollPrev()
- } else if (event.key === "ArrowRight") {
- event.preventDefault()
- scrollNext()
+ if (event.key === 'ArrowLeft') {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === 'ArrowRight') {
+ event.preventDefault();
+ scrollNext();
}
},
[scrollPrev, scrollNext]
- )
+ );
React.useEffect(() => {
if (!api || !setApi) {
- return
+ return;
}
- setApi(api)
- }, [api, setApi])
+ setApi(api);
+ }, [api, setApi]);
React.useEffect(() => {
if (!api) {
- return
+ return;
}
- onSelect(api)
- api.on("reInit", onSelect)
- api.on("select", onSelect)
+ onSelect(api);
+ api.on('reInit', onSelect);
+ api.on('select', onSelect);
return () => {
- api?.off("select", onSelect)
- }
- }, [api, onSelect])
+ api?.off('select', onSelect);
+ };
+ }, [api, onSelect]);
return (
- )
+ );
}
-)
-Carousel.displayName = "Carousel"
+);
+Carousel.displayName = 'Carousel';
const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => {
- const { carouselRef, orientation } = useCarousel()
+ const { carouselRef, orientation } = useCarousel();
return (
-
+
- )
-})
-CarouselContent.displayName = "CarouselContent"
+ );
+});
+CarouselContent.displayName = 'CarouselContent';
const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => {
- const { orientation } = useCarousel()
+ const { orientation } = useCarousel();
return (
- )
-})
-CarouselItem.displayName = "CarouselItem"
+ );
+});
+CarouselItem.displayName = 'CarouselItem';
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps
->(({ className, variant = "outline", size = "icon", ...props }, ref) => {
- const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return (
Previous slide
- )
-})
-CarouselPrevious.displayName = "CarouselPrevious"
+ );
+});
+CarouselPrevious.displayName = 'CarouselPrevious';
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps
->(({ className, variant = "outline", size = "icon", ...props }, ref) => {
- const { orientation, scrollNext, canScrollNext } = useCarousel()
+>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
return (
Next slide
- )
-})
-CarouselNext.displayName = "CarouselNext"
+ );
+});
+CarouselNext.displayName = 'CarouselNext';
export {
- type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
- CarouselPrevious,
CarouselNext,
-}
+ CarouselPrevious,
+ type CarouselApi,
+};
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
index 658532122..a828027fb 100644
--- a/src/components/ui/command.tsx
+++ b/src/components/ui/command.tsx
@@ -12,154 +12,157 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react";
-import { type DialogProps } from "@radix-ui/react-dialog";
-import { Command as CommandPrimitive } from "cmdk";
-import { Search } from "lucide-react";
+import { type DialogProps } from '@radix-ui/react-dialog';
+import { Command as CommandPrimitive } from 'cmdk';
+import { Search } from 'lucide-react';
+import * as React from 'react';
-import { cn } from "@/lib/utils";
-import { Dialog, DialogContent } from "@/components/ui/dialog";
+import { Dialog, DialogContent } from '@/components/ui/dialog';
+import { cn } from '@/lib/utils';
const Command = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
));
Command.displayName = CommandPrimitive.displayName;
const CommandDialog = ({ children, ...props }: DialogProps) => {
- return (
-
- );
+ return (
+
+ );
};
const CommandInput = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-
-
-
+
+
+
+
));
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
));
CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>((props, ref) => (
-
+
));
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
));
CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
));
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({
- className,
- ...props
+ className,
+ ...props
}: React.HTMLAttributes) => {
- return (
-
- );
+ return (
+
+ );
};
-CommandShortcut.displayName = "CommandShortcut";
+CommandShortcut.displayName = 'CommandShortcut';
export {
- Command,
- CommandDialog,
- CommandInput,
- CommandList,
- CommandEmpty,
- CommandGroup,
- CommandItem,
- CommandShortcut,
- CommandSeparator,
+ Command,
+ CommandDialog,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+ CommandShortcut,
};
diff --git a/src/components/ui/dialog.stories.tsx b/src/components/ui/dialog.stories.tsx
index 71a626ee6..0e8b7eca4 100644
--- a/src/components/ui/dialog.stories.tsx
+++ b/src/components/ui/dialog.stories.tsx
@@ -12,43 +12,46 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import type { Meta, StoryObj } from '@storybook/react-vite'
-import { useState } from 'react'
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { useState } from 'react';
+import { expect, userEvent, within } from 'storybook/test';
+import { Button } from './button';
import {
Dialog,
- DialogTrigger,
DialogContent,
- DialogHeader,
DialogContentSection,
DialogFooter,
-} from './dialog'
-import { Button } from './button'
-import { Input } from './input'
-import { expect, userEvent, within } from 'storybook/test'
+ DialogHeader,
+ DialogTrigger,
+} from './dialog';
+import { Input } from './input';
const meta: Meta = {
title: 'UI/Dialog',
component: Dialog,
-}
+};
-export default meta
+export default meta;
-type Story = StoryObj
+type Story = StoryObj;
export const Default: Story = {
render: function DefaultDialog() {
- const [open, setOpen] = useState(false)
+ const [open, setOpen] = useState(false);
return (
- )
+ );
},
-}
+};
export const SmallSize: Story = {
render: function SmallDialog() {
- const [open, setOpen] = useState(false)
+ const [open, setOpen] = useState(false);
return (
- )
+ );
},
-}
+};
export const LargeSize: Story = {
render: function LargeDialog() {
- const [open, setOpen] = useState(false)
+ const [open, setOpen] = useState(false);
return (
- )
+ );
},
-}
+};
export const WithForm: Story = {
render: function FormDialog() {
- const [open, setOpen] = useState(false)
+ const [open, setOpen] = useState(false);
return (
),
-}
+};
export const FormExample: Story = {
render: () => (
@@ -201,7 +201,7 @@ export const FormExample: Story = {
),
],
-}
+};
// Interaction test stories
export const TypeInteraction: Story = {
@@ -210,24 +210,24 @@ export const TypeInteraction: Story = {
onChange: fn(),
},
play: async ({ args, canvasElement }) => {
- const canvas = within(canvasElement)
- const input = canvas.getByPlaceholderText('Type something...')
+ const canvas = within(canvasElement);
+ const input = canvas.getByPlaceholderText('Type something...');
// Test that input is visible and enabled
- await expect(input).toBeVisible()
- await expect(input).toBeEnabled()
+ await expect(input).toBeVisible();
+ await expect(input).toBeEnabled();
// Clear any existing value and type new text
- await userEvent.clear(input)
- await userEvent.type(input, 'Hello World')
+ await userEvent.clear(input);
+ await userEvent.type(input, 'Hello World');
// Verify the input value
- await expect(input).toHaveValue('Hello World')
+ await expect(input).toHaveValue('Hello World');
// Verify onChange was called
- await expect(args.onChange).toHaveBeenCalled()
+ await expect(args.onChange).toHaveBeenCalled();
},
-}
+};
export const FocusInteraction: Story = {
args: {
@@ -237,18 +237,18 @@ export const FocusInteraction: Story = {
onBlur: fn(),
},
play: async ({ args, canvasElement }) => {
- const canvas = within(canvasElement)
- const input = canvas.getByPlaceholderText('Click to focus...')
+ const canvas = within(canvasElement);
+ const input = canvas.getByPlaceholderText('Click to focus...');
// Click to focus the input
- await userEvent.click(input)
- await expect(args.onFocus).toHaveBeenCalled()
+ await userEvent.click(input);
+ await expect(args.onFocus).toHaveBeenCalled();
// Tab away to blur
- await userEvent.tab()
- await expect(args.onBlur).toHaveBeenCalled()
+ await userEvent.tab();
+ await expect(args.onBlur).toHaveBeenCalled();
},
-}
+};
export const ClearAndTypeInteraction: Story = {
args: {
@@ -257,24 +257,24 @@ export const ClearAndTypeInteraction: Story = {
defaultValue: 'Initial value',
},
play: async ({ canvasElement }) => {
- const canvas = within(canvasElement)
- const input = canvas.getByPlaceholderText('Edit this text')
+ const canvas = within(canvasElement);
+ const input = canvas.getByPlaceholderText('Edit this text');
// Verify initial value
- await expect(input).toHaveValue('Initial value')
+ await expect(input).toHaveValue('Initial value');
// Select all and replace
- await userEvent.tripleClick(input)
- await userEvent.type(input, 'Replaced text')
+ await userEvent.tripleClick(input);
+ await userEvent.type(input, 'Replaced text');
// Verify the new value
- await expect(input).toHaveValue('Replaced text')
+ await expect(input).toHaveValue('Replaced text');
},
-}
+};
export const PasswordToggleInteraction: Story = {
render: function PasswordToggle() {
- const [showPassword, setShowPassword] = useState(false)
+ const [showPassword, setShowPassword] = useState(false);
return (
: }
onBackIconClick={() => setShowPassword(!showPassword)}
/>
- )
+ );
},
play: async ({ canvasElement }) => {
- const canvas = within(canvasElement)
- const input = canvas.getByPlaceholderText('Enter password')
- const toggleButton = canvas.getByRole('button')
+ const canvas = within(canvasElement);
+ const input = canvas.getByPlaceholderText('Enter password');
+ const toggleButton = canvas.getByRole('button');
// Initially password should be hidden (type="password")
- await expect(input).toHaveAttribute('type', 'password')
+ await expect(input).toHaveAttribute('type', 'password');
// Click toggle to show password
- await userEvent.click(toggleButton)
- await expect(input).toHaveAttribute('type', 'text')
+ await userEvent.click(toggleButton);
+ await expect(input).toHaveAttribute('type', 'text');
// Click toggle again to hide password
- await userEvent.click(toggleButton)
- await expect(input).toHaveAttribute('type', 'password')
+ await userEvent.click(toggleButton);
+ await expect(input).toHaveAttribute('type', 'password');
},
-}
+};
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
index cd81d39d0..58254b8a1 100644
--- a/src/components/ui/input.tsx
+++ b/src/components/ui/input.tsx
@@ -12,82 +12,88 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
+import * as React from 'react';
-import { cn } from "@/lib/utils"
-import { Button } from "./button"
-import { TooltipSimple } from "./tooltip"
-import { CircleAlert } from "lucide-react"
+import { cn } from '@/lib/utils';
+import { CircleAlert } from 'lucide-react';
+import { Button } from './button';
+import { TooltipSimple } from './tooltip';
-export type InputSize = "default" | "sm"
-export type InputState = "default" | "hover" | "input" | "error" | "success" | "disabled"
+export type InputSize = 'default' | 'sm';
+export type InputState =
+ | 'default'
+ | 'hover'
+ | 'input'
+ | 'error'
+ | 'success'
+ | 'disabled';
-type BaseInputProps = Omit, "size"> & {
- size?: InputSize
- state?: InputState
- title?: string
- tooltip?: string
- note?: string
- required?: boolean
- leadingIcon?: React.ReactNode
- backIcon?: React.ReactNode
- onBackIconClick?: () => void
- trailingButton?: React.ReactNode
- onEnter?: () => void
-}
+type BaseInputProps = Omit, 'size'> & {
+ size?: InputSize;
+ state?: InputState;
+ title?: string;
+ tooltip?: string;
+ note?: string;
+ required?: boolean;
+ leadingIcon?: React.ReactNode;
+ backIcon?: React.ReactNode;
+ onBackIconClick?: () => void;
+ trailingButton?: React.ReactNode;
+ onEnter?: () => void;
+};
const sizeClasses: Record = {
- default: "h-10 text-body-sm md:text-sm",
- sm: "h-8 text-body-sm",
-}
+ default: 'h-10 text-body-sm md:text-sm',
+ sm: 'h-8 text-body-sm',
+};
function resolveStateClasses(state: InputState | undefined) {
- if (state === "disabled") {
+ if (state === 'disabled') {
return {
- container: "opacity-50 cursor-not-allowed",
- field: "border-input-border-default bg-input-bg-default",
- input: "text-text-heading",
- placeholder: "placeholder-input-label-default",
- }
+ container: 'opacity-50 cursor-not-allowed',
+ field: 'border-input-border-default bg-input-bg-default',
+ input: 'text-text-heading',
+ placeholder: 'placeholder-input-label-default',
+ };
}
- if (state === "hover") {
+ if (state === 'hover') {
return {
- container: "",
- field: "border-input-border-hover bg-input-bg-default",
- input: "text-text-heading",
- placeholder: "placeholder-input-label-default",
- }
+ container: '',
+ field: 'border-input-border-hover bg-input-bg-default',
+ input: 'text-text-heading',
+ placeholder: 'placeholder-input-label-default',
+ };
}
- if (state === "input") {
+ if (state === 'input') {
return {
- container: "",
- field: "border-input-border-focus bg-input-bg-input",
- input: "text-text-heading",
- placeholder: "placeholder-input-label-default",
- }
+ container: '',
+ field: 'border-input-border-focus bg-input-bg-input',
+ input: 'text-text-heading',
+ placeholder: 'placeholder-input-label-default',
+ };
}
- if (state === "error") {
+ if (state === 'error') {
return {
- container: "",
- field: "border-input-border-cuation bg-input-bg-default",
- input: "text-text-heading",
- placeholder: "placeholder-input-label-default",
- }
+ container: '',
+ field: 'border-input-border-cuation bg-input-bg-default',
+ input: 'text-text-heading',
+ placeholder: 'placeholder-input-label-default',
+ };
}
- if (state === "success") {
+ if (state === 'success') {
return {
- container: "",
- field: "border-input-border-success bg-input-bg-confirm",
- input: "text-text-heading",
- placeholder: "placeholder-input-label-default",
- }
+ container: '',
+ field: 'border-input-border-success bg-input-bg-confirm',
+ input: 'text-text-heading',
+ placeholder: 'placeholder-input-label-default',
+ };
}
return {
- container: "",
- field: "border-input-border-default bg-input-bg-default",
- input: "text-text-heading",
- placeholder: "placeholder-input-label-default/10",
- }
+ container: '',
+ field: 'border-input-border-default bg-input-bg-default',
+ input: 'text-text-heading',
+ placeholder: 'placeholder-input-label-default/10',
+ };
}
const Input = React.forwardRef(
@@ -95,8 +101,8 @@ const Input = React.forwardRef(
{
className,
type,
- size = "default",
- state = "default",
+ size = 'default',
+ state = 'default',
title,
tooltip,
note,
@@ -112,13 +118,13 @@ const Input = React.forwardRef(
},
ref
) => {
- const { onKeyDown, ...inputProps } = props
- const stateCls = resolveStateClasses(disabled ? "disabled" : state)
- const hasLeft = Boolean(leadingIcon)
- const hasRight = Boolean(backIcon) || Boolean(trailingButton)
+ const { onKeyDown, ...inputProps } = props;
+ const stateCls = resolveStateClasses(disabled ? 'disabled' : state);
+ const hasLeft = Boolean(leadingIcon);
+ const hasRight = Boolean(backIcon) || Boolean(trailingButton);
return (
-
+
{title ? (
{title}
@@ -133,10 +139,10 @@ const Input = React.forwardRef
(
(
disabled={disabled}
placeholder={placeholder}
className={cn(
- "peer w-full bg-transparent outline-none placeholder:transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium",
+ 'peer w-full bg-transparent outline-none file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:transition-colors',
stateCls.input,
stateCls.placeholder,
- hasLeft ? "pl-9" : "pl-3",
- hasRight ? "pr-9" : "pr-3",
+ hasLeft ? 'pl-9' : 'pl-3',
+ hasRight ? 'pr-9' : 'pr-3',
className
)}
onKeyDown={(e) => {
- if (e.key === "Enter") {
- onEnter?.()
+ if (e.key === 'Enter') {
+ onEnter?.();
}
- onKeyDown?.(e)
+ onKeyDown?.(e);
}}
{...inputProps}
/>
@@ -175,7 +181,7 @@ const Input = React.forwardRef
(
variant="ghost"
size="icon"
tabIndex={-1}
- className="absolute right-2 inline-flex h-5 w-5 items-center justify-center text-icon-primary disabled:opacity-50 rounded-full focus:ring-0"
+ className="absolute right-2 inline-flex h-5 w-5 items-center justify-center rounded-full text-icon-primary focus:ring-0 disabled:opacity-50"
disabled={disabled}
onClick={onBackIconClick}
>
@@ -184,32 +190,34 @@ const Input = React.forwardRef(
) : null}
{trailingButton ? (
- {trailingButton}
+
+ {trailingButton}
+
) : null}
{note ? (
$1'
- )
+ ),
}}
/>
) : null}
- )
+ );
}
-)
-Input.displayName = "Input"
+);
+Input.displayName = 'Input';
-export { Input }
+export { Input };
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
index e3bcc224f..551a53d4d 100644
--- a/src/components/ui/label.tsx
+++ b/src/components/ui/label.tsx
@@ -12,15 +12,15 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
-import * as LabelPrimitive from "@radix-ui/react-label"
-import { cva, type VariantProps } from "class-variance-authority"
+import * as LabelPrimitive from '@radix-ui/react-label';
+import { cva, type VariantProps } from 'class-variance-authority';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
const labelVariants = cva(
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
-)
+ 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
+);
const Label = React.forwardRef<
React.ElementRef,
@@ -32,7 +32,7 @@ const Label = React.forwardRef<
className={cn(labelVariants(), className)}
{...props}
/>
-))
-Label.displayName = LabelPrimitive.Root.displayName
+));
+Label.displayName = LabelPrimitive.Root.displayName;
-export { Label }
+export { Label };
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
index 3c938af52..5e1200c5a 100644
--- a/src/components/ui/popover.tsx
+++ b/src/components/ui/popover.tsx
@@ -12,36 +12,36 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
-import * as PopoverPrimitive from "@radix-ui/react-popover"
+import * as PopoverPrimitive from '@radix-ui/react-popover';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
-const Popover = PopoverPrimitive.Root
+const Popover = PopoverPrimitive.Root;
-const PopoverTrigger = PopoverPrimitive.Trigger
+const PopoverTrigger = PopoverPrimitive.Trigger;
-const PopoverAnchor = PopoverPrimitive.Anchor
+const PopoverAnchor = PopoverPrimitive.Anchor;
-const PopoverClose = PopoverPrimitive.Close
+const PopoverClose = PopoverPrimitive.Close;
const PopoverContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
->(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
-))
-PopoverContent.displayName = PopoverPrimitive.Content.displayName
+));
+PopoverContent.displayName = PopoverPrimitive.Content.displayName;
-export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverClose }
+export { Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger };
diff --git a/src/components/ui/progress-install.tsx b/src/components/ui/progress-install.tsx
index 941f06ef1..eea19e9e9 100644
--- a/src/components/ui/progress-install.tsx
+++ b/src/components/ui/progress-install.tsx
@@ -12,32 +12,32 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react";
-import * as ProgressPrimitive from "@radix-ui/react-progress";
+import * as ProgressPrimitive from '@radix-ui/react-progress';
+import * as React from 'react';
-import { cn } from "@/lib/utils";
+import { cn } from '@/lib/utils';
const ProgressInstall = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, value, ...props }, ref) => (
-
-
-
+
+
+
));
ProgressInstall.displayName = ProgressPrimitive.Root.displayName;
diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx
index 949e4ca05..1ed14443f 100644
--- a/src/components/ui/progress.tsx
+++ b/src/components/ui/progress.tsx
@@ -12,10 +12,10 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
-import * as ProgressPrimitive from "@radix-ui/react-progress"
+import * as ProgressPrimitive from '@radix-ui/react-progress';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
const Progress = React.forwardRef<
React.ElementRef,
@@ -24,17 +24,17 @@ const Progress = React.forwardRef<
-))
-Progress.displayName = ProgressPrimitive.Root.displayName
+));
+Progress.displayName = ProgressPrimitive.Root.displayName;
-export { Progress }
+export { Progress };
diff --git a/src/components/ui/resizable.tsx b/src/components/ui/resizable.tsx
index 4df96f0df..27557997e 100644
--- a/src/components/ui/resizable.tsx
+++ b/src/components/ui/resizable.tsx
@@ -12,13 +12,13 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-"use client"
+'use client';
-import * as React from "react"
-import { GripVerticalIcon } from "lucide-react"
-import * as ResizablePrimitive from "react-resizable-panels"
+import { GripVerticalIcon } from 'lucide-react';
+import * as React from 'react';
+import * as ResizablePrimitive from 'react-resizable-panels';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
function ResizablePanelGroup({
className,
@@ -28,18 +28,18 @@ function ResizablePanelGroup({
- )
+ );
}
function ResizablePanel({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function ResizableHandle({
@@ -47,24 +47,24 @@ function ResizableHandle({
className,
...props
}: React.ComponentProps & {
- withHandle?: boolean
+ withHandle?: boolean;
}) {
return (
div]:rotate-90",
+ 'bg-border focus-visible:ring-ring focus-visible:outline-hidden relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90',
className
)}
{...props}
>
{withHandle && (
-
+
)}
- )
+ );
}
-export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
+export { ResizableHandle, ResizablePanel, ResizablePanelGroup };
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
index 111938dba..d7a0af62f 100644
--- a/src/components/ui/select.tsx
+++ b/src/components/ui/select.tsx
@@ -12,111 +12,130 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
-import * as SelectPrimitive from "@radix-ui/react-select"
-import { Check, ChevronDown, ChevronUp } from "lucide-react"
+import * as SelectPrimitive from '@radix-ui/react-select';
+import { Check, ChevronDown, ChevronUp } from 'lucide-react';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
+import { Button } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
-export type SelectSize = "default" | "sm"
+export type SelectSize = 'default' | 'sm';
// Only keep controllable states; hover/focus/default are automatic
-export type SelectState = "error" | "success"
+export type SelectState = 'error' | 'success';
-const Select = SelectPrimitive.Root
+const Select = SelectPrimitive.Root;
-const SelectGroup = SelectPrimitive.Group
+const SelectGroup = SelectPrimitive.Group;
-const SelectValue = SelectPrimitive.Value
+const SelectValue = SelectPrimitive.Value;
// Local copies to mirror Input behavior for size/state without importing internal helpers
const sizeClasses: Record
= {
- default: "h-10 text-body-sm",
- sm: "h-8 text-body-sm",
-}
+ default: 'h-10 text-body-sm',
+ sm: 'h-8 text-body-sm',
+};
-function resolveStateClasses(state: SelectState | undefined, disabled: boolean) {
+function resolveStateClasses(
+ state: SelectState | undefined,
+ disabled: boolean
+) {
if (disabled) {
return {
- wrapper: "opacity-50 cursor-not-allowed",
- note: "text-text-label",
- }
+ wrapper: 'opacity-50 cursor-not-allowed',
+ note: 'text-text-label',
+ };
}
- if (state === "error") {
+ if (state === 'error') {
return {
- wrapper: "",
- trigger: "border-input-border-caution bg-input-bg-default",
- note: "text-text-caution",
- }
+ wrapper: '',
+ trigger: 'border-input-border-caution bg-input-bg-default',
+ note: 'text-text-caution',
+ };
}
- if (state === "success") {
+ if (state === 'success') {
return {
- wrapper: "",
- trigger: "border-input-border-success bg-input-bg-confirm",
- note: "text-text-success",
- }
+ wrapper: '',
+ trigger: 'border-input-border-success bg-input-bg-confirm',
+ note: 'text-text-success',
+ };
}
return {
- wrapper: "",
- trigger: "",
- note: "text-text-label",
- }
+ wrapper: '',
+ trigger: '',
+ note: 'text-text-label',
+ };
}
type SelectTriggerExtraProps = {
- size?: SelectSize
- state?: SelectState
- title?: string
- note?: string
-}
+ size?: SelectSize;
+ state?: SelectState;
+ title?: string;
+ note?: string;
+};
const SelectTrigger = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef &
SelectTriggerExtraProps
->(({ className, children, size = "default", state, title, note, disabled, ...props }, ref) => {
- const stateCls = resolveStateClasses(state, Boolean(disabled))
- return (
-
- {title ? (
-
{title}
- ) : null}
-
span]:line-clamp-1 whitespace-nowrap",
- // Default state (when no error/success)
- !state && "border-input-border-default bg-input-bg-default",
- // Interactive states (only when no error state)
- state !== "error" && [
- "hover:bg-input-bg-hover hover:border-input-border-hover",
- "focus-visible:ring-0 data-[state=open]:bg-input-bg-input",
- "focus-within:border-input-border-focus",
- ],
- // Validation states (override defaults)
- stateCls.trigger,
- // Placeholder styling
- "data-[placeholder]:text-input-label-default/50",
- className
- )}
- {...props}
- >
- {children}
-
-
-
-
- {note ? (
-
{note}
- ) : null}
-
- )
-})
-SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+>(
+ (
+ {
+ className,
+ children,
+ size = 'default',
+ state,
+ title,
+ note,
+ disabled,
+ ...props
+ },
+ ref
+ ) => {
+ const stateCls = resolveStateClasses(state, Boolean(disabled));
+ return (
+
+ {title ? (
+
+ {title}
+
+ ) : null}
+
span]:line-clamp-1',
+ // Default state (when no error/success)
+ !state && 'border-input-border-default bg-input-bg-default',
+ // Interactive states (only when no error state)
+ state !== 'error' && [
+ 'hover:border-input-border-hover hover:bg-input-bg-hover',
+ 'focus-visible:ring-0 data-[state=open]:bg-input-bg-input',
+ 'focus-within:border-input-border-focus',
+ ],
+ // Validation states (override defaults)
+ stateCls.trigger,
+ // Placeholder styling
+ 'data-[placeholder]:text-input-label-default/50',
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+
+ {note ? (
+
{note}
+ ) : null}
+
+ );
+ }
+);
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef<
React.ElementRef,
@@ -125,15 +144,15 @@ const SelectScrollUpButton = React.forwardRef<
-))
-SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+));
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef<
React.ElementRef,
@@ -142,28 +161,28 @@ const SelectScrollDownButton = React.forwardRef<
-))
+));
SelectScrollDownButton.displayName =
- SelectPrimitive.ScrollDownButton.displayName
+ SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
->(({ className, children, position = "popper", ...props }, ref) => (
+>(({ className, children, position = 'popper', ...props }, ref) => (
{children}
@@ -182,8 +201,8 @@ const SelectContent = React.forwardRef<
-))
-SelectContent.displayName = SelectPrimitive.Content.displayName
+));
+SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef,
@@ -191,11 +210,11 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-SelectLabel.displayName = SelectPrimitive.Label.displayName
+));
+SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef,
@@ -204,7 +223,7 @@ const SelectItem = React.forwardRef<
{children}
-))
-SelectItem.displayName = SelectPrimitive.Item.displayName
+));
+SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef,
@@ -225,71 +244,84 @@ const SelectSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+));
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
type SelectItemWithButtonProps = {
- value: string
- label: React.ReactNode
- enabled: boolean
- buttonText?: string
- onButtonClick?: (e: React.MouseEvent) => void
- className?: string
-}
+ value: string;
+ label: React.ReactNode;
+ enabled: boolean;
+ buttonText?: string;
+ onButtonClick?: (e: React.MouseEvent) => void;
+ className?: string;
+};
const SelectItemWithButton = React.forwardRef<
React.ElementRef,
SelectItemWithButtonProps
->(({ value, label, enabled, buttonText = "Setting", onButtonClick, className, ...props }, ref) => (
-
-
-
-
-
-
-
-
{label}
- {!enabled && onButtonClick && (
-
{
- e.preventDefault();
- e.stopPropagation();
- onButtonClick(e);
- }}
- >
- {buttonText}
-
+>(
+ (
+ {
+ value,
+ label,
+ enabled,
+ buttonText = 'Setting',
+ onButtonClick,
+ className,
+ ...props
+ },
+ ref
+ ) => (
+
-
-))
-SelectItemWithButton.displayName = "SelectItemWithButton"
+ {...props}
+ >
+
+
+
+
+
+
+ {label}
+ {!enabled && onButtonClick && (
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ onButtonClick(e);
+ }}
+ >
+ {buttonText}
+
+ )}
+
+
+ )
+);
+SelectItemWithButton.displayName = 'SelectItemWithButton';
export {
Select,
- SelectGroup,
- SelectValue,
- SelectTrigger,
SelectContent,
- SelectLabel,
+ SelectGroup,
SelectItem,
SelectItemWithButton,
- SelectSeparator,
- SelectScrollUpButton,
+ SelectLabel,
SelectScrollDownButton,
-}
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+};
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
index 914e0ca54..c46e47767 100644
--- a/src/components/ui/separator.tsx
+++ b/src/components/ui/separator.tsx
@@ -12,17 +12,17 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
-import * as SeparatorPrimitive from "@radix-ui/react-separator"
+import * as SeparatorPrimitive from '@radix-ui/react-separator';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
const Separator = React.forwardRef<
React.ElementRef
,
React.ComponentPropsWithoutRef
>(
(
- { className, orientation = "horizontal", decorative = true, ...props },
+ { className, orientation = 'horizontal', decorative = true, ...props },
ref
) => (
)
-)
-Separator.displayName = SeparatorPrimitive.Root.displayName
+);
+Separator.displayName = SeparatorPrimitive.Root.displayName;
-export { Separator }
+export { Separator };
diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx
index de0413ac4..e86cef4f3 100644
--- a/src/components/ui/sheet.tsx
+++ b/src/components/ui/sheet.tsx
@@ -12,22 +12,22 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-"use client"
+'use client';
-import * as React from "react"
-import * as SheetPrimitive from "@radix-ui/react-dialog"
-import { cva, type VariantProps } from "class-variance-authority"
-import { X } from "lucide-react"
+import * as SheetPrimitive from '@radix-ui/react-dialog';
+import { cva, type VariantProps } from 'class-variance-authority';
+import { X } from 'lucide-react';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
-const Sheet = SheetPrimitive.Root
+const Sheet = SheetPrimitive.Root;
-const SheetTrigger = SheetPrimitive.Trigger
+const SheetTrigger = SheetPrimitive.Trigger;
-const SheetClose = SheetPrimitive.Close
+const SheetClose = SheetPrimitive.Close;
-const SheetPortal = SheetPrimitive.Portal
+const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef,
@@ -35,42 +35,43 @@ const SheetOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+));
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
- "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
+ 'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
{
variants: {
side: {
- top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
+ top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
bottom:
- "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
- left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+ 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
+ left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
right:
- "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+ 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
},
},
defaultVariants: {
- side: "right",
+ side: 'right',
},
}
-)
+);
interface SheetContentProps
- extends React.ComponentPropsWithoutRef,
+ extends
+ React.ComponentPropsWithoutRef,
VariantProps {}
const SheetContent = React.forwardRef<
React.ElementRef,
SheetContentProps
->(({ side = "right", className, children, ...props }, ref) => (
+>(({ side = 'right', className, children, ...props }, ref) => (
-
+
Close
{children}
-))
-SheetContent.displayName = SheetPrimitive.Content.displayName
+));
+SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
@@ -94,13 +95,13 @@ const SheetHeader = ({
}: React.HTMLAttributes) => (
-)
-SheetHeader.displayName = "SheetHeader"
+);
+SheetHeader.displayName = 'SheetHeader';
const SheetFooter = ({
className,
@@ -108,13 +109,13 @@ const SheetFooter = ({
}: React.HTMLAttributes) => (
-)
-SheetFooter.displayName = "SheetFooter"
+);
+SheetFooter.displayName = 'SheetFooter';
const SheetTitle = React.forwardRef<
React.ElementRef,
@@ -122,11 +123,11 @@ const SheetTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-SheetTitle.displayName = SheetPrimitive.Title.displayName
+));
+SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef,
@@ -134,21 +135,21 @@ const SheetDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-SheetDescription.displayName = SheetPrimitive.Description.displayName
+));
+SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
- SheetPortal,
- SheetOverlay,
- SheetTrigger,
SheetClose,
SheetContent,
- SheetHeader,
- SheetFooter,
- SheetTitle,
SheetDescription,
-}
+ SheetFooter,
+ SheetHeader,
+ SheetOverlay,
+ SheetPortal,
+ SheetTitle,
+ SheetTrigger,
+};
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
index 49d3c965b..9961f514d 100644
--- a/src/components/ui/sidebar.tsx
+++ b/src/components/ui/sidebar.tsx
@@ -12,65 +12,65 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { VariantProps, cva } from "class-variance-authority"
-import { PanelLeft } from "lucide-react"
+import { Slot } from '@radix-ui/react-slot';
+import { VariantProps, cva } from 'class-variance-authority';
+import { PanelLeft } from 'lucide-react';
+import * as React from 'react';
-import { useIsMobile } from "@/hooks/use-mobile"
-import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Separator } from "@/components/ui/separator"
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Separator } from '@/components/ui/separator';
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
-} from "@/components/ui/sheet"
-import { Skeleton } from "@/components/ui/skeleton"
+} from '@/components/ui/sheet';
+import { Skeleton } from '@/components/ui/skeleton';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
-} from "@/components/ui/tooltip"
+} from '@/components/ui/tooltip';
+import { useIsMobile } from '@/hooks/use-mobile';
+import { cn } from '@/lib/utils';
-const SIDEBAR_COOKIE_NAME = "sidebar_state"
-const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
-const SIDEBAR_WIDTH = "16rem"
-const SIDEBAR_WIDTH_MOBILE = "18rem"
-const SIDEBAR_WIDTH_ICON = "3rem"
-const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+const SIDEBAR_COOKIE_NAME = 'sidebar_state';
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
+const SIDEBAR_WIDTH = '16rem';
+const SIDEBAR_WIDTH_MOBILE = '18rem';
+const SIDEBAR_WIDTH_ICON = '3rem';
+const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
type SidebarContextProps = {
- state: "expanded" | "collapsed"
- open: boolean
- setOpen: (open: boolean) => void
- openMobile: boolean
- setOpenMobile: (open: boolean) => void
- isMobile: boolean
- toggleSidebar: () => void
-}
+ state: 'expanded' | 'collapsed';
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ openMobile: boolean;
+ setOpenMobile: (open: boolean) => void;
+ isMobile: boolean;
+ toggleSidebar: () => void;
+};
-const SidebarContext = React.createContext(null)
+const SidebarContext = React.createContext(null);
function useSidebar() {
- const context = React.useContext(SidebarContext)
+ const context = React.useContext(SidebarContext);
if (!context) {
- throw new Error("useSidebar must be used within a SidebarProvider.")
+ throw new Error('useSidebar must be used within a SidebarProvider.');
}
- return context
+ return context;
}
const SidebarProvider = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div"> & {
- defaultOpen?: boolean
- open?: boolean
- onOpenChange?: (open: boolean) => void
+ React.ComponentProps<'div'> & {
+ defaultOpen?: boolean;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
}
>(
(
@@ -85,34 +85,34 @@ const SidebarProvider = React.forwardRef<
},
ref
) => {
- const isMobile = useIsMobile()
- const [openMobile, setOpenMobile] = React.useState(false)
+ const isMobile = useIsMobile();
+ const [openMobile, setOpenMobile] = React.useState(false);
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
- const [_open, _setOpen] = React.useState(defaultOpen)
- const open = openProp ?? _open
+ const [_open, _setOpen] = React.useState(defaultOpen);
+ const open = openProp ?? _open;
const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
- const openState = typeof value === "function" ? value(open) : value
+ const openState = typeof value === 'function' ? value(open) : value;
if (setOpenProp) {
- setOpenProp(openState)
+ setOpenProp(openState);
} else {
- _setOpen(openState)
+ _setOpen(openState);
}
// This sets the cookie to keep the sidebar state.
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
},
[setOpenProp, open]
- )
+ );
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile
? setOpenMobile((open) => !open)
- : setOpen((open) => !open)
- }, [isMobile, setOpen, setOpenMobile])
+ : setOpen((open) => !open);
+ }, [isMobile, setOpen, setOpenMobile]);
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
@@ -121,18 +121,18 @@ const SidebarProvider = React.forwardRef<
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
- event.preventDefault()
- toggleSidebar()
+ event.preventDefault();
+ toggleSidebar();
}
- }
+ };
- window.addEventListener("keydown", handleKeyDown)
- return () => window.removeEventListener("keydown", handleKeyDown)
- }, [toggleSidebar])
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [toggleSidebar]);
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
- const state = open ? "expanded" : "collapsed"
+ const state = open ? 'expanded' : 'collapsed';
const contextValue = React.useMemo(
() => ({
@@ -145,7 +145,7 @@ const SidebarProvider = React.forwardRef<
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
- )
+ );
return (
@@ -153,13 +153,13 @@ const SidebarProvider = React.forwardRef<
- )
+ );
}
-)
-SidebarProvider.displayName = "SidebarProvider"
+);
+SidebarProvider.displayName = 'SidebarProvider';
const Sidebar = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div"> & {
- side?: "left" | "right"
- variant?: "sidebar" | "floating" | "inset"
- collapsible?: "offcanvas" | "icon" | "none"
+ React.ComponentProps<'div'> & {
+ side?: 'left' | 'right';
+ variant?: 'sidebar' | 'floating' | 'inset';
+ collapsible?: 'offcanvas' | 'icon' | 'none';
}
>(
(
{
- side = "left",
- variant = "sidebar",
- collapsible = "offcanvas",
+ side = 'left',
+ variant = 'sidebar',
+ collapsible = 'offcanvas',
className,
children,
...props
},
ref
) => {
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
- if (collapsible === "none") {
+ if (collapsible === 'none') {
return (
{children}
- )
+ );
}
if (isMobile) {
@@ -216,10 +216,10 @@ const Sidebar = React.forwardRef<
{children}
- )
+ );
}
return (
{/* This is what handles the sidebar gap on desktop */}
- )
+ );
}
-)
-Sidebar.displayName = "Sidebar"
+);
+Sidebar.displayName = 'Sidebar';
const SidebarTrigger = React.forwardRef<
React.ElementRef,
React.ComponentProps
>(({ className, onClick, ...props }, ref) => {
- const { toggleSidebar } = useSidebar()
+ const { toggleSidebar } = useSidebar();
return (
{
- onClick?.(event)
- toggleSidebar()
+ onClick?.(event);
+ toggleSidebar();
}}
{...props}
>
Toggle Sidebar
- )
-})
-SidebarTrigger.displayName = "SidebarTrigger"
+ );
+});
+SidebarTrigger.displayName = 'SidebarTrigger';
const SidebarRail = React.forwardRef<
HTMLButtonElement,
- React.ComponentProps<"button">
+ React.ComponentProps<'button'>
>(({ className, ...props }, ref) => {
- const { toggleSidebar } = useSidebar()
+ const { toggleSidebar } = useSidebar();
return (
- )
-})
-SidebarRail.displayName = "SidebarRail"
+ );
+});
+SidebarRail.displayName = 'SidebarRail';
const SidebarInset = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"main">
+ React.ComponentProps<'main'>
>(({ className, ...props }, ref) => {
return (
- )
-})
-SidebarInset.displayName = "SidebarInset"
+ );
+});
+SidebarInset.displayName = 'SidebarInset';
const SidebarInput = React.forwardRef<
React.ElementRef,
@@ -363,44 +363,44 @@ const SidebarInput = React.forwardRef<
ref={ref}
data-sidebar="input"
className={cn(
- "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
+ 'bg-background focus-visible:ring-sidebar-ring h-8 w-full shadow-none focus-visible:ring-2',
className
)}
{...props}
/>
- )
-})
-SidebarInput.displayName = "SidebarInput"
+ );
+});
+SidebarInput.displayName = 'SidebarInput';
const SidebarHeader = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div">
+ React.ComponentProps<'div'>
>(({ className, ...props }, ref) => {
return (
- )
-})
-SidebarHeader.displayName = "SidebarHeader"
+ );
+});
+SidebarHeader.displayName = 'SidebarHeader';
const SidebarFooter = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div">
+ React.ComponentProps<'div'>
>(({ className, ...props }, ref) => {
return (
- )
-})
-SidebarFooter.displayName = "SidebarFooter"
+ );
+});
+SidebarFooter.displayName = 'SidebarFooter';
const SidebarSeparator = React.forwardRef<
React.ElementRef,
@@ -410,173 +410,173 @@ const SidebarSeparator = React.forwardRef<
- )
-})
-SidebarSeparator.displayName = "SidebarSeparator"
+ );
+});
+SidebarSeparator.displayName = 'SidebarSeparator';
const SidebarContent = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div">
+ React.ComponentProps<'div'>
>(({ className, ...props }, ref) => {
return (
- )
-})
-SidebarContent.displayName = "SidebarContent"
+ );
+});
+SidebarContent.displayName = 'SidebarContent';
const SidebarGroup = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div">
+ React.ComponentProps<'div'>
>(({ className, ...props }, ref) => {
return (
- )
-})
-SidebarGroup.displayName = "SidebarGroup"
+ );
+});
+SidebarGroup.displayName = 'SidebarGroup';
const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div"> & { asChild?: boolean }
+ React.ComponentProps<'div'> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "div"
+ const Comp = asChild ? Slot : 'div';
return (
svg]:size-4 [&>svg]:shrink-0",
- "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
+ 'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-none transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
+ 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
className
)}
{...props}
/>
- )
-})
-SidebarGroupLabel.displayName = "SidebarGroupLabel"
+ );
+});
+SidebarGroupLabel.displayName = 'SidebarGroupLabel';
const SidebarGroupAction = React.forwardRef<
HTMLButtonElement,
- React.ComponentProps<"button"> & { asChild?: boolean }
+ React.ComponentProps<'button'> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button"
+ const Comp = asChild ? Slot : 'button';
return (
svg]:size-4 [&>svg]:shrink-0",
+ 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-none transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
- "after:absolute after:-inset-2 after:md:hidden",
- "group-data-[collapsible=icon]:hidden",
+ 'after:absolute after:-inset-2 after:md:hidden',
+ 'group-data-[collapsible=icon]:hidden',
className
)}
{...props}
/>
- )
-})
-SidebarGroupAction.displayName = "SidebarGroupAction"
+ );
+});
+SidebarGroupAction.displayName = 'SidebarGroupAction';
const SidebarGroupContent = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div">
+ React.ComponentProps<'div'>
>(({ className, ...props }, ref) => (
-))
-SidebarGroupContent.displayName = "SidebarGroupContent"
+));
+SidebarGroupContent.displayName = 'SidebarGroupContent';
const SidebarMenu = React.forwardRef<
HTMLUListElement,
- React.ComponentProps<"ul">
+ React.ComponentProps<'ul'>
>(({ className, ...props }, ref) => (
-))
-SidebarMenu.displayName = "SidebarMenu"
+));
+SidebarMenu.displayName = 'SidebarMenu';
const SidebarMenuItem = React.forwardRef<
HTMLLIElement,
- React.ComponentProps<"li">
+ React.ComponentProps<'li'>
>(({ className, ...props }, ref) => (
-))
-SidebarMenuItem.displayName = "SidebarMenuItem"
+));
+SidebarMenuItem.displayName = 'SidebarMenuItem';
const sidebarMenuButtonVariants = cva(
- "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
{
variants: {
variant: {
- default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+ default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
outline:
- "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+ 'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
},
size: {
- default: "h-8 text-sm",
- sm: "h-7 text-xs",
- lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
+ default: 'h-8 text-sm',
+ sm: 'h-7 text-xs',
+ lg: 'h-12 text-sm group-data-[collapsible=icon]:!p-0',
},
},
defaultVariants: {
- variant: "default",
- size: "default",
+ variant: 'default',
+ size: 'default',
},
}
-)
+);
const SidebarMenuButton = React.forwardRef<
HTMLButtonElement,
- React.ComponentProps<"button"> & {
- asChild?: boolean
- isActive?: boolean
- tooltip?: string | React.ComponentProps
+ React.ComponentProps<'button'> & {
+ asChild?: boolean;
+ isActive?: boolean;
+ tooltip?: string | React.ComponentProps;
} & VariantProps
>(
(
{
asChild = false,
isActive = false,
- variant = "default",
- size = "default",
+ variant = 'default',
+ size = 'default',
tooltip,
className,
...props
},
ref
) => {
- const Comp = asChild ? Slot : "button"
- const { isMobile, state } = useSidebar()
+ const Comp = asChild ? Slot : 'button';
+ const { isMobile, state } = useSidebar();
const button = (
- )
+ );
if (!tooltip) {
- return button
+ return button;
}
- if (typeof tooltip === "string") {
+ if (typeof tooltip === 'string') {
tooltip = {
children: tooltip,
- }
+ };
}
return (
@@ -605,83 +605,83 @@ const SidebarMenuButton = React.forwardRef<
- )
+ );
}
-)
-SidebarMenuButton.displayName = "SidebarMenuButton"
+);
+SidebarMenuButton.displayName = 'SidebarMenuButton';
const SidebarMenuAction = React.forwardRef<
HTMLButtonElement,
- React.ComponentProps<"button"> & {
- asChild?: boolean
- showOnHover?: boolean
+ React.ComponentProps<'button'> & {
+ asChild?: boolean;
+ showOnHover?: boolean;
}
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button"
+ const Comp = asChild ? Slot : 'button';
return (
svg]:size-4 [&>svg]:shrink-0",
+ 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-none transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
- "after:absolute after:-inset-2 after:md:hidden",
- "peer-data-[size=sm]/menu-button:top-1",
- "peer-data-[size=default]/menu-button:top-1.5",
- "peer-data-[size=lg]/menu-button:top-2.5",
- "group-data-[collapsible=icon]:hidden",
+ 'after:absolute after:-inset-2 after:md:hidden',
+ 'peer-data-[size=sm]/menu-button:top-1',
+ 'peer-data-[size=default]/menu-button:top-1.5',
+ 'peer-data-[size=lg]/menu-button:top-2.5',
+ 'group-data-[collapsible=icon]:hidden',
showOnHover &&
- "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
+ 'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
className
)}
{...props}
/>
- )
-})
-SidebarMenuAction.displayName = "SidebarMenuAction"
+ );
+});
+SidebarMenuAction.displayName = 'SidebarMenuAction';
const SidebarMenuBadge = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div">
+ React.ComponentProps<'div'>
>(({ className, ...props }, ref) => (
-))
-SidebarMenuBadge.displayName = "SidebarMenuBadge"
+));
+SidebarMenuBadge.displayName = 'SidebarMenuBadge';
const SidebarMenuSkeleton = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div"> & {
- showIcon?: boolean
+ React.ComponentProps<'div'> & {
+ showIcon?: boolean;
}
>(({ className, showIcon = false, ...props }, ref) => {
- // Random width between 50 to 90%.
- const width = React.useMemo(() => {
- return `${Math.floor(Math.random() * 40) + 50}%`
- }, [])
+ // Random width between 50 to 90% - generated once on mount
+ const width = React.useState(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`;
+ })[0];
return (
{showIcon && (
@@ -695,47 +695,47 @@ const SidebarMenuSkeleton = React.forwardRef<
data-sidebar="menu-skeleton-text"
style={
{
- "--skeleton-width": width,
+ '--skeleton-width': width,
} as React.CSSProperties
}
/>
- )
-})
-SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
+ );
+});
+SidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton';
const SidebarMenuSub = React.forwardRef<
HTMLUListElement,
- React.ComponentProps<"ul">
+ React.ComponentProps<'ul'>
>(({ className, ...props }, ref) => (
-))
-SidebarMenuSub.displayName = "SidebarMenuSub"
+));
+SidebarMenuSub.displayName = 'SidebarMenuSub';
const SidebarMenuSubItem = React.forwardRef<
HTMLLIElement,
- React.ComponentProps<"li">
->(({ ...props }, ref) => )
-SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
+ React.ComponentProps<'li'>
+>(({ ...props }, ref) => );
+SidebarMenuSubItem.displayName = 'SidebarMenuSubItem';
const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement,
- React.ComponentProps<"a"> & {
- asChild?: boolean
- size?: "sm" | "md"
- isActive?: boolean
+ React.ComponentProps<'a'> & {
+ asChild?: boolean;
+ size?: 'sm' | 'md';
+ isActive?: boolean;
}
->(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
- const Comp = asChild ? Slot : "a"
+>(({ asChild = false, size = 'md', isActive, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'a';
return (
span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
- "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
- size === "sm" && "text-xs",
- size === "md" && "text-sm",
- "group-data-[collapsible=icon]:hidden",
+ 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
+ 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
+ size === 'sm' && 'text-xs',
+ size === 'md' && 'text-sm',
+ 'group-data-[collapsible=icon]:hidden',
className
)}
{...props}
/>
- )
-})
-SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
+ );
+});
+SidebarMenuSubButton.displayName = 'SidebarMenuSubButton';
export {
Sidebar,
@@ -782,4 +782,4 @@ export {
SidebarSeparator,
SidebarTrigger,
useSidebar,
-}
+};
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
index d4616716d..1ea6d865d 100644
--- a/src/components/ui/skeleton.tsx
+++ b/src/components/ui/skeleton.tsx
@@ -12,7 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
function Skeleton({
className,
@@ -20,10 +20,10 @@ function Skeleton({
}: React.HTMLAttributes) {
return (
- )
+ );
}
-export { Skeleton }
+export { Skeleton };
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
index 137604d03..528ec81a8 100644
--- a/src/components/ui/sonner.tsx
+++ b/src/components/ui/sonner.tsx
@@ -12,32 +12,32 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import { useTheme } from "next-themes"
-import { Toaster as Sonner } from "sonner"
+import { useTheme } from 'next-themes';
+import { Toaster as Sonner } from 'sonner';
-type ToasterProps = React.ComponentProps
+type ToasterProps = React.ComponentProps;
const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme()
+ const { theme = 'system' } = useTheme();
return (
- )
-}
+ );
+};
-export { Toaster }
+export { Toaster };
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
index d703aef78..f9f374288 100644
--- a/src/components/ui/switch.tsx
+++ b/src/components/ui/switch.tsx
@@ -12,10 +12,10 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
-import * as SwitchPrimitives from "@radix-ui/react-switch"
+import * as SwitchPrimitives from '@radix-ui/react-switch';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
const Switch = React.forwardRef<
React.ElementRef,
@@ -23,7 +23,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-Switch.displayName = SwitchPrimitives.Root.displayName
+));
+Switch.displayName = SwitchPrimitives.Root.displayName;
-export { Switch }
+export { Switch };
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
index 94a33e525..f081dfc23 100644
--- a/src/components/ui/tabs.tsx
+++ b/src/components/ui/tabs.tsx
@@ -12,57 +12,57 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react";
-import * as TabsPrimitive from "@radix-ui/react-tabs";
+import * as TabsPrimitive from '@radix-ui/react-tabs';
+import * as React from 'react';
-import { cn } from "@/lib/utils";
+import { cn } from '@/lib/utils';
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
-export { Tabs, TabsList, TabsTrigger, TabsContent };
+export { Tabs, TabsContent, TabsList, TabsTrigger };
diff --git a/src/components/ui/tag.tsx b/src/components/ui/tag.tsx
index 5b27268a8..7bde7b4f2 100644
--- a/src/components/ui/tag.tsx
+++ b/src/components/ui/tag.tsx
@@ -12,78 +12,89 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react";
-import { Slot } from "@radix-ui/react-slot";
-import { cva, type VariantProps } from "class-variance-authority";
+import { Slot } from '@radix-ui/react-slot';
+import { cva, type VariantProps } from 'class-variance-authority';
+import * as React from 'react';
-import { cn } from "@/lib/utils";
+import { cn } from '@/lib/utils';
const tagVariants = cva(
- "inline-flex justify-start items-center leading-relaxed",
- {
- variants: {
- variant: {
- primary: "bg-tag-fill-info text-[var(--tag-foreground-info)]",
- info: "bg-tag-fill-info !text-[var(--tag-foreground-info)]",
- success: "bg-tag-fill-success !text-[var(--tag-foreground-success)]",
- cuation: "bg-tag-fill-cuation !text-[var(--tag-foreground-cuation)]",
- warning: "bg-tag-fill-warning !text-[var(--tag-foreground-warning)]",
- default: "bg-tag-fill-default !text-[var(--tag-foreground-default)]",
- ghost: "bg-transparent !text-[var(--tag-foreground-default)]",
- },
- size: {
- xs: "px-2 py-0.5 gap-1 text-body-xs font-bold leading-tight [&_svg]:size-[10px] rounded-full",
- sm: "px-2 py-1.5 gap-1 text-body-xs font-bold leading-tight [&_svg]:size-[16px] rounded-full",
- md: "px-3 py-1.5 gap-2 text-body-md font-semibold leading-relaxed [&_svg]:size-[20px] rounded-xl",
- },
- },
- defaultVariants: {
- variant: "primary",
- size: "sm",
- },
- }
+ 'inline-flex justify-start items-center leading-relaxed',
+ {
+ variants: {
+ variant: {
+ primary: 'bg-tag-fill-info text-[var(--tag-foreground-info)]',
+ info: 'bg-tag-fill-info !text-[var(--tag-foreground-info)]',
+ success: 'bg-tag-fill-success !text-[var(--tag-foreground-success)]',
+ cuation: 'bg-tag-fill-cuation !text-[var(--tag-foreground-cuation)]',
+ warning: 'bg-tag-fill-warning !text-[var(--tag-foreground-warning)]',
+ default: 'bg-tag-fill-default !text-[var(--tag-foreground-default)]',
+ ghost: 'bg-transparent !text-[var(--tag-foreground-default)]',
+ },
+ size: {
+ xs: 'px-2 py-0.5 gap-1 text-body-xs font-bold leading-tight [&_svg]:size-[10px] rounded-full',
+ sm: 'px-2 py-1.5 gap-1 text-body-xs font-bold leading-tight [&_svg]:size-[16px] rounded-full',
+ md: 'px-3 py-1.5 gap-2 text-body-md font-semibold leading-relaxed [&_svg]:size-[20px] rounded-xl',
+ },
+ },
+ defaultVariants: {
+ variant: 'primary',
+ size: 'sm',
+ },
+ }
);
interface TagProps
- extends React.ComponentProps<"div">,
- VariantProps {
- asChild?: boolean;
- text?: string;
- icon?: React.ReactNode;
+ extends React.ComponentProps<'div'>, VariantProps {
+ asChild?: boolean;
+ text?: string;
+ icon?: React.ReactNode;
}
const Tag = React.forwardRef(
- ({ className, variant, size, asChild = false, text, icon, children, ...props }, ref) => {
- const Comp = asChild ? Slot : "div";
+ (
+ {
+ className,
+ variant,
+ size,
+ asChild = false,
+ text,
+ icon,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const Comp = asChild ? Slot : 'div';
- // When asChild is true, just pass through the child without wrapping
- if (asChild) {
- return (
-
- {children}
-
- );
- }
+ // When asChild is true, just pass through the child without wrapping
+ if (asChild) {
+ return (
+
+ {children}
+
+ );
+ }
- // Normal rendering when asChild is false
- return (
-
- {icon && {icon}}
- {text && {text}}
- {children}
-
- );
- }
+ // Normal rendering when asChild is false
+ return (
+
+ {icon && {icon}}
+ {text && {text}}
+ {children}
+
+ );
+ }
);
-Tag.displayName = "Tag";
+Tag.displayName = 'Tag';
export { Tag, tagVariants };
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
index 22d36179d..a5b1960d5 100644
--- a/src/components/ui/textarea.tsx
+++ b/src/components/ui/textarea.tsx
@@ -12,93 +12,97 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
-import * as React from "react"
+import * as React from 'react';
-import { cn } from "@/lib/utils"
-import { Button } from "./button"
-import { TooltipSimple } from "./tooltip"
-import { CircleAlert } from "lucide-react"
+import { cn } from '@/lib/utils';
+import { CircleAlert } from 'lucide-react';
+import { Button } from './button';
+import { TooltipSimple } from './tooltip';
-export type TextareaVariant = "none" | "enhanced"
-export type TextareaSize = "default" | "sm"
-export type TextareaState = "default" | "hover" | "input" | "error" | "success" | "disabled"
+export type TextareaVariant = 'none' | 'enhanced';
+export type TextareaSize = 'default' | 'sm';
+export type TextareaState =
+ | 'default'
+ | 'hover'
+ | 'input'
+ | 'error'
+ | 'success'
+ | 'disabled';
-type BaseTextareaProps = Omit, "size"> & {
- variant?: TextareaVariant
- size?: TextareaSize
- state?: TextareaState
- title?: string
- tooltip?: string
- note?: string
- required?: boolean
- leadingIcon?: React.ReactNode
- backIcon?: React.ReactNode
- onBackIconClick?: () => void
- trailingButton?: React.ReactNode
- onEnter?: () => void
-}
+type BaseTextareaProps = Omit, 'size'> & {
+ variant?: TextareaVariant;
+ size?: TextareaSize;
+ state?: TextareaState;
+ title?: string;
+ tooltip?: string;
+ note?: string;
+ required?: boolean;
+ leadingIcon?: React.ReactNode;
+ backIcon?: React.ReactNode;
+ onBackIconClick?: () => void;
+ trailingButton?: React.ReactNode;
+ onEnter?: () => void;
+};
const sizeClasses: Record = {
- default: "min-h-[60px] text-body-sm md:text-sm",
- sm: "min-h-[40px] text-body-sm",
-}
+ default: 'min-h-[60px] text-body-sm md:text-sm',
+ sm: 'min-h-[40px] text-body-sm',
+};
function resolveStateClasses(state: TextareaState | undefined) {
- if (state === "disabled") {
+ if (state === 'disabled') {
return {
- container: "opacity-50 cursor-not-allowed",
+ container: 'opacity-50 cursor-not-allowed',
field:
- "border-input-border-default bg-input-bg-default text-input-text-default",
- placeholder: "text-input-label-default",
- }
+ 'border-input-border-default bg-input-bg-default text-input-text-default',
+ placeholder: 'text-input-label-default',
+ };
}
- if (state === "hover") {
+ if (state === 'hover') {
return {
- container: "",
+ container: '',
field:
- "border-input-border-hover bg-input-bg-default text-input-text-default",
- placeholder: "text-input-label-default",
- }
+ 'border-input-border-hover bg-input-bg-default text-input-text-default',
+ placeholder: 'text-input-label-default',
+ };
}
- if (state === "input") {
+ if (state === 'input') {
return {
- container: "",
+ container: '',
field:
- "border-input-border-focus bg-input-bg-input text-input-text-focus",
- placeholder: "text-input-label-default",
- }
+ 'border-input-border-focus bg-input-bg-input text-input-text-focus',
+ placeholder: 'text-input-label-default',
+ };
}
- if (state === "error") {
+ if (state === 'error') {
return {
- container: "",
- field:
- "border-input-border-cuation bg-input-bg-default text-text-body",
- placeholder: "text-input-label-default",
- }
+ container: '',
+ field: 'border-input-border-cuation bg-input-bg-default text-text-body',
+ placeholder: 'text-input-label-default',
+ };
}
- if (state === "success") {
+ if (state === 'success') {
return {
- container: "",
- field:
- "border-input-border-success bg-input-bg-confirm text-text-body",
- placeholder: "text-input-label-default",
- }
+ container: '',
+ field: 'border-input-border-success bg-input-bg-confirm text-text-body',
+ placeholder: 'text-input-label-default',
+ };
}
return {
- container: "",
+ container: '',
field:
- "border-input-border-default bg-input-bg-default text-input-text-default",
- placeholder: "text-input-label-default/10",
- }
+ 'border-input-border-default bg-input-bg-default text-input-text-default',
+ placeholder: 'text-input-label-default/10',
+ };
}
const Textarea = React.forwardRef(
(
{
className,
- variant = "none",
- size = "default",
- state = "default",
+ variant = 'none',
+ size = 'default',
+ state = 'default',
title,
tooltip,
note,
@@ -115,29 +119,29 @@ const Textarea = React.forwardRef(
},
ref
) => {
- const { onKeyDown, ...textareaProps } = props
+ const { onKeyDown, ...textareaProps } = props;
// Original "none" variant - keep the original styling
- if (variant === "none") {
+ if (variant === 'none') {
return (
<>