diff --git a/src/components/base/base-empty.tsx b/src/components/base/base-empty.tsx
index dc3f1325e4398a10aec237921692589d96dcb776..420aa2fe89491bf6fb58165bc75ca0d9b602c685 100644
--- a/src/components/base/base-empty.tsx
+++ b/src/components/base/base-empty.tsx
@@ -1,5 +1,5 @@
 import { alpha, Box, Typography } from "@mui/material";
-import { BlurOnRounded } from "@mui/icons-material";
+import { InboxRounded } from "@mui/icons-material";
 
 interface Props {
   text?: React.ReactNode;
@@ -21,7 +21,7 @@ const BaseEmpty = (props: Props) => {
         color: alpha(palette.text.secondary, 0.75),
       })}
     >
-      <BlurOnRounded sx={{ fontSize: "4em" }} />
+      <InboxRounded sx={{ fontSize: "4em" }} />
       <Typography sx={{ fontSize: "1.25em" }}>{text}</Typography>
       {extra}
     </Box>
diff --git a/src/components/proxy/proxy-global.tsx b/src/components/proxy/proxy-global.tsx
deleted file mode 100644
index 58651d5669051e3a71ae12cfadf731f57950a69c..0000000000000000000000000000000000000000
--- a/src/components/proxy/proxy-global.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import useSWR, { useSWRConfig } from "swr";
-import { useEffect, useRef, useState } from "react";
-import { useLockFn } from "ahooks";
-import { Virtuoso } from "react-virtuoso";
-import { providerHealthCheck, updateProxy } from "@/services/api";
-import { getProfiles, patchProfile } from "@/services/cmds";
-import delayManager from "@/services/delay";
-import useHeadState from "./use-head-state";
-import useFilterSort from "./use-filter-sort";
-import ProxyHead from "./proxy-head";
-import ProxyItem from "./proxy-item";
-
-interface Props {
-  groupName: string;
-  curProxy?: string;
-  proxies: IProxyItem[];
-}
-
-// this component will be used for DIRECT/GLOBAL
-const ProxyGlobal = (props: Props) => {
-  const { groupName, curProxy, proxies } = props;
-
-  const { mutate } = useSWRConfig();
-  const [now, setNow] = useState(curProxy || "DIRECT");
-
-  const [headState, setHeadState] = useHeadState(groupName);
-
-  const virtuosoRef = useRef<any>();
-  const sortedProxies = useFilterSort(
-    proxies,
-    groupName,
-    headState.filterText,
-    headState.sortType
-  );
-
-  const { data: profiles } = useSWR("getProfiles", getProfiles);
-
-  const onChangeProxy = useLockFn(async (name: string) => {
-    await updateProxy(groupName, name);
-    setNow(name);
-
-    if (groupName === "DIRECT") return;
-
-    // update global selected
-    const profile = profiles?.items?.find((p) => p.uid === profiles.current);
-    if (!profile) return;
-    if (!profile.selected) profile.selected = [];
-
-    const index = profile.selected.findIndex((item) => item.name === groupName);
-    if (index < 0) {
-      profile.selected.unshift({ name: groupName, now: name });
-    } else {
-      profile.selected[index] = { name: groupName, now: name };
-    }
-
-    await patchProfile(profiles!.current!, { selected: profile.selected });
-  });
-
-  const onLocation = (smooth = true) => {
-    const index = sortedProxies.findIndex((p) => p.name === now);
-
-    if (index >= 0) {
-      virtuosoRef.current?.scrollToIndex?.({
-        index,
-        align: "center",
-        behavior: smooth ? "smooth" : "auto",
-      });
-    }
-  };
-
-  const onCheckAll = useLockFn(async () => {
-    const providers = new Set(
-      sortedProxies.map((p) => p.provider!).filter(Boolean)
-    );
-
-    if (providers.size) {
-      Promise.allSettled(
-        [...providers].map((p) => providerHealthCheck(p))
-      ).then(() => mutate("getProxies"));
-    }
-
-    await delayManager.checkListDelay(
-      sortedProxies.filter((p) => !p.provider).map((p) => p.name),
-      groupName,
-      16
-    );
-
-    mutate("getProxies");
-  });
-
-  useEffect(() => onLocation(false), [groupName]);
-
-  useEffect(() => {
-    if (groupName === "DIRECT") setNow("DIRECT");
-    else if (groupName === "GLOBAL") {
-      if (profiles) {
-        const current = profiles.current;
-        const profile = profiles.items?.find((p) => p.uid === current);
-
-        profile?.selected?.forEach((item) => {
-          if (item.name === "GLOBAL") {
-            if (item.now && item.now !== curProxy) {
-              updateProxy("GLOBAL", item.now).then(() => setNow(item!.now!));
-              mutate("getProxies");
-            }
-          }
-        });
-      }
-
-      setNow(curProxy || "DIRECT");
-    }
-  }, [groupName, curProxy, profiles]);
-
-  return (
-    <>
-      <ProxyHead
-        sx={{ px: 3, my: 0.5, button: { mr: 0.5 } }}
-        groupName={groupName}
-        headState={headState}
-        onLocation={onLocation}
-        onCheckDelay={onCheckAll}
-        onHeadState={setHeadState}
-      />
-
-      <Virtuoso
-        ref={virtuosoRef}
-        style={{ height: "calc(100% - 40px)" }}
-        totalCount={sortedProxies.length}
-        itemContent={(index) => (
-          <ProxyItem
-            groupName={groupName}
-            proxy={sortedProxies[index]}
-            selected={sortedProxies[index].name === now}
-            showType={headState.showType}
-            onClick={onChangeProxy}
-            sx={{ py: 0, px: 2 }}
-          />
-        )}
-      />
-    </>
-  );
-};
-
-export default ProxyGlobal;
diff --git a/src/components/proxy/proxy-group.tsx b/src/components/proxy/proxy-group.tsx
deleted file mode 100644
index 827359fd72a5810837cf33fafe4e12b2f6c29063..0000000000000000000000000000000000000000
--- a/src/components/proxy/proxy-group.tsx
+++ /dev/null
@@ -1,232 +0,0 @@
-import useSWR, { useSWRConfig } from "swr";
-import { useEffect, useRef, useState } from "react";
-import { useLockFn } from "ahooks";
-import { Virtuoso } from "react-virtuoso";
-import {
-  Box,
-  Collapse,
-  Divider,
-  List,
-  ListItem,
-  ListItemText,
-} from "@mui/material";
-import {
-  SendRounded,
-  ExpandLessRounded,
-  ExpandMoreRounded,
-} from "@mui/icons-material";
-import {
-  getConnections,
-  providerHealthCheck,
-  updateProxy,
-  deleteConnection,
-} from "@/services/api";
-import { getProfiles, patchProfile } from "@/services/cmds";
-import { useVergeConfig } from "@/hooks/use-verge-config";
-import delayManager from "@/services/delay";
-import useHeadState from "./use-head-state";
-import useFilterSort from "./use-filter-sort";
-import ProxyHead from "./proxy-head";
-import ProxyItem from "./proxy-item";
-
-interface Props {
-  group: IProxyGroupItem;
-}
-
-const ProxyGroup = ({ group }: Props) => {
-  const { mutate } = useSWRConfig();
-  const [now, setNow] = useState(group.now);
-
-  const [headState, setHeadState] = useHeadState(group.name);
-
-  const virtuosoRef = useRef<any>();
-  const sortedProxies = useFilterSort(
-    group.all,
-    group.name,
-    headState.filterText,
-    headState.sortType
-  );
-
-  const { data: profiles } = useSWR("getProfiles", getProfiles);
-  const { data: vergeConfig } = useVergeConfig();
-
-  const onChangeProxy = useLockFn(async (name: string) => {
-    // Todo: support another proxy group type
-    if (group.type !== "Selector") return;
-
-    const oldValue = now;
-    try {
-      setNow(name);
-      await updateProxy(group.name, name);
-
-      if (vergeConfig?.auto_close_connection) {
-        getConnections().then((snapshot) => {
-          snapshot.connections.forEach((conn) => {
-            if (conn.chains.includes(oldValue!)) {
-              deleteConnection(conn.id);
-            }
-          });
-        });
-      }
-    } catch {
-      setNow(oldValue);
-      return; // do not update profile
-    }
-
-    try {
-      const profile = profiles?.items?.find((p) => p.uid === profiles.current);
-      if (!profile) return;
-      if (!profile.selected) profile.selected = [];
-
-      const index = profile.selected.findIndex(
-        (item) => item.name === group.name
-      );
-
-      if (index < 0) {
-        profile.selected.push({ name: group.name, now: name });
-      } else {
-        profile.selected[index] = { name: group.name, now: name };
-      }
-      await patchProfile(profiles!.current!, { selected: profile.selected });
-    } catch (err) {
-      console.error(err);
-    }
-  });
-
-  const onLocation = (smooth = true) => {
-    const index = sortedProxies.findIndex((p) => p.name === now);
-
-    if (index >= 0) {
-      virtuosoRef.current?.scrollToIndex?.({
-        index,
-        align: "center",
-        behavior: smooth ? "smooth" : "auto",
-      });
-    }
-  };
-
-  const onCheckAll = useLockFn(async () => {
-    const providers = new Set(
-      sortedProxies.map((p) => p.provider!).filter(Boolean)
-    );
-
-    if (providers.size) {
-      Promise.allSettled(
-        [...providers].map((p) => providerHealthCheck(p))
-      ).then(() => mutate("getProxies"));
-    }
-
-    await delayManager.checkListDelay(
-      sortedProxies.filter((p) => !p.provider).map((p) => p.name),
-      group.name,
-      16
-    );
-
-    mutate("getProxies");
-  });
-
-  // auto scroll to current index
-  useEffect(() => {
-    if (headState.open) {
-      setTimeout(() => onLocation(false), 10);
-    }
-  }, [headState.open, sortedProxies]);
-
-  // // auto scroll when sorted changed
-  // const timerRef = useRef<any>();
-  // useEffect(() => {
-  //   if (headState.open) {
-  //     clearTimeout(timerRef.current);
-  //     timerRef.current = setTimeout(() => onLocation(false), 500);
-  //   }
-  // }, [headState.open, sortedProxies]);
-
-  return (
-    <>
-      <ListItem
-        button
-        dense
-        onClick={() => setHeadState({ open: !headState.open })}
-      >
-        <ListItemText
-          primary={group.name}
-          secondary={
-            <>
-              <SendRounded color="primary" sx={{ mr: 1, fontSize: 14 }} />
-              <span>{now}</span>
-            </>
-          }
-          secondaryTypographyProps={{
-            sx: { display: "flex", alignItems: "center" },
-          }}
-        />
-
-        {headState.open ? <ExpandLessRounded /> : <ExpandMoreRounded />}
-      </ListItem>
-
-      <Collapse in={headState.open} timeout="auto" unmountOnExit>
-        <ProxyHead
-          sx={{ pl: 4, pr: 3, my: 0.5, button: { mr: 0.5 } }}
-          groupName={group.name}
-          headState={headState}
-          onLocation={onLocation}
-          onCheckDelay={onCheckAll}
-          onHeadState={setHeadState}
-        />
-
-        {!sortedProxies.length && (
-          <Box
-            sx={{
-              py: 3,
-              fontSize: 18,
-              textAlign: "center",
-              color: "text.secondary",
-            }}
-          >
-            Empty
-          </Box>
-        )}
-
-        {sortedProxies.length >= 10 ? (
-          <Virtuoso
-            ref={virtuosoRef}
-            style={{ height: "320px", marginBottom: "4px" }}
-            totalCount={sortedProxies.length}
-            itemContent={(index) => (
-              <ProxyItem
-                groupName={group.name}
-                proxy={sortedProxies[index]}
-                selected={sortedProxies[index].name === now}
-                showType={headState.showType}
-                sx={{ py: 0, pl: 4 }}
-                onClick={onChangeProxy}
-              />
-            )}
-          />
-        ) : (
-          <List
-            component="div"
-            disablePadding
-            sx={{ maxHeight: "320px", overflow: "auto", mb: "4px" }}
-          >
-            {sortedProxies.map((proxy) => (
-              <ProxyItem
-                key={proxy.name}
-                groupName={group.name}
-                proxy={proxy}
-                selected={proxy.name === now}
-                showType={headState.showType}
-                sx={{ py: 0, pl: 4 }}
-                onClick={onChangeProxy}
-              />
-            ))}
-          </List>
-        )}
-
-        <Divider variant="middle" />
-      </Collapse>
-    </>
-  );
-};
-
-export default ProxyGroup;
diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ebaccc82aa0af809b3a0414c49def7bc06196c41
--- /dev/null
+++ b/src/components/proxy/proxy-groups.tsx
@@ -0,0 +1,225 @@
+import { useRef } from "react";
+import { useLockFn } from "ahooks";
+import {
+  Box,
+  Divider,
+  ListItem,
+  ListItemText,
+  Typography,
+} from "@mui/material";
+import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
+import {
+  ExpandLessRounded,
+  ExpandMoreRounded,
+  InboxRounded,
+  SendRounded,
+} from "@mui/icons-material";
+import {
+  getConnections,
+  providerHealthCheck,
+  updateProxy,
+  deleteConnection,
+} from "@/services/api";
+import { useProfiles } from "@/hooks/use-profiles";
+import { useVergeConfig } from "@/hooks/use-verge-config";
+import { useRenderList, type IRenderItem } from "./use-render-list";
+import { HeadState } from "./use-head-state";
+import { ProxyHead } from "./proxy-head";
+import { ProxyItem } from "./proxy-item";
+import delayManager from "@/services/delay";
+
+interface Props {
+  mode: string;
+}
+
+export const ProxyGroups = (props: Props) => {
+  const { mode } = props;
+
+  const { renderList, onProxies, onHeadState } = useRenderList(mode);
+
+  const { data: vergeConfig } = useVergeConfig();
+  const { current, patchCurrent } = useProfiles();
+
+  const virtuosoRef = useRef<VirtuosoHandle>(null);
+
+  // 切换分组的节点代理
+  const handleChangeProxy = useLockFn(
+    async (group: IProxyGroupItem, proxy: IProxyItem) => {
+      if (group.type !== "Selector") return;
+
+      const { name, now } = group;
+      await updateProxy(name, proxy.name);
+      onProxies();
+
+      // 断开连接
+      if (vergeConfig?.auto_close_connection) {
+        getConnections().then(({ connections }) => {
+          connections.forEach((conn) => {
+            if (conn.chains.includes(now!)) {
+              deleteConnection(conn.id);
+            }
+          });
+        });
+      }
+
+      // 保存到selected中
+      if (!current) return;
+      if (!current.selected) current.selected = [];
+
+      const index = current.selected.findIndex(
+        (item) => item.name === group.name
+      );
+
+      if (index < 0) {
+        current.selected.push({ name, now: proxy.name });
+      } else {
+        current.selected[index] = { name, now: proxy.name };
+      }
+      await patchCurrent({ selected: current.selected });
+    }
+  );
+
+  // 测全部延迟
+  const handleCheckAll = useLockFn(async (groupName: string) => {
+    const proxies = renderList
+      .filter((e) => e.type === 2 && e.group?.name === groupName)
+      .map((e) => e.proxy!)
+      .filter(Boolean);
+
+    const providers = new Set(proxies.map((p) => p!.provider!).filter(Boolean));
+
+    if (providers.size) {
+      Promise.allSettled(
+        [...providers].map((p) => providerHealthCheck(p))
+      ).then(() => onProxies());
+    }
+
+    const names = proxies.filter((p) => !p!.provider).map((p) => p!.name);
+    await delayManager.checkListDelay(names, groupName, 24);
+
+    onProxies();
+  });
+
+  // 滚到对应的节点
+  const handleLocation = (group: IProxyGroupItem) => {
+    if (!group) return;
+    const { name, now } = group;
+
+    const index = renderList.findIndex(
+      (e) => e.type === 2 && e.group?.name === name && e.proxy?.name === now
+    );
+
+    if (index >= 0) {
+      virtuosoRef.current?.scrollToIndex?.({
+        index,
+        align: "center",
+        behavior: "smooth",
+      });
+    }
+  };
+
+  return (
+    <Virtuoso
+      ref={virtuosoRef}
+      style={{ height: "100%" }}
+      totalCount={renderList.length}
+      itemContent={(index) => (
+        <ProxyRenderItem
+          key={renderList[index].key}
+          item={renderList[index]}
+          indent={mode === "rule" || mode === "script"}
+          onLocation={handleLocation}
+          onCheckAll={handleCheckAll}
+          onHeadState={onHeadState}
+          onChangeProxy={handleChangeProxy}
+        />
+      )}
+    />
+  );
+};
+
+interface RenderProps {
+  item: IRenderItem;
+  indent: boolean;
+  onLocation: (group: IProxyGroupItem) => void;
+  onCheckAll: (groupName: string) => void;
+  onHeadState: (groupName: string, patch: Partial<HeadState>) => void;
+  onChangeProxy: (group: IProxyGroupItem, proxy: IProxyItem) => void;
+}
+
+function ProxyRenderItem(props: RenderProps) {
+  const { indent, item, onLocation, onCheckAll, onHeadState, onChangeProxy } =
+    props;
+  const { type, group, headState, proxy } = item;
+
+  if (type === 0) {
+    return (
+      <ListItem
+        button
+        dense
+        onClick={() => onHeadState(group.name, { open: !headState?.open })}
+      >
+        <ListItemText
+          primary={group.name}
+          secondary={
+            <>
+              <SendRounded color="primary" sx={{ mr: 1, fontSize: 14 }} />
+              {/* <span>{group.type}</span> */}
+              <span>{group.now}</span>
+            </>
+          }
+          secondaryTypographyProps={{
+            sx: { display: "flex", alignItems: "center" },
+          }}
+        />
+        {headState?.open ? <ExpandLessRounded /> : <ExpandMoreRounded />}
+      </ListItem>
+    );
+  }
+
+  if (type === 1) {
+    return (
+      <ProxyHead
+        sx={{ pl: indent ? 4.5 : 2.5, pr: 3, my: 1, button: { mr: 0.5 } }}
+        groupName={group.name}
+        headState={headState!}
+        onLocation={() => onLocation(group)}
+        onCheckDelay={() => onCheckAll(group.name)}
+        onHeadState={(p) => onHeadState(group.name, p)}
+      />
+    );
+  }
+
+  if (type === 2) {
+    return (
+      <ProxyItem
+        groupName={group.name}
+        proxy={proxy!}
+        selected={group.now === proxy?.name}
+        showType={headState?.showType}
+        sx={{ py: 0, pl: indent ? 4 : 2 }}
+        onClick={() => onChangeProxy(group, proxy!)}
+      />
+    );
+  }
+
+  if (type === 3) {
+    return (
+      <Box
+        sx={{
+          py: 2,
+          pl: indent ? 4.5 : 0,
+          display: "flex",
+          flexDirection: "column",
+          alignItems: "center",
+          justifyContent: "center",
+        }}
+      >
+        <InboxRounded sx={{ fontSize: "2.5em", color: "inherit" }} />
+        <Typography sx={{ color: "inherit" }}>No Proxies</Typography>
+      </Box>
+    );
+  }
+
+  return null;
+}
diff --git a/src/components/proxy/proxy-head.tsx b/src/components/proxy/proxy-head.tsx
index 7dbaae08ef0f9643eda263ea783cbe5572100dfa..04bc6caa32f6765ee28f16fa20a3f102223d1b46 100644
--- a/src/components/proxy/proxy-head.tsx
+++ b/src/components/proxy/proxy-head.tsx
@@ -28,7 +28,7 @@ interface Props {
   onHeadState: (val: Partial<HeadState>) => void;
 }
 
-const ProxyHead = (props: Props) => {
+export const ProxyHead = (props: Props) => {
   const { sx = {}, groupName, headState, onHeadState } = props;
 
   const { showType, sortType, filterText, textState, testUrl } = headState;
@@ -163,5 +163,3 @@ const ProxyHead = (props: Props) => {
     </Box>
   );
 };
-
-export default ProxyHead;
diff --git a/src/components/proxy/proxy-item.tsx b/src/components/proxy/proxy-item.tsx
index 5dbdb3180f51060909ff4dd7ebfbedf5ba374db6..d72068269eb0492540c758fad01509c2b202f289 100644
--- a/src/components/proxy/proxy-item.tsx
+++ b/src/components/proxy/proxy-item.tsx
@@ -42,7 +42,7 @@ const TypeBox = styled(Box)(({ theme }) => ({
   lineHeight: 1.25,
 }));
 
-const ProxyItem = (props: Props) => {
+export const ProxyItem = (props: Props) => {
   const { groupName, proxy, selected, showType = true, sx, onClick } = props;
 
   // -1/<=0 为 不显示
@@ -174,5 +174,3 @@ const ProxyItem = (props: Props) => {
     </ListItem>
   );
 };
-
-export default ProxyItem;
diff --git a/src/components/proxy/use-filter-sort.ts b/src/components/proxy/use-filter-sort.ts
index c5c3cba13dc9f6318989effbb08690b638d0fc65..3d7b7fd98f2c195c4a91b030e0bc818d940acbea 100644
--- a/src/components/proxy/use-filter-sort.ts
+++ b/src/components/proxy/use-filter-sort.ts
@@ -36,6 +36,17 @@ export default function useFilterSort(
   }, [proxies, groupName, filterText, sortType, refresh]);
 }
 
+export function filterSort(
+  proxies: IProxyItem[],
+  groupName: string,
+  filterText: string,
+  sortType: ProxySortType
+) {
+  const fp = filterProxies(proxies, groupName, filterText);
+  const sp = sortProxies(fp, groupName, sortType);
+  return sp;
+}
+
 /**
  * 可以通过延迟数/节点类型 过滤
  */
diff --git a/src/components/proxy/use-head-state.ts b/src/components/proxy/use-head-state.ts
index 5e2a6777fcbb4752f612602150fe3d590c580c99..630f59418cac0e645bdae7093ce8fb0d253c0e36 100644
--- a/src/components/proxy/use-head-state.ts
+++ b/src/components/proxy/use-head-state.ts
@@ -15,7 +15,7 @@ export interface HeadState {
 type HeadStateStorage = Record<string, Record<string, HeadState>>;
 
 const HEAD_STATE_KEY = "proxy-head-state";
-const DEFAULT_STATE: HeadState = {
+export const DEFAULT_STATE: HeadState = {
   open: false,
   showType: false,
   sortType: 0,
@@ -78,3 +78,59 @@ export default function useHeadState(groupName: string) {
 
   return [state, setHeadState] as const;
 }
+
+export function useHeadStateNew() {
+  const current = useRecoilValue(atomCurrentProfile);
+
+  const [state, setState] = useState<Record<string, HeadState>>({});
+
+  useEffect(() => {
+    if (!current) {
+      setState({});
+      return;
+    }
+
+    try {
+      const data = JSON.parse(
+        localStorage.getItem(HEAD_STATE_KEY)!
+      ) as HeadStateStorage;
+
+      const value = data[current] || {};
+
+      if (value && typeof value === "object") {
+        setState(value);
+      } else {
+        setState({});
+      }
+    } catch {}
+  }, [current]);
+
+  const setHeadState = useCallback(
+    (groupName: string, obj: Partial<HeadState>) => {
+      setState((old) => {
+        const state = old[groupName] || DEFAULT_STATE;
+        const ret = { ...old, [groupName]: { ...state, ...obj } };
+
+        // 保存到存储中
+        setTimeout(() => {
+          try {
+            const item = localStorage.getItem(HEAD_STATE_KEY);
+
+            let data = (item ? JSON.parse(item) : {}) as HeadStateStorage;
+
+            if (!data || typeof data !== "object") data = {};
+
+            data[current] = ret;
+
+            localStorage.setItem(HEAD_STATE_KEY, JSON.stringify(data));
+          } catch {}
+        });
+
+        return ret;
+      });
+    },
+    [current]
+  );
+
+  return [state, setHeadState] as const;
+}
diff --git a/src/components/proxy/use-render-list.ts b/src/components/proxy/use-render-list.ts
new file mode 100644
index 0000000000000000000000000000000000000000..843dc06d0741e98a9b987fb49b118997321c4f08
--- /dev/null
+++ b/src/components/proxy/use-render-list.ts
@@ -0,0 +1,84 @@
+import useSWR from "swr";
+import { getProxies } from "@/services/api";
+import { useEffect, useMemo } from "react";
+import { filterSort } from "./use-filter-sort";
+import { useHeadStateNew, type HeadState } from "./use-head-state";
+
+export interface IRenderItem {
+  type: 0 | 1 | 2 | 3; // 组 | head | item | empty
+  key: string;
+  group: IProxyGroupItem;
+  proxy?: IProxyItem;
+  headState?: HeadState;
+}
+
+export const useRenderList = (mode: string) => {
+  const { data: proxiesData, mutate: mutateProxies } = useSWR(
+    "getProxies",
+    getProxies,
+    { refreshInterval: 45000, suspense: true }
+  );
+
+  const [headStates, setHeadState] = useHeadStateNew();
+
+  // make sure that fetch the proxies successfully
+  useEffect(() => {
+    if (!proxiesData) return;
+    const { groups, proxies } = proxiesData;
+
+    if (
+      (mode === "rule" && !groups.length) ||
+      (mode === "global" && proxies.length < 2)
+    ) {
+      setTimeout(() => mutateProxies(), 500);
+    }
+  }, [proxiesData, mode]);
+
+  const renderList: IRenderItem[] = useMemo(() => {
+    const useRule = mode === "rule" || mode === "script";
+    const renderGroups =
+      (useRule ? proxiesData?.groups : [proxiesData?.global!]) || [];
+
+    const retList = renderGroups.flatMap((group) => {
+      const headState = headStates[group.name];
+      const ret: IRenderItem[] = [
+        { type: 0, key: group.name, group, headState },
+      ];
+
+      if (headState?.open) {
+        const proxies = filterSort(
+          group.all,
+          group.name,
+          headState.filterText,
+          headState.sortType
+        );
+
+        ret.push({ type: 1, key: `head${group.name}`, group, headState });
+
+        if (!proxies.length) {
+          ret.push({ type: 3, key: `empty${group.name}`, group, headState });
+        }
+
+        return ret.concat(
+          proxies.map((proxy) => ({
+            type: 2,
+            key: `${group.name}-${proxy!.name}`,
+            group,
+            proxy,
+            headState,
+          }))
+        );
+      }
+      return ret;
+    });
+
+    if (!useRule) return retList.slice(1);
+    return retList;
+  }, [headStates, proxiesData, mode]);
+
+  return {
+    renderList,
+    onProxies: mutateProxies,
+    onHeadState: setHeadState,
+  };
+};
diff --git a/src/hooks/use-profiles.ts b/src/hooks/use-profiles.ts
new file mode 100644
index 0000000000000000000000000000000000000000..17b2886eb57751db2743d0dbf11b58dc32e14ea9
--- /dev/null
+++ b/src/hooks/use-profiles.ts
@@ -0,0 +1,29 @@
+import useSWR from "swr";
+import {
+  getProfiles,
+  patchProfile,
+  patchProfilesConfig,
+} from "@/services/cmds";
+
+export const useProfiles = () => {
+  const { data: profiles, mutate } = useSWR("getProfiles", getProfiles);
+
+  const patchProfiles = async (value: Partial<IProfilesConfig>) => {
+    await patchProfilesConfig(value);
+    mutate();
+  };
+
+  const patchCurrent = async (value: Partial<IProfileItem>) => {
+    if (profiles?.current) {
+      await patchProfile(profiles.current, value);
+      mutate();
+    }
+  };
+
+  return {
+    profiles,
+    current: profiles?.items?.find((p) => p.uid === profiles.current),
+    patchProfiles,
+    patchCurrent,
+  };
+};
diff --git a/src/pages/proxies.tsx b/src/pages/proxies.tsx
index ca5bc99e4a058e1a3dc566eb2a84a4432bfff765..6816bbc276ee82ce38395047508d0de837576ea6 100644
--- a/src/pages/proxies.tsx
+++ b/src/pages/proxies.tsx
@@ -1,62 +1,32 @@
-import useSWR, { useSWRConfig } from "swr";
-import { useEffect, useMemo } from "react";
+import useSWR from "swr";
 import { useLockFn } from "ahooks";
 import { useTranslation } from "react-i18next";
-import { Button, ButtonGroup, List, Paper } from "@mui/material";
+import { Button, ButtonGroup, Paper } from "@mui/material";
 import { getClashConfig, updateConfigs } from "@/services/api";
 import { patchClashConfig } from "@/services/cmds";
-import { getProxies } from "@/services/api";
+import { ProxyGroups } from "@/components/proxy/proxy-groups";
 import BasePage from "@/components/base/base-page";
-import BaseEmpty from "@/components/base/base-empty";
-import ProxyGroup from "@/components/proxy/proxy-group";
 
 const ProxyPage = () => {
   const { t } = useTranslation();
-  const { mutate } = useSWRConfig();
-  const { data: proxiesData } = useSWR("getProxies", getProxies, {
-    refreshInterval: 45000, // 45s
-  });
-  const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
+
+  const { data: clashConfig, mutate: mutateClash } = useSWR(
+    "getClashConfig",
+    getClashConfig
+  );
 
   const modeList = ["rule", "global", "direct", "script"];
   const curMode = clashConfig?.mode.toLowerCase();
-  const { global, groups = [], proxies = [] } = proxiesData ?? {};
-
-  // make sure that fetch the proxies successfully
-  useEffect(() => {
-    if (
-      (curMode === "rule" && !groups.length) ||
-      (curMode === "global" && proxies.length < 2)
-    ) {
-      setTimeout(() => mutate("getProxies"), 500);
-    }
-  }, [groups, proxies, curMode]);
 
   const onChangeMode = useLockFn(async (mode: string) => {
-    // switch rapidly
     await updateConfigs({ mode });
     await patchClashConfig({ mode });
-    mutate("getClashConfig");
+    mutateClash();
   });
 
-  // 仅mode为全局和直连的时候展示global分组
-  const displayGroups = useMemo(() => {
-    if (!global) return groups;
-    if (curMode === "global" || curMode === "direct" || groups.length === 0)
-      return [global, ...groups];
-    return groups;
-  }, [global, groups, curMode]);
-
-  // difference style
-  const showGroup = displayGroups.length > 0;
-  const pageStyle = showGroup ? {} : { height: "100%" };
-  const paperStyle: any = showGroup
-    ? { mb: 0.5 }
-    : { py: 1, height: "100%", boxSizing: "border-box" };
-
   return (
     <BasePage
-      contentStyle={pageStyle}
+      contentStyle={{ height: "100%" }}
       title={t("Proxy Groups")}
       header={
         <ButtonGroup size="small">
@@ -73,16 +43,16 @@ const ProxyPage = () => {
         </ButtonGroup>
       }
     >
-      <Paper sx={{ borderRadius: 1, boxShadow: 2, ...paperStyle }}>
-        {displayGroups.length > 0 ? (
-          <List>
-            {displayGroups.map((group) => (
-              <ProxyGroup key={group.name} group={group} />
-            ))}
-          </List>
-        ) : (
-          <BaseEmpty />
-        )}
+      <Paper
+        sx={{
+          borderRadius: 1,
+          boxShadow: 2,
+          height: "100%",
+          boxSizing: "border-box",
+          py: 1,
+        }}
+      >
+        <ProxyGroups mode={curMode!} />
       </Paper>
     </BasePage>
   );