[
  {
    "path": ".eslintcache",
    "content": "[{\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\api\\\\pay.ts\":\"1\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\purchase-dialog.tsx\":\"2\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\ai\\\\ai-page.tsx\":\"3\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\eslint.config.js\":\"4\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\api\\\\ai.ts\":\"5\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\api\\\\supabase.ts\":\"6\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\api\\\\web-widget-api.ts\":\"7\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\app.tsx\":\"8\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\app-sidebar.tsx\":\"9\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\dashboard-layout.tsx\":\"10\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\login-check.tsx\":\"11\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\manager\\\\deployed-widget-card.tsx\":\"12\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\manager\\\\deployed-widget-list.tsx\":\"13\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\nav-main.tsx\":\"14\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\nav-user.tsx\":\"15\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\setting-section.tsx\":\"16\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\team-switcher.tsx\":\"17\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\tray\\\\social-links.tsx\":\"18\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\tray\\\\tray-menu-item.tsx\":\"19\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\user-avatar.tsx\":\"20\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\widget-sync-item.tsx\":\"21\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-app-broadcast.ts\":\"22\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-app-language.ts\":\"23\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-app-runtime-info.ts\":\"24\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-cell-size-config.ts\":\"25\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-debounce.ts\":\"26\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-debug-config.ts\":\"27\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-ipc-listener.ts\":\"28\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-launch-at-startup-config.ts\":\"29\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-mobile.ts\":\"30\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-supabase-channel.ts\":\"31\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-user.ts\":\"32\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-widget-package.ts\":\"33\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\i18n\\\\config.ts\":\"34\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\lib\\\\request.ts\":\"35\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\lib\\\\utils.ts\":\"36\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\main.tsx\":\"37\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\model\\\\app-version.ts\":\"38\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\add-widget-page.tsx\":\"39\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\components\\\\feature-wall-list.tsx\":\"40\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\components\\\\search-item.tsx\":\"41\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\components\\\\widget-container.tsx\":\"42\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\components\\\\widget-tags.tsx\":\"43\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\components\\\\zoom-image.tsx\":\"44\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\dev\\\\dev-page.tsx\":\"45\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\error-page.tsx\":\"46\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\packages\\\\components\\\\widget-package-item.tsx\":\"47\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\packages\\\\widget-package-manager-page.tsx\":\"48\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\app-info-page.tsx\":\"49\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\components\\\\app-theme-form.tsx\":\"50\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\components\\\\font-family-picker.tsx\":\"51\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\components\\\\theme-preview.tsx\":\"52\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\components\\\\theme-tags.tsx\":\"53\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\general-page.tsx\":\"54\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\proxy-page.tsx\":\"55\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\theme-page.tsx\":\"56\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\tray\\\\tray-page.tsx\":\"57\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\user\\\\profile-page.tsx\":\"58\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\router\\\\index.tsx\":\"59\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\utils\\\\version-utils.ts\":\"60\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\utils\\\\widget-util.ts\":\"61\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\vite.config.ts\":\"62\",\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\size\\\\size-page.tsx\":\"63\"},{\"size\":1300,\"mtime\":1775124929188,\"results\":\"64\",\"hashOfConfig\":\"65\"},{\"size\":4878,\"mtime\":1775124610329,\"results\":\"66\",\"hashOfConfig\":\"65\"},{\"size\":10222,\"mtime\":1775274604419,\"results\":\"67\",\"hashOfConfig\":\"65\"},{\"size\":756,\"mtime\":1773145200570,\"results\":\"68\",\"hashOfConfig\":\"69\"},{\"size\":1682,\"mtime\":1772368162180,\"results\":\"70\",\"hashOfConfig\":\"65\"},{\"size\":515,\"mtime\":1771899559889,\"results\":\"71\",\"hashOfConfig\":\"65\"},{\"size\":258,\"mtime\":1772368162182,\"results\":\"72\",\"hashOfConfig\":\"65\"},{\"size\":682,\"mtime\":1773144567704,\"results\":\"73\",\"hashOfConfig\":\"65\"},{\"size\":3763,\"mtime\":1772421333067,\"results\":\"74\",\"hashOfConfig\":\"65\"},{\"size\":1636,\"mtime\":1773144571201,\"results\":\"75\",\"hashOfConfig\":\"65\"},{\"size\":2640,\"mtime\":1772368162186,\"results\":\"76\",\"hashOfConfig\":\"65\"},{\"size\":6005,\"mtime\":1772368162186,\"results\":\"77\",\"hashOfConfig\":\"65\"},{\"size\":1484,\"mtime\":1772368162186,\"results\":\"78\",\"hashOfConfig\":\"65\"},{\"size\":955,\"mtime\":1772368162186,\"results\":\"79\",\"hashOfConfig\":\"65\"},{\"size\":2849,\"mtime\":1772368162186,\"results\":\"80\",\"hashOfConfig\":\"65\"},{\"size\":550,\"mtime\":1772368162189,\"results\":\"81\",\"hashOfConfig\":\"65\"},{\"size\":3101,\"mtime\":1772368162190,\"results\":\"82\",\"hashOfConfig\":\"65\"},{\"size\":2660,\"mtime\":1772371236947,\"results\":\"83\",\"hashOfConfig\":\"65\"},{\"size\":844,\"mtime\":1772368162192,\"results\":\"84\",\"hashOfConfig\":\"65\"},{\"size\":658,\"mtime\":1772368162213,\"results\":\"85\",\"hashOfConfig\":\"65\"},{\"size\":1696,\"mtime\":1772368162214,\"results\":\"86\",\"hashOfConfig\":\"65\"},{\"size\":824,\"mtime\":1772368162216,\"results\":\"87\",\"hashOfConfig\":\"65\"},{\"size\":1451,\"mtime\":1772368162217,\"results\":\"88\",\"hashOfConfig\":\"65\"},{\"size\":856,\"mtime\":1772368162217,\"results\":\"89\",\"hashOfConfig\":\"65\"},{\"size\":585,\"mtime\":1772368162218,\"results\":\"90\",\"hashOfConfig\":\"65\"},{\"size\":392,\"mtime\":1772368162218,\"results\":\"91\",\"hashOfConfig\":\"65\"},{\"size\":644,\"mtime\":1772368162219,\"results\":\"92\",\"hashOfConfig\":\"65\"},{\"size\":606,\"mtime\":1772368162220,\"results\":\"93\",\"hashOfConfig\":\"65\"},{\"size\":748,\"mtime\":1772368162221,\"results\":\"94\",\"hashOfConfig\":\"65\"},{\"size\":584,\"mtime\":1772368162221,\"results\":\"95\",\"hashOfConfig\":\"65\"},{\"size\":1550,\"mtime\":1772368162222,\"results\":\"96\",\"hashOfConfig\":\"65\"},{\"size\":2933,\"mtime\":1772368162223,\"results\":\"97\",\"hashOfConfig\":\"65\"},{\"size\":4035,\"mtime\":1772368162224,\"results\":\"98\",\"hashOfConfig\":\"65\"},{\"size\":655,\"mtime\":1775274383807,\"results\":\"99\",\"hashOfConfig\":\"65\"},{\"size\":804,\"mtime\":1772368162227,\"results\":\"100\",\"hashOfConfig\":\"65\"},{\"size\":195,\"mtime\":1772368162227,\"results\":\"101\",\"hashOfConfig\":\"65\"},{\"size\":315,\"mtime\":1772420673037,\"results\":\"102\",\"hashOfConfig\":\"65\"},{\"size\":144,\"mtime\":1772368162233,\"results\":\"103\",\"hashOfConfig\":\"65\"},{\"size\":6054,\"mtime\":1772368162234,\"results\":\"104\",\"hashOfConfig\":\"65\"},{\"size\":766,\"mtime\":1772368162236,\"results\":\"105\",\"hashOfConfig\":\"65\"},{\"size\":7667,\"mtime\":1775184463071,\"results\":\"106\",\"hashOfConfig\":\"65\"},{\"size\":2709,\"mtime\":1773150161576,\"results\":\"107\",\"hashOfConfig\":\"65\"},{\"size\":2019,\"mtime\":1772368162238,\"results\":\"108\",\"hashOfConfig\":\"65\"},{\"size\":1010,\"mtime\":1773150362366,\"results\":\"109\",\"hashOfConfig\":\"65\"},{\"size\":2604,\"mtime\":1773146651982,\"results\":\"110\",\"hashOfConfig\":\"65\"},{\"size\":1051,\"mtime\":1772421315890,\"results\":\"111\",\"hashOfConfig\":\"65\"},{\"size\":3208,\"mtime\":1772368162241,\"results\":\"112\",\"hashOfConfig\":\"65\"},{\"size\":1534,\"mtime\":1772368162242,\"results\":\"113\",\"hashOfConfig\":\"65\"},{\"size\":5656,\"mtime\":1772368162242,\"results\":\"114\",\"hashOfConfig\":\"65\"},{\"size\":9310,\"mtime\":1775013855943,\"results\":\"115\",\"hashOfConfig\":\"65\"},{\"size\":5502,\"mtime\":1775013918653,\"results\":\"116\",\"hashOfConfig\":\"65\"},{\"size\":5771,\"mtime\":1774959028330,\"results\":\"117\",\"hashOfConfig\":\"65\"},{\"size\":6422,\"mtime\":1775013672434,\"results\":\"118\",\"hashOfConfig\":\"65\"},{\"size\":4458,\"mtime\":1772368162257,\"results\":\"119\",\"hashOfConfig\":\"65\"},{\"size\":4277,\"mtime\":1772368162258,\"results\":\"120\",\"hashOfConfig\":\"65\"},{\"size\":6389,\"mtime\":1775012528499,\"results\":\"121\",\"hashOfConfig\":\"65\"},{\"size\":7144,\"mtime\":1772368162260,\"results\":\"122\",\"hashOfConfig\":\"65\"},{\"size\":7541,\"mtime\":1772368162261,\"results\":\"123\",\"hashOfConfig\":\"65\"},{\"size\":3831,\"mtime\":1775356510301,\"results\":\"124\",\"hashOfConfig\":\"65\"},{\"size\":1109,\"mtime\":1772368162264,\"results\":\"125\",\"hashOfConfig\":\"65\"},{\"size\":984,\"mtime\":1772368162265,\"results\":\"126\",\"hashOfConfig\":\"65\"},{\"size\":447,\"mtime\":1772368162268,\"results\":\"127\",\"hashOfConfig\":\"128\"},{\"size\":4664,\"mtime\":1775443912214,\"results\":\"129\",\"hashOfConfig\":\"65\"},{\"filePath\":\"130\",\"messages\":\"131\",\"suppressedMessages\":\"132\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},\"6w5kbo\",{\"filePath\":\"133\",\"messages\":\"134\",\"suppressedMessages\":\"135\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":3,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"136\",\"messages\":\"137\",\"suppressedMessages\":\"138\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":2,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"139\",\"messages\":\"140\",\"suppressedMessages\":\"141\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},\"hzg01x\",{\"filePath\":\"142\",\"messages\":\"143\",\"suppressedMessages\":\"144\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"145\",\"messages\":\"146\",\"suppressedMessages\":\"147\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"148\",\"messages\":\"149\",\"suppressedMessages\":\"150\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"151\",\"messages\":\"152\",\"suppressedMessages\":\"153\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"154\",\"messages\":\"155\",\"suppressedMessages\":\"156\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":1,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"157\",\"messages\":\"158\",\"suppressedMessages\":\"159\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"160\",\"messages\":\"161\",\"suppressedMessages\":\"162\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":1,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"163\",\"messages\":\"164\",\"suppressedMessages\":\"165\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"166\",\"messages\":\"167\",\"suppressedMessages\":\"168\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"169\",\"messages\":\"170\",\"suppressedMessages\":\"171\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"172\",\"messages\":\"173\",\"suppressedMessages\":\"174\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":1,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"175\",\"messages\":\"176\",\"suppressedMessages\":\"177\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"178\",\"messages\":\"179\",\"suppressedMessages\":\"180\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"181\",\"messages\":\"182\",\"suppressedMessages\":\"183\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"184\",\"messages\":\"185\",\"suppressedMessages\":\"186\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"187\",\"messages\":\"188\",\"suppressedMessages\":\"189\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"190\",\"messages\":\"191\",\"suppressedMessages\":\"192\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"193\",\"messages\":\"194\",\"suppressedMessages\":\"195\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":2,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"196\",\"messages\":\"197\",\"suppressedMessages\":\"198\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":1,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"199\",\"messages\":\"200\",\"suppressedMessages\":\"201\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"202\",\"messages\":\"203\",\"suppressedMessages\":\"204\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"205\",\"messages\":\"206\",\"suppressedMessages\":\"207\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"208\",\"messages\":\"209\",\"suppressedMessages\":\"210\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"211\",\"messages\":\"212\",\"suppressedMessages\":\"213\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"214\",\"messages\":\"215\",\"suppressedMessages\":\"216\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"217\",\"messages\":\"218\",\"suppressedMessages\":\"219\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":1,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"220\",\"messages\":\"221\",\"suppressedMessages\":\"222\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"223\",\"messages\":\"224\",\"suppressedMessages\":\"225\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":3,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"226\",\"messages\":\"227\",\"suppressedMessages\":\"228\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":3,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"229\",\"messages\":\"230\",\"suppressedMessages\":\"231\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"232\",\"messages\":\"233\",\"suppressedMessages\":\"234\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"235\",\"messages\":\"236\",\"suppressedMessages\":\"237\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"238\",\"messages\":\"239\",\"suppressedMessages\":\"240\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"241\",\"messages\":\"242\",\"suppressedMessages\":\"243\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"244\",\"messages\":\"245\",\"suppressedMessages\":\"246\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"247\",\"messages\":\"248\",\"suppressedMessages\":\"249\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"250\",\"messages\":\"251\",\"suppressedMessages\":\"252\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"253\",\"messages\":\"254\",\"suppressedMessages\":\"255\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"256\",\"messages\":\"257\",\"suppressedMessages\":\"258\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"259\",\"messages\":\"260\",\"suppressedMessages\":\"261\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"262\",\"messages\":\"263\",\"suppressedMessages\":\"264\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"265\",\"messages\":\"266\",\"suppressedMessages\":\"267\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"268\",\"messages\":\"269\",\"suppressedMessages\":\"270\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"271\",\"messages\":\"272\",\"suppressedMessages\":\"273\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"274\",\"messages\":\"275\",\"suppressedMessages\":\"276\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":4,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"277\",\"messages\":\"278\",\"suppressedMessages\":\"279\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"280\",\"messages\":\"281\",\"suppressedMessages\":\"282\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"283\",\"messages\":\"284\",\"suppressedMessages\":\"285\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"286\",\"messages\":\"287\",\"suppressedMessages\":\"288\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"289\",\"messages\":\"290\",\"suppressedMessages\":\"291\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"292\",\"messages\":\"293\",\"suppressedMessages\":\"294\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"295\",\"messages\":\"296\",\"suppressedMessages\":\"297\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":2,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"298\",\"messages\":\"299\",\"suppressedMessages\":\"300\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":1,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"301\",\"messages\":\"302\",\"suppressedMessages\":\"303\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":2,\"fixableErrorCount\":0,\"fixableWarningCount\":0,\"source\":null},{\"filePath\":\"304\",\"messages\":\"305\",\"suppressedMessages\":\"306\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"307\",\"messages\":\"308\",\"suppressedMessages\":\"309\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"310\",\"messages\":\"311\",\"suppressedMessages\":\"312\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},{\"filePath\":\"313\",\"messages\":\"314\",\"suppressedMessages\":\"315\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},\"19g2b6m\",{\"filePath\":\"316\",\"messages\":\"317\",\"suppressedMessages\":\"318\",\"errorCount\":0,\"fatalErrorCount\":0,\"warningCount\":0,\"fixableErrorCount\":0,\"fixableWarningCount\":0},\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\api\\\\pay.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\purchase-dialog.tsx\",[\"319\",\"320\",\"321\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\ai\\\\ai-page.tsx\",[\"322\",\"323\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\eslint.config.js\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\api\\\\ai.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\api\\\\supabase.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\api\\\\web-widget-api.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\app.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\app-sidebar.tsx\",[\"324\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\dashboard-layout.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\login-check.tsx\",[\"325\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\manager\\\\deployed-widget-card.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\manager\\\\deployed-widget-list.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\nav-main.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\nav-user.tsx\",[\"326\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\setting-section.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\team-switcher.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\tray\\\\social-links.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\tray\\\\tray-menu-item.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\user-avatar.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\components\\\\widget-sync-item.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-app-broadcast.ts\",[\"327\",\"328\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-app-language.ts\",[\"329\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-app-runtime-info.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-cell-size-config.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-debounce.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-debug-config.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-ipc-listener.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-launch-at-startup-config.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-mobile.ts\",[\"330\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-supabase-channel.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-user.ts\",[\"331\",\"332\",\"333\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\hooks\\\\use-widget-package.ts\",[\"334\",\"335\",\"336\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\i18n\\\\config.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\lib\\\\request.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\lib\\\\utils.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\main.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\model\\\\app-version.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\add-widget-page.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\components\\\\feature-wall-list.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\components\\\\search-item.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\components\\\\widget-container.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\components\\\\widget-tags.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\add\\\\components\\\\zoom-image.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\dev\\\\dev-page.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\error-page.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\packages\\\\components\\\\widget-package-item.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\packages\\\\widget-package-manager-page.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\app-info-page.tsx\",[\"337\",\"338\",\"339\",\"340\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\components\\\\app-theme-form.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\components\\\\font-family-picker.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\components\\\\theme-preview.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\components\\\\theme-tags.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\general-page.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\proxy-page.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\settings\\\\theme-page.tsx\",[\"341\",\"342\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\tray\\\\tray-page.tsx\",[\"343\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\user\\\\profile-page.tsx\",[\"344\",\"345\"],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\router\\\\index.tsx\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\utils\\\\version-utils.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\utils\\\\widget-util.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\vite.config.ts\",[],[],\"C:\\\\Users\\\\rtuge\\\\Desktop\\\\workspace\\\\widgetjs\\\\packages\\\\widgets\\\\src\\\\pages\\\\size\\\\size-page.tsx\",[],[],{\"ruleId\":\"346\",\"severity\":1,\"message\":\"347\",\"line\":56,\"column\":6,\"nodeType\":\"348\",\"endLine\":56,\"endColumn\":13,\"suggestions\":\"349\"},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"351\",\"line\":98,\"column\":7,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":98,\"endColumn\":21},{\"ruleId\":\"346\",\"severity\":1,\"message\":\"354\",\"line\":100,\"column\":6,\"nodeType\":\"348\",\"endLine\":100,\"endColumn\":23,\"suggestions\":\"355\"},{\"ruleId\":\"346\",\"severity\":1,\"message\":\"356\",\"line\":63,\"column\":6,\"nodeType\":\"348\",\"endLine\":63,\"endColumn\":12,\"suggestions\":\"357\"},{\"ruleId\":\"346\",\"severity\":1,\"message\":\"358\",\"line\":87,\"column\":6,\"nodeType\":\"348\",\"endLine\":87,\"endColumn\":18,\"suggestions\":\"359\"},{\"ruleId\":\"360\",\"severity\":1,\"message\":\"361\",\"line\":102,\"column\":27,\"nodeType\":\"362\",\"messageId\":\"353\",\"endLine\":102,\"endColumn\":32},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"363\",\"line\":29,\"column\":24,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":29,\"endColumn\":50},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"363\",\"line\":35,\"column\":24,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":35,\"endColumn\":50},{\"ruleId\":\"346\",\"severity\":1,\"message\":\"364\",\"line\":21,\"column\":6,\"nodeType\":\"348\",\"endLine\":21,\"endColumn\":30,\"suggestions\":\"365\"},{\"ruleId\":\"346\",\"severity\":1,\"message\":\"366\",\"line\":21,\"column\":7,\"nodeType\":\"352\",\"endLine\":21,\"endColumn\":29},{\"ruleId\":\"346\",\"severity\":1,\"message\":\"367\",\"line\":21,\"column\":6,\"nodeType\":\"348\",\"endLine\":21,\"endColumn\":8,\"suggestions\":\"368\"},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"369\",\"line\":14,\"column\":5,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":14,\"endColumn\":55},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"370\",\"line\":67,\"column\":7,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":67,\"endColumn\":26},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"371\",\"line\":76,\"column\":5,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":76,\"endColumn\":21},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"371\",\"line\":81,\"column\":7,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":81,\"endColumn\":24},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"372\",\"line\":43,\"column\":7,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":43,\"endColumn\":57},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"373\",\"line\":44,\"column\":7,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":44,\"endColumn\":55},{\"ruleId\":\"346\",\"severity\":1,\"message\":\"374\",\"line\":126,\"column\":6,\"nodeType\":\"348\",\"endLine\":126,\"endColumn\":34,\"suggestions\":\"375\"},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"371\",\"line\":23,\"column\":5,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":23,\"endColumn\":21},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"376\",\"line\":24,\"column\":5,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":24,\"endColumn\":19},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"377\",\"line\":25,\"column\":5,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":25,\"endColumn\":24},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"378\",\"line\":26,\"column\":5,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":26,\"endColumn\":28},{\"ruleId\":\"379\",\"severity\":1,\"message\":\"380\",\"line\":60,\"column\":54,\"nodeType\":\"381\",\"messageId\":\"353\",\"endLine\":60,\"endColumn\":68},{\"ruleId\":\"382\",\"severity\":1,\"message\":\"383\",\"line\":63,\"column\":9,\"nodeType\":\"362\",\"messageId\":\"384\",\"endLine\":63,\"endColumn\":19},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"363\",\"line\":37,\"column\":24,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":37,\"endColumn\":50},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"385\",\"line\":43,\"column\":21,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":43,\"endColumn\":46},{\"ruleId\":\"350\",\"severity\":1,\"message\":\"386\",\"line\":44,\"column\":19,\"nodeType\":\"352\",\"messageId\":\"353\",\"endLine\":44,\"endColumn\":40},\"react-hooks/exhaustive-deps\",\"React Hook useEffect has missing dependencies: 'onOpenChange' and 'onSuccess'. Either include them or remove the dependency array. If 'onOpenChange' changes too often, find the parent component that defines it and wrap that definition in useCallback.\",\"ArrayExpression\",[\"387\"],\"react-hooks-extra/no-direct-set-state-in-use-effect\",\"Do not call the 'set' function 'setOrder' of 'useState' directly in 'useEffect'.\",\"CallExpression\",\"default\",\"React Hook useEffect has a missing dependency: 'initPurchase'. Either include it or remove the dependency array.\",[\"388\"],\"React Hook useEffect has a missing dependency: 'loadPackages'. Either include it or remove the dependency array.\",[\"389\"],\"React Hook useEffect has a missing dependency: 'loadHistory'. Either include it or remove the dependency array.\",[\"390\"],\"react/no-array-index-key\",\"Do not use item index in the array as its key.\",\"Identifier\",\"Do not call the 'set' function 'setLoginState' of 'useState' directly in 'useEffect'.\",\"React Hook useEffect has a missing dependency: 'events'. Either include it or remove the dependency array.\",[\"391\"],\"React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked.\",\"React Hook useEffect has a missing dependency: 'options'. Either include it or remove the dependency array.\",[\"392\"],\"Do not call the 'set' function 'setIsMobile' of 'useState' directly in 'useEffect'.\",\"Do not call the 'set' function 'setUser' of 'useState' directly in 'useEffect'.\",\"Do not call the 'set' function 'setLoading' of 'useState' directly in 'useEffect'.\",\"Do not call the 'set' function 'setUpgradable' of 'useState' directly in 'useEffect'.\",\"Do not call the 'set' function 'setUpgrading' of 'useState' directly in 'useEffect'.\",\"React Hook useCallback has a missing dependency: 't'. Either include it or remove the dependency array.\",[\"393\"],\"Do not call the 'set' function 'setError' of 'useState' directly in 'useEffect'.\",\"Do not call the 'set' function 'setAppVersion' of 'useState' directly in 'useEffect'.\",\"Do not call the 'set' function 'setHasNewVersion' of 'useState' directly in 'useEffect'.\",\"react/prefer-use-state-lazy-initialization\",\"To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'.\",\"NewExpression\",\"react-naming-convention/ref-name\",\"A ref identifier must be named 'ref' or ending in 'Ref'.\",\"invalidRefName\",\"Do not call the 'set' function 'setNicknameEdit' of 'useState' directly in 'useEffect'.\",\"Do not call the 'set' function 'setAvatarEdit' of 'useState' directly in 'useEffect'.\",{\"desc\":\"394\",\"fix\":\"395\"},{\"desc\":\"396\",\"fix\":\"397\"},{\"desc\":\"398\",\"fix\":\"399\"},{\"desc\":\"400\",\"fix\":\"401\"},{\"desc\":\"402\",\"fix\":\"403\"},{\"desc\":\"404\",\"fix\":\"405\"},{\"desc\":\"406\",\"fix\":\"407\"},\"Update the dependencies array to be: [onOpenChange, onSuccess, order]\",{\"range\":\"408\",\"text\":\"409\"},\"Update the dependencies array to be: [initPurchase, open, productId]\",{\"range\":\"410\",\"text\":\"411\"},\"Update the dependencies array to be: [loadPackages, user]\",{\"range\":\"412\",\"text\":\"413\"},\"Update the dependencies array to be: [loadHistory, page, user]\",{\"range\":\"414\",\"text\":\"415\"},\"Update the dependencies array to be: [events]\",{\"range\":\"416\",\"text\":\"417\"},\"Update the dependencies array to be: [options]\",{\"range\":\"418\",\"text\":\"419\"},\"Update the dependencies array to be: [packageName, remoteUrlInfo, t]\",{\"range\":\"420\",\"text\":\"421\"},[1683,1690],\"[onOpenChange, onSuccess, order]\",[2691,2708],\"[initPurchase, open, productId]\",[1731,1737],\"[loadPackages, user]\",[2253,2265],\"[loadHistory, page, user]\",[600,624],\"[events]\",[727,729],\"[options]\",[3909,3937],\"[packageName, remoteUrlInfo, t]\"]"
  },
  {
    "path": ".github/workflows/github-page.yml",
    "content": "# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs\n\nname: Deploy CI\npermissions:\n  id-token: write\n  pages: write\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Use Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22.x'\n      - name: Setup pnpm\n        uses: pnpm/action-setup@v4\n        with:\n          version: 8\n      - name: Build static files\n        id: build\n        run: |\n          pnpm install\n          pnpm run build\n      - name: Upload static files as artifact\n        id: deployment\n        uses: actions/upload-pages-artifact@v3\n        with:\n          path: dist/\n\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": ".npmrc",
    "content": "#proxy=http://127.0.0.1:7890\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n<a href=\"https://github.com/widget-js/widgets\">\n  <img width=\"150\" src=\"https://raw.githubusercontent.com/widget-js/widgets/master/screenshot/logo.png\" alt=\"Widgets - Capable and ergonomic widgets.\" width=\"300\">\n</a>\n<br>\n桌面组件\n</p>\n\n<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/Q%E7%BE%A4-701784679-EB1923?logo=tencentqq&logoColor=white&sanitize=true\" alt=\"Chat\">\n  <a href=\"https://space.bilibili.com/207395767\"><img src=\"https://img.shields.io/badge/-Bilibili-00A1D6?logo=bilibili&logoColor=white\" alt=\"bilibili\"></a>\n  <a href=\"https://faq.widgetjs.cn\"><img src=\"https://img.shields.io/badge/-功能建议-2378ff?logo=vowpalwabbit&logoColor=white&sanitize=true\" alt=\"Feedback\"></a>\n  <a href=\"https://discord.gg/vwSAaRR8cT\"><img src=\"https://img.shields.io/badge/-Discord-5865F2?logo=discord&logoColor=white\" alt=\"discord\"></a>\n</p>\n\n![png](screenshot/screenshot.jpg)\n![png](https://github.com/rtugeek/stock/raw/master/public/screenshot.png)\n\n## 🔗预览所有组件\n\nhttps://widget-js.github.io/widgets/#/\n\n![png](screenshot/react.png)\n\n\n## 📖说明\n\n这是桌面组件前端开源组件，项目还在持续完善中\n\nWindows 10/11 客户端下载地址：\n- https://www.microsoft.com/store/productId/9NPR50GQ7T53\n- https://widgetjs.cn\n\n## ▶️运行项目\n\n1. 下载并运行桌面组件客户端\n2. 克隆代码\n\n```shell\ngit clone https://github.com/widget-js/widget.git\n```\n\n3. 到项目目录下载依赖\n\n```shell\npnpm run install\n```\n\n4. 运行\n\n```shell\npnpm run dev\n```\n## 📋组件列表\n\n### AI组件包\n\nhttps://github.com/rtugeek/ai\n\n### 一键呼出AI DeepSeek/ChatGPT/Gemini\n\n![png](https://raw.githubusercontent.com/rtugeek/ai/refs/heads/master/screenshot/ai.png)\n\n### 监控组件包\n\n|                                                       监控组件包                                                        |\n|:------------------------------------------------------------------------------------------------------------------:|\n|   ![监控面板](https://github.com/rtugeek/monitor/blob/master/public/image/preview_base_panel.png?raw=true) <br/>监控面板   |\n| ![监控面板](https://github.com/rtugeek/monitor/blob/master/public/image/preview_energy_label.png?raw=true) <br/>打工能耗标签 |\n|   ![监控面板](https://github.com/rtugeek/monitor/blob/master/public/image/preview_server.png?raw=true) <br/>服务器监控面板    |\n\n### 默认组件包\n\n| 默认组件包 |                                   https://github.com/widget-js/widgets                                   |\n|:-----:|:--------------------------------------------------------------------------------------------------------:|\n|  倒计时  |                                 ![gif](screenshot/widget_countdown.png)                                  |\n| 灵动通知  |                                  ![gif](screenshot/dynamic_island.gif)                                   |\n| 打工进度  |                     ![screenshot/labor_progress.gif](screenshot/labor_progress.gif)                      |\n| 时间进度  | <img style=\"border: 3px solid #c3c3c31f;border-radius: 12px\" src=\"screenshot/widget_time_progress.png\"/> |\n\n### 文件夹\n\nhttps://github.com/rtugeek/grid\n\n![png](https://raw.githubusercontent.com/rtugeek/grid/refs/heads/master/public/preview_grid.png)\n\n### iTime组件包\n\n| iTime组件包 |                                        https://github.com/rtugeek/itime-web                                         |\n|:--------:|:-------------------------------------------------------------------------------------------------------------------:|\n|   待办事项   | ![image](https://raw.githubusercontent.com/rtugeek/itime-web/refs/heads/master/public/images/preview_todo_list.png) |\n| Deadline |  ![gif](https://raw.githubusercontent.com/rtugeek/itime-web/refs/heads/master/public/images/preview_deadline.png)   |\n|   番茄钟    |  ![gif](https://raw.githubusercontent.com/rtugeek/itime-web/refs/heads/master/public/images/preview_pomodoro.png)   |\n|    日历    |  ![gif](https://raw.githubusercontent.com/rtugeek/itime-web/refs/heads/master/public/images/preview_calendar.png)   |\n\n### 剪切板组件包\n\n| 剪切板组件包 |                                     https://github.com/rtugeek/clipboard                                     |\n|:------:|:------------------------------------------------------------------------------------------------------------:|\n|  剪切板   | ![Clipboard](https://raw.githubusercontent.com/rtugeek/clipboard/master/public/images/preview_clipboard.png) |\n\n### 天气组件包\n\n| 天气组件包 |                             https://github.com/rtugeek/weather                             |\n|:-----:|:------------------------------------------------------------------------------------------:|\n|  2x2  | ![2x2](https://raw.githubusercontent.com/rtugeek/weather/master/public/preview_small.png)  |\n|  4x2  | ![4x2](https://raw.githubusercontent.com/rtugeek/weather/master/public/preview_medium.png) |\n|  4x4  | ![4x4](https://raw.githubusercontent.com/rtugeek/weather/master/public/preview_large.png)  |\n\n### 热点组件包\n\n|                                                       热点组件包                                                        | https://github.com/widget-js/hotspot                                                                        |\n|:------------------------------------------------------------------------------------------------------------------:|-------------------------------------------------------------------------------------------------------------|\n|   ![bilibili](https://raw.githubusercontent.com/widget-js/hotspot/master/public/images/bilibili_hot_search.png)    | ![weibo.png](https://raw.githubusercontent.com/widget-js/hotspot/master/public/images/weibo_hot_search.png) |\n|       ![douyin](https://raw.githubusercontent.com/widget-js/hotspot/master/public/images/preview_douyin.png)       | ![zhihu.png](https://raw.githubusercontent.com/widget-js/hotspot/master/public/images/preview_zhihu.png)    |\n| ![douyin](https://raw.githubusercontent.com/widget-js/hotspot/master/public/images/preview_bangumi.png)  <br/>追番助手 |                                                                                                             |\n\n### 时钟组件包\n\n|                                                           时钟组件包                                                           |                                            https://github.com/rtugeek/clock                                             |\n|:-------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------:|\n|   ![Flip Clock](https://raw.githubusercontent.com/rtugeek/clock/master/public/images/preview_flip_clock.png) <br/>翻页时钟    |        ![Clock](https://raw.githubusercontent.com/rtugeek/clock/master/public/images/preview_clock.png)  <br/>时钟        |\n| ![Glitch Clock](https://raw.githubusercontent.com/rtugeek/clock/master/public/images/preview_glitch_clock.png)  <br/>故障时钟 | ![Micky Clock](https://raw.githubusercontent.com/rtugeek/clock/master/public/images/preview_micky_clock.png)  <br/>米奇时钟 |\n\n### 相册组件包\n\n|                                                   相册组件包                                                   |                                      https://github.com/rtugeek/photo                                       |\n|:---------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------:|\n| ![大头贴](https://raw.githubusercontent.com/rtugeek/photo/master/public/images/preview_sticker.png) <br/>大头贴 | ![Clock](https://raw.githubusercontent.com/rtugeek/photo/master/public/images/preview_photo.png)  <br/>轮播相册 |\n\n### 趣味组件包\n\n|                                                     趣味组件包                                                     |                                                  https://github.com/rtugeek/fun                                                  |\n|:-------------------------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------:|\n| ![电子木鱼](https://raw.githubusercontent.com/rtugeek/fun/master/public/images/preview_wooden_fish.png) <br/>电子木鱼 | ![preview_hitler.png](https://raw.githubusercontent.com/rtugeek/fun/refs/heads/master/public/images/preview_hitler.png) <br/>仪表盘 |\n\n\n"
  },
  {
    "path": "components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": false,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"src/index.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"iconLibrary\": \"lucide\",\n  \"rtl\": false,\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\",\n    \"ui\": \"@/components/ui\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"registries\": {}\n}\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import antfu from '@antfu/eslint-config'\n\nexport default antfu({\n  type: 'lib',\n  stylistic: {\n    indent: 2, // 4, or 'tab'\n    quotes: 'single', // or 'double'\n  },\n  typescript: true,\n  react: true,\n  jsx: true,\n  vue: false,\n  jsonc: false,\n  yaml: false,\n  markdown: false,\n  ignores: [\n    '**/fixtures',\n    'src/components/ui/**',\n  ],\n  rules: {\n    'curly': ['error', 'multi-line'],\n    'no-use-before-define': 'off',\n    'eqeqeq': 'off',\n    'unused-imports/no-unused-vars': ['error', {\n      caughtErrors: 'none',\n      argsIgnorePattern: '^_',\n      varsIgnorePattern: '^_',\n    }],\n    'ts/explicit-function-return-type': 'off',\n    'style/max-statements-per-line': ['error', {\n      max: 2,\n    }],\n  },\n})\n"
  },
  {
    "path": "index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Widget Hub</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@widget-js/react-app\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"preview\": \"vite preview\",\n    \"lint\": \"eslint --cache .\",\n    \"lint:fix\": \"eslint --cache . --fix\",\n    \"prepare\": \"simple-git-hooks\"\n  },\n  \"dependencies\": {\n    \"@icons-pack/react-simple-icons\": \"^13.11.2\",\n    \"@radix-ui/react-checkbox\": \"^1.3.3\",\n    \"@radix-ui/react-popover\": \"^1.1.15\",\n    \"@supabase/auth-js\": \"^2.86.0\",\n    \"@supabase/supabase-js\": \"^2.84.0\",\n    \"@tailwindcss/vite\": \"^4.2.0\",\n    \"@uiw/react-color\": \"^2.9.5\",\n    \"@widget-js/core\": \"latest\",\n    \"@widget-js/react\": \"latest\",\n    \"@widget-js/web-api\": \"24.1.1-beta.70\",\n    \"axios\": \"^1.13.5\",\n    \"baseline-browser-mapping\": \"^2.10.0\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"cmdk\": \"^1.1.1\",\n    \"consola\": \"^3.4.2\",\n    \"driver.js\": \"^1.4.0\",\n    \"framer-motion\": \"^12.34.3\",\n    \"i18next\": \"^25.8.13\",\n    \"i18next-browser-languagedetector\": \"^8.2.1\",\n    \"lucide-react\": \"^0.575.0\",\n    \"next-themes\": \"^0.4.6\",\n    \"qrcode.react\": \"^4.2.0\",\n    \"radix-ui\": \"^1.4.3\",\n    \"react\": \"^19.2.0\",\n    \"react-dom\": \"^19.2.0\",\n    \"react-dropzone\": \"^15.0.0\",\n    \"react-i18next\": \"^16.5.4\",\n    \"react-medium-image-zoom\": \"^5.4.1\",\n    \"react-router-dom\": \"^7.1.5\",\n    \"react-use\": \"^17.6.0\",\n    \"semver\": \"^7.7.4\",\n    \"sonner\": \"^2.0.7\",\n    \"tailwind-merge\": \"^3.5.0\",\n    \"tailwindcss\": \"^4.2.0\"\n  },\n  \"devDependencies\": {\n    \"@antfu/eslint-config\": \"^7.6.1\",\n    \"@eslint-react/eslint-plugin\": \"^2.13.0\",\n    \"@types/node\": \"^24.10.13\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"@types/semver\": \"^7.7.1\",\n    \"@vitejs/plugin-react\": \"^5.1.1\",\n    \"@widget-js/vite-plugin-widget\": \"24.1.1-beta.72\",\n    \"eslint\": \"^10.0.2\",\n    \"eslint-plugin-format\": \"^2.0.1\",\n    \"eslint-plugin-react-hooks\": \"^7.0.1\",\n    \"eslint-plugin-react-refresh\": \"^0.4.26\",\n    \"eslint-plugin-unicorn\": \"^63.0.0\",\n    \"globals\": \"^16.5.0\",\n    \"lint-staged\": \"^16.3.0\",\n    \"shadcn\": \"^3.8.5\",\n    \"simple-git-hooks\": \"^2.13.1\",\n    \"tw-animate-css\": \"^1.4.0\",\n    \"typescript\": \"~5.9.3\",\n    \"vite\": \"^7.3.1\"\n  },\n  \"simple-git-hooks\": {\n    \"pre-commit\": \"npx lint-staged\"\n  },\n  \"lint-staged\": {\n    \"*.{js,ts,tsx,vue,md}\": [\n      \"eslint --cache --fix\"\n    ]\n  }\n}\n"
  },
  {
    "path": "src/api/ai.ts",
    "content": "import request from '@/lib/request'\n\nexport interface AiTokenHistory {\n  id: string\n  user_id: string\n  change_amount: number\n  token_remain_after: number | null\n  model: string | null\n  prompt_tokens: number | null\n  completion_tokens: number | null\n  total_tokens: number | null\n  cost: number | null\n  request_type: string | null\n  input_token_details: any | null\n  output_token_details: any | null\n  raw_usage: any | null\n  related_id: string | null\n  remark: string | null\n  create_time: string\n  sourcePackage?: string | null\n  sourcePackageName?: string | null\n}\n\nexport interface AiTokenPackage {\n  id: string\n  name: string\n  userId: string\n  maxToken: number\n  usedToken: number\n  expireTime: string | null\n  createTime: string\n  productId: number | null\n  enable: boolean | null\n  updateTime: string | null\n}\n\ninterface PaginationResult<T> {\n  data: T[]\n  page: string | number\n  pageSize: string | number\n  totalPage: number\n  total: number\n}\n\nexport const AiApi = {\n  async getPackages(params?: { page?: number, limit?: number }): Promise<{ items: AiTokenPackage[], total: number }> {\n    const res = await request.get('/ai/package', { params })\n    const data = res as unknown as PaginationResult<AiTokenPackage>\n    return {\n      items: data.data,\n      total: data.total,\n    }\n  },\n\n  async getUsage(params?: { page?: number, limit?: number }): Promise<{ items: AiTokenHistory[], total: number }> {\n    const res = await request.get('/ai/usage', { params })\n    const data = res as unknown as PaginationResult<AiTokenHistory>\n    return {\n      items: data.data,\n      total: data.total,\n    }\n  },\n}\n"
  },
  {
    "path": "src/api/pay.ts",
    "content": "import request from '@/lib/request'\n\nexport interface PayVirtualProduct {\n  id: number\n  createTime: string\n  updateTime: string | null\n  name: string\n  description: string | null\n  coverUrl: string | null\n  price: number\n  category: string\n  metadata: string | null\n  enable: boolean\n}\n\nexport interface WxOrderResult {\n  codeUrl: string\n  product: PayVirtualProduct\n  orderId: string\n}\n\nexport interface AlipayOrderResult {\n  orderId: string\n  product: PayVirtualProduct\n  form: string\n}\n\nexport const PayApi = {\n  async getProducts(category: string = 'ai'): Promise<PayVirtualProduct[]> {\n    return request.get('https://widgetjs.cn/pay/products', {\n      params: { category },\n    })\n  },\n\n  async createWxOrder(productId: number): Promise<WxOrderResult> {\n    return request.get('https://widgetjs.cn/pay/wx/order', {\n      params: { productId },\n    })\n  },\n\n  async createAlipayOrder(productId: number, returnUrl: string = 'https://widgetjs.cn'): Promise<AlipayOrderResult> {\n    return request.post('https://widgetjs.cn/pay/alipay/order/page', null, {\n      params: { productId, returnUrl },\n    })\n  },\n\n  async getAlipayUrl(orderId: string): Promise<string> {\n    return `https://widgetjs.cn/pay/alipay/order/page?orderId=${orderId}`\n  },\n}\n"
  },
  {
    "path": "src/api/supabase.ts",
    "content": "import { createClient } from '@supabase/supabase-js'\n\nconst anonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzY0MDAwMDAwLCJleHAiOjE5MjE3NjY0MDB9.3nGFAW2q2bzxWmx1T-ycnmklITh9OcEvA1kZPXz4dBs'\nconst supabaseUrl = 'https://supabase.widgetjs.cn'\nconst supabase = createClient(supabaseUrl, anonKey)\n\nfunction getStorageLink(fullPath: string) {\n  return `https://supabase.widgetjs.cn/storage/v1/object/public/${fullPath}`\n}\nexport { getStorageLink, supabase }\n"
  },
  {
    "path": "src/api/web-widget-api.ts",
    "content": "import type { WidgetSearchOptions } from '@widget-js/web-api'\nimport request from '@/lib/request'\n\nexport const WebWidgetApi = {\n  search: (options: WidgetSearchOptions) => {\n    return request.get('/widget', {\n      params: options,\n    })\n  },\n}\n"
  },
  {
    "path": "src/app.css",
    "content": "/* App.css cleared for Tailwind compatibility */\n*{\n  /*user-select: none;*/\n}\n/* Global Scrollbar Styling matching src/components/ui/scroll-area.tsx */\n::-webkit-scrollbar {\n  width: 10px;\n  height: 10px;\n}\n\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  background-color: var(--border);\n  border-radius: 9999px;\n  border: 1px solid transparent;\n  background-clip: content-box;\n}\n\n::-webkit-scrollbar-corner {\n  background: transparent;\n}\n"
  },
  {
    "path": "src/app.tsx",
    "content": "import consola from 'consola'\nimport { useTranslation } from 'react-i18next'\nimport { RouterProvider } from 'react-router-dom'\nimport { useAppLanguage } from './hooks/use-app-language'\nimport { router } from './router'\nimport '@widget-js/react/style.css'\nimport './app.css'\n\nfunction App() {\n  const { i18n } = useTranslation()\n  useAppLanguage({\n    onLoad: (lang) => {\n      consola.log('App language loaded:', lang)\n      i18n.changeLanguage(lang)\n    },\n    onChange: (lang) => {\n      consola.log('App language onChange:', lang)\n      i18n.changeLanguage(lang)\n    },\n  })\n\n  return (\n    <RouterProvider router={router} />\n  )\n}\n\nexport default App\n"
  },
  {
    "path": "src/components/app-sidebar.tsx",
    "content": "'use client'\n\nimport {\n  User,\n} from 'lucide-react'\nimport * as React from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useLocation, useNavigate } from 'react-router-dom'\nimport logo from '@/assets/images/logo.png'\nimport { NavMain } from '@/components/nav-main'\nimport { NavUser } from '@/components/nav-user'\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarHeader,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarRail,\n} from '@/components/ui/sidebar'\nimport { useAppRuntimeInfo } from '@/hooks/use-app-runtime-info'\nimport { useUser } from '@/hooks/use-user'\nimport { routes } from '@/router'\n\ninterface RouteHandle {\n  title: string\n  icon?: any\n  sidebarGroup?: boolean\n  sidebarMenu?: boolean\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n  const { t } = useTranslation()\n  const location = useLocation()\n  const navigate = useNavigate()\n  const { simpleInfo } = useAppRuntimeInfo()\n  const { user, nickname, avatar } = useUser()\n\n  const userData = {\n    name: nickname,\n    email: user?.email ?? '',\n    avatar: user ? (avatar || logo) : undefined,\n    icon: !user ? User : undefined,\n  }\n\n  const rootRoute = routes[0]\n\n  const sidebarGroups = rootRoute.children\n    ?.filter(route => (route.handle as RouteHandle | undefined)?.sidebarGroup)\n    .map((route) => {\n      const handle = route.handle as RouteHandle\n      // Handle nested routes logic\n      const groupPath = route.path === '/' ? '' : route.path || ''\n\n      const items = route.children\n        ?.filter(child => (child.handle as RouteHandle | undefined)?.sidebarMenu)\n        .map((child) => {\n          const childHandle = child.handle as RouteHandle\n          // Handle child path: '/' as root of group\n          const childPath = child.path === '/' ? '' : child.path || ''\n          const url = `/${groupPath}${childPath ? `/${childPath}` : ''}`.replace(/\\/+/g, '/')\n\n          return {\n            title: t(childHandle.title),\n            url,\n            icon: childHandle.icon,\n            isActive: location.pathname === url,\n          }\n        }) || []\n\n      return {\n        label: t(handle.title),\n        items,\n      }\n    }) || []\n\n  return (\n    <>\n      <Sidebar collapsible=\"none\" {...props}>\n        <SidebarHeader>\n          <SidebarMenu>\n            <SidebarMenuItem>\n              <SidebarMenuButton\n                size=\"lg\"\n                onClick={() => navigate('/setting/info')}\n                className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n              >\n                <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground\">\n                  <img src={logo} alt=\"Widget Hub\" className=\"size-8\" />\n                </div>\n                <div className=\"grid flex-1 text-left text-sm leading-tight\">\n                  <span className=\"truncate font-semibold\">{t('sidebar.widgetHub')}</span>\n                  <span className=\"truncate text-xs\">{(simpleInfo?.app as any)?.version ?? (simpleInfo?.app ? String(simpleInfo.app) : t('sidebar.loading'))}</span>\n                </div>\n              </SidebarMenuButton>\n            </SidebarMenuItem>\n          </SidebarMenu>\n        </SidebarHeader>\n        <SidebarContent>\n          {sidebarGroups.map((group, index) => (\n            <NavMain key={index} label={group.label} items={group.items} />\n          ))}\n        </SidebarContent>\n        <SidebarFooter>\n          <NavUser user={userData} />\n        </SidebarFooter>\n        <SidebarRail />\n      </Sidebar>\n    </>\n  )\n}\n"
  },
  {
    "path": "src/components/dashboard-layout.tsx",
    "content": "import { WindowControls } from '@widget-js/react'\nimport { useTranslation } from 'react-i18next'\nimport { Outlet, useMatches } from 'react-router-dom'\nimport { AppSidebar } from '@/components/app-sidebar'\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbList,\n  BreadcrumbPage,\n} from '@/components/ui/breadcrumb'\nimport {\n  SidebarInset,\n  SidebarProvider,\n} from '@/components/ui/sidebar'\n\nexport function DashboardLayout() {\n  const { t } = useTranslation()\n  const matches = useMatches()\n\n  // Get title from the last match that has a handle with a title\n  const currentMatch = matches.findLast(match => (match.handle as any)?.title)\n  const titleKey = (currentMatch?.handle as any)?.title\n  const title = titleKey ? t(titleKey) : t('dashboard.dashboard')\n\n  return (\n    <SidebarProvider>\n      <AppSidebar />\n      <SidebarInset className=\"overflow-hidden\">\n        <header className=\"draggable-region flex h-[54px] shrink-0 items-center justify-between gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12\">\n          <div className=\"flex items-center gap-2 px-4\">\n            <Breadcrumb>\n              <BreadcrumbList>\n                <BreadcrumbItem>\n                  <BreadcrumbPage>{title}</BreadcrumbPage>\n                </BreadcrumbItem>\n              </BreadcrumbList>\n            </Breadcrumb>\n          </div>\n          <WindowControls />\n        </header>\n        <div className=\"flex flex-1 flex-col gap-4 pt-0 overflow-y-auto\">\n          <Outlet />\n        </div>\n      </SidebarInset>\n    </SidebarProvider>\n  )\n}\n"
  },
  {
    "path": "src/components/login-check.tsx",
    "content": "import { BrowserWindowApi, NotificationApi } from '@widget-js/core'\nimport consola from 'consola'\nimport { Lock } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { toast } from 'sonner'\nimport { supabase } from '@/api/supabase'\nimport { Button } from '@/components/ui/button'\nimport {\n  Empty,\n  EmptyContent,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyMedia,\n  EmptyTitle,\n} from '@/components/ui/empty'\nimport { useSupabaseChannel } from '@/hooks/use-supabase-channel'\nimport { useUser } from '@/hooks/use-user'\n\ninterface LoginCheckProps {\n  children: React.ReactNode\n}\n\nexport function LoginCheck({ children }: LoginCheckProps) {\n  const { user, loading: userLoading } = useUser()\n  const [loginState, setLoginState] = useState('')\n\n  useEffect(() => {\n    const storedState = localStorage.getItem('wechat_login_state')\n    if (storedState) { setLoginState(storedState) }\n  }, [])\n\n  // Handle Supabase channel for login\n  useSupabaseChannel(loginState ? `wechat-login-${loginState}` : '', async (payload: any) => {\n    consola.info(payload)\n    const currentSession = payload.payload.session\n    const loginRes = await supabase.auth.setSession(currentSession)\n    if (loginRes.error) {\n      NotificationApi.error(loginRes.error.message)\n    }\n    else {\n      toast.success('登录成功')\n    }\n  })\n\n  const handleLogin = () => {\n    const newState = crypto.randomUUID().replace(/-/g, '')\n    setLoginState(newState)\n    localStorage.setItem('wechat_login_state', newState)\n\n    BrowserWindowApi.openUrl(`https://open.weixin.qq.com/connect/qrconnect?appid=wxf91b19da281f23a9&redirect_uri=https%3A%2F%2Fwidgetjs.cn%2Fapi%2Fv1%2Fuser%2Flogin%2Fwechat%2Fcallback&response_type=code&scope=snsapi_login&state=${newState}#wechat_redirect`, {\n      width: 800,\n      height: 600,\n      frame: true,\n      transparent: false,\n      titleBarStyle: 'default',\n    })\n  }\n\n  if (!user && !userLoading) {\n    return (\n      <div className=\"flex flex-col items-center justify-center h-[60vh]\">\n        <Empty>\n          <EmptyHeader>\n            <EmptyMedia variant=\"icon\">\n              <Lock className=\"h-6 w-6\" />\n            </EmptyMedia>\n            <EmptyTitle>需要登录</EmptyTitle>\n            <EmptyDescription>\n              请先登录账号以继续使用\n            </EmptyDescription>\n          </EmptyHeader>\n          <EmptyContent>\n            <Button onClick={handleLogin}>\n              立即登录\n            </Button>\n          </EmptyContent>\n        </Empty>\n      </div>\n    )\n  }\n\n  return <>{children}</>\n}\n"
  },
  {
    "path": "src/components/manager/deployed-widget-card.tsx",
    "content": "import type { DeployedWidget, Widget } from '@widget-js/core'\nimport { DeployedWidgetApi, WidgetApi } from '@widget-js/core'\nimport { Code, RefreshCw, Settings, X } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from '@/components/ui/alert-dialog'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Checkbox } from '@/components/ui/checkbox'\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from '@/components/ui/tooltip'\nimport { useDebugConfig } from '@/hooks/use-debug-config'\n\ninterface DeployedWidgetCardProps {\n  deployedWidget: DeployedWidget\n  onRemove: (widget: DeployedWidget) => void\n}\n\nexport function DeployedWidgetCard({ deployedWidget, onRemove }: DeployedWidgetCardProps) {\n  const { t, i18n } = useTranslation()\n  const [widget, setWidget] = useState<Widget>()\n  const [ignoreMouseEvents, setIgnoreMouseEvents] = useState(deployedWidget.isIgnoreMouseEvents ?? false)\n  const [isDebugMode] = useDebugConfig()\n\n  useEffect(() => {\n    WidgetApi.getWidget(deployedWidget.name).then(setWidget)\n  }, [deployedWidget.name])\n\n  const getLocalizedText = (text: any) => {\n    if (typeof text === 'string') { return text }\n    if (!text) { return '' }\n    return text[i18n.language] || text['zh-CN'] || Object.values(text)[0] || ''\n  }\n\n  const openSettings = () => {\n    if (widget?.configPagePath) {\n      DeployedWidgetApi.openConfigPage(deployedWidget.id)\n    }\n  }\n\n  const openDevTools = () => {\n    DeployedWidgetApi.openDevTools(deployedWidget.id)\n  }\n\n  const refresh = () => {\n    WidgetApi.reload(deployedWidget.id)\n  }\n\n  const onIgnoreMouseEventChange = (checked: boolean) => {\n    setIgnoreMouseEvents(checked)\n    WidgetApi.setIgnoreMouseEvents(deployedWidget.id, checked)\n  }\n\n  return (\n    <Card className=\"shadow-none bg-secondary/20 group relative gap-0 p-0\">\n      <CardHeader className=\"p-2 pb-2\">\n        <div className=\"flex flex-col gap-1 overflow-hidden flex-1 text-left\">\n          {widget && (\n            <CardTitle className=\"text-sm\">\n              {getLocalizedText(widget.title)}\n            </CardTitle>\n          )}\n          <CardDescription className=\"text-sm flex gap-2\">\n            {deployedWidget.name}\n          </CardDescription>\n        </div>\n      </CardHeader>\n      <CardFooter className=\"p-2 flex flex-col gap-3 bg-muted/40 rounded-md\">\n\n        <div className=\"flex w-full items-center gap-2\">\n          <div className=\"flex items-center space-x-2 mr-auto\">\n            <Checkbox\n              id={`ignore-mouse-${deployedWidget.id}`}\n              checked={ignoreMouseEvents}\n              onCheckedChange={onIgnoreMouseEventChange}\n            />\n            <label\n              htmlFor={`ignore-mouse-${deployedWidget.id}`}\n              className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n            >\n              鼠标穿透\n            </label>\n          </div>\n\n          {isDebugMode && (\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\" onClick={openDevTools}>\n                  <Code className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent>\n                <p>DevTools</p>\n              </TooltipContent>\n            </Tooltip>\n          )}\n\n          <Tooltip>\n            <TooltipTrigger asChild>\n              <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\" onClick={refresh}>\n                <RefreshCw className=\"h-4 w-4\" />\n              </Button>\n            </TooltipTrigger>\n            <TooltipContent>\n              <p>{t('tray.restartWidgets')}</p>\n            </TooltipContent>\n          </Tooltip>\n\n          {widget?.configPagePath && (\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\" onClick={openSettings}>\n                  <Settings className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent>\n                <p>{t('settings.title')}</p>\n              </TooltipContent>\n            </Tooltip>\n          )}\n\n          <AlertDialog>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <AlertDialogTrigger asChild>\n                  <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8 hover:text-destructive\">\n                    <X className=\"h-4 w-4\" />\n                  </Button>\n                </AlertDialogTrigger>\n              </TooltipTrigger>\n              <TooltipContent>\n                <p>{t('manager.remove')}</p>\n              </TooltipContent>\n            </Tooltip>\n            <AlertDialogContent>\n              <AlertDialogHeader>\n                <AlertDialogTitle>{t('manager.confirmRemove')}</AlertDialogTitle>\n                <AlertDialogDescription>\n                  {t('manager.removeDesc', { name: widget ? getLocalizedText(widget.title) : deployedWidget.name })}\n                </AlertDialogDescription>\n              </AlertDialogHeader>\n              <AlertDialogFooter>\n                <AlertDialogCancel>{t('settings.widgetPackage.cancel')}</AlertDialogCancel>\n                <AlertDialogAction onClick={() => onRemove(deployedWidget)}>\n                  {t('settings.widgetPackage.confirm')}\n                </AlertDialogAction>\n              </AlertDialogFooter>\n            </AlertDialogContent>\n          </AlertDialog>\n        </div>\n      </CardFooter>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "src/components/manager/deployed-widget-list.tsx",
    "content": "import type { DeployedWidget } from '@widget-js/core'\nimport { BrowserWindowApi, DeployedWidgetApi } from '@widget-js/core'\nimport { useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { DeployedWidgetCard } from './deployed-widget-card'\n\nexport default function DeployedWidgetList() {\n  const [widgets, setWidgets] = useState<DeployedWidget[]>([])\n  const { t } = useTranslation()\n\n  const refreshWidgets = () => {\n    DeployedWidgetApi.getDeployedWidgets().then(setWidgets)\n  }\n\n  useEffect(() => {\n    refreshWidgets()\n    BrowserWindowApi.setAlwaysOnTop(true)\n\n    // Listen for window focus to refresh list\n    window.addEventListener('focus', refreshWidgets)\n    return () => {\n      window.removeEventListener('focus', refreshWidgets)\n    }\n  }, [])\n\n  const removeWidget = async (widget: DeployedWidget) => {\n    await DeployedWidgetApi.removeDeployedWidget(widget.id)\n    setWidgets(prev => prev.filter(w => w.id !== widget.id))\n  }\n\n  return (\n    <div className=\"flex flex-col gap-2\">\n      {widgets.map(widget => (\n        <DeployedWidgetCard\n          key={widget.id}\n          deployedWidget={widget}\n          onRemove={() => removeWidget(widget)}\n        />\n      ))}\n      {widgets.length === 0 && (\n        <div className=\"text-center text-muted-foreground text-xs py-4\">\n          {t('tray.runningWidgets')}\n          {' '}\n          (0)\n        </div>\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/components/nav-main.tsx",
    "content": "import type { LucideIcon } from 'lucide-react'\nimport { Link } from 'react-router-dom'\n\nimport {\n  SidebarGroup,\n  SidebarGroupLabel,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from '@/components/ui/sidebar'\n\nexport function NavMain({\n  label,\n  items,\n}: {\n  label: string\n  items: {\n    title: string\n    url: string\n    icon: LucideIcon\n    isActive?: boolean\n  }[]\n}) {\n  return (\n    <SidebarGroup>\n      <SidebarGroupLabel>{label}</SidebarGroupLabel>\n      <SidebarMenu>\n        {items.map(item => (\n          <SidebarMenuItem key={item.url}>\n            <SidebarMenuButton asChild tooltip={item.title} isActive={item.isActive}>\n              <Link to={item.url}>\n                {item.icon && <item.icon />}\n                <span>{item.title}</span>\n              </Link>\n            </SidebarMenuButton>\n          </SidebarMenuItem>\n        ))}\n      </SidebarMenu>\n    </SidebarGroup>\n  )\n}\n"
  },
  {
    "path": "src/components/nav-user.tsx",
    "content": "import type { LucideIcon } from 'lucide-react'\nimport { BrowserWindowApi, NotificationApi } from '@widget-js/core'\nimport consola from 'consola'\nimport {\n  ChevronsUpDown,\n\n} from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { useNavigate } from 'react-router-dom'\n\nimport { supabase } from '@/api/supabase'\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from '@/components/ui/sidebar'\nimport UserAvatar from '@/components/user-avatar'\nimport { useSupabaseChannel } from '@/hooks/use-supabase-channel'\n\nexport function NavUser({\n  user,\n}: {\n  user: {\n    name: string\n    email: string\n    avatar?: string\n    icon?: LucideIcon\n  }\n}) {\n  const navigate = useNavigate()\n  const [loginState, setLoginState] = useState('')\n\n  useEffect(() => {\n    const storedState = localStorage.getItem('wechat_login_state')\n    if (storedState) { setLoginState(storedState) }\n  }, [])\n\n  // Handle Supabase channel for login\n  useSupabaseChannel(loginState ? `wechat-login-${loginState}` : '', async (payload: any) => {\n    consola.info(payload)\n    const currentSession = payload.payload.session\n    const loginRes = await supabase.auth.setSession(currentSession)\n    if (loginRes.error) {\n      NotificationApi.error(loginRes.error.message)\n    }\n    else {\n      navigate('/user/profile')\n    }\n  })\n\n  const handleLogin = () => {\n    const newState = crypto.randomUUID().replace(/-/g, '')\n    setLoginState(newState)\n    localStorage.setItem('wechat_login_state', newState)\n\n    BrowserWindowApi.openUrl(`https://open.weixin.qq.com/connect/qrconnect?appid=wxf91b19da281f23a9&redirect_uri=https%3A%2F%2Fwidgetjs.cn%2Fapi%2Fv1%2Fuser%2Flogin%2Fwechat%2Fcallback&response_type=code&scope=snsapi_login&state=${newState}#wechat_redirect`, {\n      width: 800,\n      height: 600,\n      frame: true,\n      transparent: false,\n      titleBarStyle: 'default',\n    })\n  }\n\n  const handleClick = () => {\n    if (user.email) {\n      navigate('/user/profile')\n    }\n    else {\n      handleLogin()\n    }\n  }\n\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <SidebarMenuButton\n          size=\"lg\"\n          className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n          onClick={handleClick}\n        >\n          <UserAvatar\n            src={user.avatar}\n            alt={user.name}\n            className=\"h-8 w-8 rounded-lg bg-sidebar-primary text-sidebar-primary-foreground\"\n          />\n          <div className=\"grid flex-1 text-left text-sm leading-tight\">\n            <span className=\"truncate font-medium\">{user.name}</span>\n          </div>\n          <ChevronsUpDown className=\"ml-auto size-4\" />\n        </SidebarMenuButton>\n      </SidebarMenuItem>\n    </SidebarMenu>\n  )\n}\n"
  },
  {
    "path": "src/components/purchase-dialog.tsx",
    "content": "import type { AlipayOrderResult, PayVirtualProduct } from '@/api/pay'\nimport { SiAlipay } from '@icons-pack/react-simple-icons'\nimport { BrowserWindowApi } from '@widget-js/core'\nimport consola from 'consola'\nimport { useEffect, useState } from 'react'\nimport { toast } from 'sonner'\nimport { PayApi } from '@/api/pay'\nimport { supabase } from '@/api/supabase'\nimport { Button } from '@/components/ui/button'\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n} from '@/components/ui/dialog'\nimport { Spinner } from '@/components/ui/spinner'\n\ninterface PurchaseDialogProps {\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  productId?: number\n  onSuccess?: () => void\n}\n\nexport function PurchaseDialog({ open, onOpenChange, productId = 2, onSuccess }: PurchaseDialogProps) {\n  const [loading, setLoading] = useState(false)\n  const [product, setProduct] = useState<PayVirtualProduct | null>(null)\n  const [order, setOrder] = useState<AlipayOrderResult | null>(null)\n  const [payUrl, setPayUrl] = useState<string>('')\n\n  useEffect(() => {\n    if (!order) { return }\n\n    const channel = supabase\n      .channel('table-db-changes')\n      .on(\n        'postgres_changes',\n        {\n          event: 'INSERT',\n          schema: 'public',\n          table: 'ai_token_packages',\n          filter: `order_id=eq.${order.orderId}`,\n        },\n        (payload) => {\n          consola.log('Payment successful:', payload)\n          toast.success('支付成功！')\n          onOpenChange(false)\n          onSuccess?.()\n        },\n      )\n      .subscribe()\n\n    return () => {\n      supabase.removeChannel(channel)\n    }\n  }, [order])\n\n  const initPurchase = async () => {\n    try {\n      setLoading(true)\n      setProduct(null)\n      setOrder(null)\n      setPayUrl('')\n\n      // 1. Get Product Info\n      const products = await PayApi.getProducts('ai')\n      const targetProduct = products.find(p => p.id === productId)\n\n      if (!targetProduct) {\n        toast.error('商品不存在')\n        onOpenChange(false)\n        return\n      }\n      setProduct(targetProduct)\n\n      // 2. Create Order\n      const orderRes = await PayApi.createAlipayOrder(targetProduct.id, window.location.href)\n      setOrder(orderRes)\n      const url = await PayApi.getAlipayUrl(orderRes.orderId)\n      setPayUrl(url)\n    }\n    catch (error) {\n      console.error(error)\n      toast.error('获取购买信息失败')\n      onOpenChange(false)\n    }\n    finally {\n      setLoading(false)\n    }\n  }\n\n  useEffect(() => {\n    if (open) {\n      initPurchase()\n    }\n    else {\n      // Clean up when closed\n      setOrder(null)\n    }\n  }, [open, productId])\n  return (\n    <Dialog open={open} onOpenChange={onOpenChange}>\n      <DialogContent className=\"sm:max-w-md\">\n        <DialogHeader>\n          <DialogTitle>购买套餐</DialogTitle>\n        </DialogHeader>\n        <div className=\"flex flex-col items-center justify-center p-4 gap-6\">\n          {loading\n            ? (\n                <div className=\"flex flex-col items-center gap-2 py-8\">\n                  <Spinner className=\"h-8 w-8\" />\n                  <p className=\"text-sm text-muted-foreground\">正在创建订单...</p>\n                </div>\n              )\n            : product && order\n              ? (\n                  <>\n                    <div className=\"text-center space-y-2\">\n                      <h3 className=\"font-semibold text-lg\">{product.name}</h3>\n                      <p className=\"text-2xl font-bold text-primary\">\n                        ¥\n                        {(product.price / 100).toFixed(2)}\n                      </p>\n                      {product.description && (\n                        <p className=\"text-sm text-muted-foreground\">{product.description}</p>\n                      )}\n                    </div>\n\n                    <div className=\"flex flex-col items-center gap-4 pt-4 w-full\">\n                      <Button\n                        onClick={() => BrowserWindowApi.openUrl(payUrl, { external: true })}\n                        className=\"w-full bg-[#1677FF] hover:bg-[#1677FF]/90 text-white\"\n                      >\n                        <SiAlipay className=\"mr-2 h-4 w-4\" />\n                        前往支付宝支付\n                      </Button>\n                      <div className=\"text-sm text-muted-foreground\">\n                        支付完成后，本窗口会自动刷新\n                      </div>\n                    </div>\n                  </>\n                )\n              : (\n                  <div className=\"py-8 text-muted-foreground\">\n                    加载失败，请重试\n                  </div>\n                )}\n        </div>\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "src/components/setting-section.tsx",
    "content": "import type { ReactNode } from 'react'\nimport { Separator } from '@/components/ui/separator'\n\ninterface SettingSectionProps {\n  title: string\n  children: ReactNode\n}\n\nexport function SettingSection({ title, children }: SettingSectionProps) {\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"space-y-1\">\n        <h4 className=\"text-sm font-medium leading-none\">{title}</h4>\n        <Separator className=\"my-2\" />\n      </div>\n      <div className=\"space-y-4 pl-1\">\n        {children}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/components/team-switcher.tsx",
    "content": "'use client'\n\nimport { ChevronsUpDown, Plus } from 'lucide-react'\nimport * as React from 'react'\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from '@/components/ui/sidebar'\n\nexport function TeamSwitcher({\n  teams,\n}: {\n  teams: {\n    name: string\n    logo: React.ElementType\n    plan: string\n  }[]\n}) {\n  const { isMobile } = useSidebar()\n  const [activeTeam, setActiveTeam] = React.useState(teams[0])\n\n  if (!activeTeam) {\n    return null\n  }\n\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <DropdownMenu>\n          <DropdownMenuTrigger asChild>\n            <SidebarMenuButton\n              size=\"lg\"\n              className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n            >\n              <div className=\"bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg\">\n                <activeTeam.logo className=\"size-4\" />\n              </div>\n              <div className=\"grid flex-1 text-left text-sm leading-tight\">\n                <span className=\"truncate font-medium\">{activeTeam.name}</span>\n                <span className=\"truncate text-xs\">{activeTeam.plan}</span>\n              </div>\n              <ChevronsUpDown className=\"ml-auto\" />\n            </SidebarMenuButton>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            className=\"w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg\"\n            align=\"start\"\n            side={isMobile ? 'bottom' : 'right'}\n            sideOffset={4}\n          >\n            <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n              Teams\n            </DropdownMenuLabel>\n            {teams.map((team, index) => (\n              <DropdownMenuItem\n                key={team.name}\n                onClick={() => setActiveTeam(team)}\n                className=\"gap-2 p-2\"\n              >\n                <div className=\"flex size-6 items-center justify-center rounded-md border\">\n                  <team.logo className=\"size-3.5 shrink-0\" />\n                </div>\n                {team.name}\n                <DropdownMenuShortcut>\n                  ⌘\n                  {index + 1}\n                </DropdownMenuShortcut>\n              </DropdownMenuItem>\n            ))}\n            <DropdownMenuSeparator />\n            <DropdownMenuItem className=\"gap-2 p-2\">\n              <div className=\"flex size-6 items-center justify-center rounded-md border bg-transparent\">\n                <Plus className=\"size-4\" />\n              </div>\n              <div className=\"text-muted-foreground font-medium\">Add team</div>\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </SidebarMenuItem>\n    </SidebarMenu>\n  )\n}\n"
  },
  {
    "path": "src/components/tray/social-links.tsx",
    "content": "import { AppApi, NotificationApi } from '@widget-js/core'\nimport { useCallback, useRef } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport bilibili from '@/assets/images/bilibili_logo_red.png'\nimport douyin from '@/assets/images/douyin.png'\nimport githubMark from '@/assets/images/github-mark.png'\nimport logo from '@/assets/images/logo.png'\nimport qq from '@/assets/images/qq.png'\n\ninterface SocialLinksProps {\n  iconSize?: number\n}\n\nfunction useLongPress(callback: () => void, delay: number = 3000) {\n  const timeoutRef = useRef<number | null>(null)\n\n  const start = useCallback(() => {\n    timeoutRef.current = window.setTimeout(() => {\n      callback()\n    }, delay)\n  }, [callback, delay])\n\n  const clear = useCallback(() => {\n    if (timeoutRef.current) {\n      clearTimeout(timeoutRef.current)\n      timeoutRef.current = null\n    }\n  }, [])\n\n  return {\n    onMouseDown: start,\n    onMouseUp: clear,\n    onMouseLeave: clear,\n    onTouchStart: start,\n    onTouchEnd: clear,\n  }\n}\n\nexport function SocialLinks({ iconSize = 20 }: SocialLinksProps) {\n  const { t } = useTranslation()\n\n  const handleEnableDevMode = useCallback(async () => {\n    NotificationApi.info(t('notification.enableDevMode'))\n    await AppApi.setConfig('debug', true)\n  }, [t])\n\n  const longPressProps = useLongPress(handleEnableDevMode, 3000)\n\n  const links = [\n    {\n      name: 'Widget',\n      url: 'https://widgetjs.cn/',\n      icon: logo,\n    },\n    {\n      name: 'TikTok',\n      url: 'https://v.douyin.com/YhuNAb8/',\n      icon: douyin,\n    },\n    {\n      name: 'QQ',\n      url: 'https://jq.qq.com/?_wv=1027&k=TgO2mUQe',\n      icon: qq,\n    },\n    {\n      name: 'Bilibili',\n      url: 'https://space.bilibili.com/207395767',\n      icon: bilibili,\n    },\n    {\n      name: 'GitHub',\n      url: 'https://github.com/widget-js/widgets',\n      icon: githubMark,\n      ...longPressProps, // Apply long press to GitHub only\n    },\n  ]\n\n  return (\n    <div className=\"flex items-center justify-start gap-[0.8rem]\">\n      {links.map(link => (\n        <a\n          key={link.name}\n          href={link.url}\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"text-primary no-underline leading-none hover:opacity-80 transition-opacity\"\n          {...(link.name === 'GitHub' ? longPressProps : {})}\n        >\n          <img\n            src={link.icon}\n            alt={link.name}\n            style={{ width: `${iconSize}px`, height: `${iconSize}px` }}\n            className=\"object-contain\"\n          />\n        </a>\n      ))}\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/components/tray/tray-menu-item.tsx",
    "content": "import type { LucideIcon } from 'lucide-react'\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\ninterface TrayMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {\n  icon: LucideIcon\n  label: string\n}\n\nfunction TrayMenuItem({ ref, className, icon: Icon, label, ...props }: TrayMenuItemProps & { ref?: React.RefObject<HTMLDivElement | null> }) {\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        'flex flex-col items-center justify-center gap-2 px-1 py-4 rounded cursor-pointer transition-colors bg-[rgba(0,0,0,0.04)] hover:bg-[rgba(0,0,0,0.08)]',\n        className,\n      )}\n      {...props}\n    >\n      <Icon className=\"h-5 w-5 cursor-pointer\" />\n      <span className=\"text-xs\">{label}</span>\n    </div>\n  )\n}\nTrayMenuItem.displayName = 'TrayMenuItem'\n\nexport { TrayMenuItem }\n"
  },
  {
    "path": "src/components/ui/alert-dialog.tsx",
    "content": "import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { cn } from '@/lib/utils'\n\nfunction AlertDialog({\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {\n  return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />\n}\n\nfunction AlertDialogTrigger({\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {\n  return (\n    <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />\n  )\n}\n\nfunction AlertDialogPortal({\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {\n  return (\n    <AlertDialogPrimitive.Portal data-slot=\"alert-dialog-portal\" {...props} />\n  )\n}\n\nfunction AlertDialogOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {\n  return (\n    <AlertDialogPrimitive.Overlay\n      data-slot=\"alert-dialog-overlay\"\n      className={cn(\n        'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogContent({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {\n  size?: 'default' | 'sm'\n}) {\n  return (\n    <AlertDialogPortal>\n      <AlertDialogOverlay />\n      <AlertDialogPrimitive.Content\n        data-slot=\"alert-dialog-content\"\n        data-size={size}\n        className={cn(\n          'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg',\n          className,\n        )}\n        {...props}\n      />\n    </AlertDialogPortal>\n  )\n}\n\nfunction AlertDialogHeader({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"alert-dialog-header\"\n      className={cn(\n        'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogFooter({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"alert-dialog-footer\"\n      className={cn(\n        'flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {\n  return (\n    <AlertDialogPrimitive.Title\n      data-slot=\"alert-dialog-title\"\n      className={cn(\n        'text-lg font-semibold sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {\n  return (\n    <AlertDialogPrimitive.Description\n      data-slot=\"alert-dialog-description\"\n      className={cn('text-muted-foreground text-sm', className)}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogMedia({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"alert-dialog-media\"\n      className={cn(\n        'bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*=\\'size-\\'])]:size-8',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogAction({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Action>\n  & Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {\n  return (\n    <Button variant={variant} size={size} asChild>\n      <AlertDialogPrimitive.Action\n        data-slot=\"alert-dialog-action\"\n        className={cn(className)}\n        {...props}\n      />\n    </Button>\n  )\n}\n\nfunction AlertDialogCancel({\n  className,\n  variant = 'outline',\n  size = 'default',\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>\n  & Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {\n  return (\n    <Button variant={variant} size={size} asChild>\n      <AlertDialogPrimitive.Cancel\n        data-slot=\"alert-dialog-cancel\"\n        className={cn(className)}\n        {...props}\n      />\n    </Button>\n  )\n}\n\nexport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogMedia,\n  AlertDialogOverlay,\n  AlertDialogPortal,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n}\n"
  },
  {
    "path": "src/components/ui/avatar.tsx",
    "content": "import { Avatar as AvatarPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Root> & {\n  size?: 'default' | 'sm' | 'lg'\n}) {\n  return (\n    <AvatarPrimitive.Root\n      data-slot=\"avatar\"\n      data-size={size}\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarImage({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Image>) {\n  return (\n    <AvatarPrimitive.Image\n      data-slot=\"avatar-image\"\n      className={cn('aspect-square size-full', className)}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {\n  return (\n    <AvatarPrimitive.Fallback\n      data-slot=\"avatar-fallback\"\n      className={cn(\n        'bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      data-slot=\"avatar-badge\"\n      className={cn(\n        'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"avatar-group\"\n      className={cn(\n        '*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"avatar-group-count\"\n      className={cn(\n        'bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Avatar,\n  AvatarBadge,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarImage,\n}\n"
  },
  {
    "path": "src/components/ui/breadcrumb.tsx",
    "content": "import { ChevronRight, MoreHorizontal } from 'lucide-react'\nimport { Slot } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {\n  return <nav aria-label=\"breadcrumb\" data-slot=\"breadcrumb\" {...props} />\n}\n\nfunction BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {\n  return (\n    <ol\n      data-slot=\"breadcrumb-list\"\n      className={cn(\n        'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"breadcrumb-item\"\n      className={cn('inline-flex items-center gap-1.5', className)}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbLink({\n  asChild,\n  className,\n  ...props\n}: React.ComponentProps<'a'> & {\n  asChild?: boolean\n}) {\n  const Comp = asChild ? Slot.Root : 'a'\n\n  return (\n    <Comp\n      data-slot=\"breadcrumb-link\"\n      className={cn('hover:text-foreground transition-colors', className)}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      data-slot=\"breadcrumb-page\"\n      role=\"link\"\n      aria-disabled=\"true\"\n      aria-current=\"page\"\n      className={cn('text-foreground font-normal', className)}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"breadcrumb-separator\"\n      role=\"presentation\"\n      aria-hidden=\"true\"\n      className={cn('[&>svg]:size-3.5', className)}\n      {...props}\n    >\n      {children ?? <ChevronRight />}\n    </li>\n  )\n}\n\nfunction BreadcrumbEllipsis({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      data-slot=\"breadcrumb-ellipsis\"\n      role=\"presentation\"\n      aria-hidden=\"true\"\n      className={cn('flex size-9 items-center justify-center', className)}\n      {...props}\n    >\n      <MoreHorizontal className=\"size-4\" />\n      <span className=\"sr-only\">More</span>\n    </span>\n  )\n}\n\nexport {\n  Breadcrumb,\n  BreadcrumbEllipsis,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n}\n"
  },
  {
    "path": "src/components/ui/button.tsx",
    "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\nimport { Slot } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nconst buttonVariants = cva(\n  'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all 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 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n        destructive:\n          'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',\n        outline:\n          'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n        ghost:\n          'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        'default': 'h-9 px-4 py-2 has-[>svg]:px-3',\n        'xs': 'h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*=\\'size-\\'])]:size-3',\n        'sm': 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',\n        'lg': 'h-10 rounded-md px-6 has-[>svg]:px-4',\n        'icon': 'size-9',\n        'icon-xs': 'size-6 rounded-md [&_svg:not([class*=\\'size-\\'])]:size-3',\n        'icon-sm': 'size-8',\n        'icon-lg': 'size-10',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n)\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  asChild = false,\n  ...props\n}: React.ComponentProps<'button'>\n  & VariantProps<typeof buttonVariants> & {\n    asChild?: boolean\n  }) {\n  const Comp = asChild ? Slot.Root : 'button'\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "src/components/ui/card.tsx",
    "content": "import * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Card({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card\"\n      className={cn(\n        'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        '@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn('leading-none font-semibold', className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn('text-muted-foreground text-sm', className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-content\"\n      className={cn('px-6', className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn('flex items-center px-6 [.border-t]:pt-6', className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Card,\n  CardAction,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n}\n"
  },
  {
    "path": "src/components/ui/checkbox.tsx",
    "content": "import * as CheckboxPrimitive from '@radix-ui/react-checkbox'\nimport { Check } from 'lucide-react'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Checkbox({\n  className,\n  ...props\n}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {\n  return (\n    <CheckboxPrimitive.Root\n      data-slot=\"checkbox\"\n      className={cn(\n        'peer border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',\n        className,\n      )}\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        data-slot=\"checkbox-indicator\"\n        className=\"flex items-center justify-center text-current transition-none\"\n      >\n        <Check className=\"size-3.5\" />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  )\n}\n\nexport { Checkbox }\n"
  },
  {
    "path": "src/components/ui/collapsible.tsx",
    "content": "'use client'\n\nimport { Collapsible as CollapsiblePrimitive } from 'radix-ui'\n\nfunction Collapsible({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />\n}\n\nfunction CollapsibleTrigger({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {\n  return (\n    <CollapsiblePrimitive.CollapsibleTrigger\n      data-slot=\"collapsible-trigger\"\n      {...props}\n    />\n  )\n}\n\nfunction CollapsibleContent({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {\n  return (\n    <CollapsiblePrimitive.CollapsibleContent\n      data-slot=\"collapsible-content\"\n      {...props}\n    />\n  )\n}\n\nexport { Collapsible, CollapsibleContent, CollapsibleTrigger }\n"
  },
  {
    "path": "src/components/ui/command.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Command as CommandPrimitive } from \"cmdk\"\nimport { SearchIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\"\n\nfunction Command({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive>) {\n  return (\n    <CommandPrimitive\n      data-slot=\"command\"\n      className={cn(\n        \"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandDialog({\n  title = \"Command Palette\",\n  description = \"Search for a command to run...\",\n  children,\n  className,\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof Dialog> & {\n  title?: string\n  description?: string\n  className?: string\n  showCloseButton?: boolean\n}) {\n  return (\n    <Dialog {...props}>\n      <DialogHeader className=\"sr-only\">\n        <DialogTitle>{title}</DialogTitle>\n        <DialogDescription>{description}</DialogDescription>\n      </DialogHeader>\n      <DialogContent\n        className={cn(\"overflow-hidden p-0\", className)}\n        showCloseButton={showCloseButton}\n      >\n        <Command className=\"**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n          {children}\n        </Command>\n      </DialogContent>\n    </Dialog>\n  )\n}\n\nfunction CommandInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  return (\n    <div\n      data-slot=\"command-input-wrapper\"\n      className=\"flex h-9 items-center gap-2 border-b px-3\"\n    >\n      <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n      <CommandPrimitive.Input\n        data-slot=\"command-input\"\n        className={cn(\n          \"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n          className\n        )}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction CommandList({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.List>) {\n  return (\n    <CommandPrimitive.List\n      data-slot=\"command-list\"\n      className={cn(\n        \"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandEmpty({\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  return (\n    <CommandPrimitive.Empty\n      data-slot=\"command-empty\"\n      className=\"py-6 text-center text-sm\"\n      {...props}\n    />\n  )\n}\n\nfunction CommandGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  return (\n    <CommandPrimitive.Group\n      data-slot=\"command-group\"\n      className={cn(\n        \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return (\n    <CommandPrimitive.Separator\n      data-slot=\"command-separator\"\n      className={cn(\"-mx-1 h-px bg-border\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CommandItem({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  return (\n    <CommandPrimitive.Item\n      data-slot=\"command-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"command-shortcut\"\n      className={cn(\n        \"ml-auto text-xs tracking-widest text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n}\n"
  },
  {
    "path": "src/components/ui/dialog.tsx",
    "content": "import { XIcon } from 'lucide-react'\nimport { Dialog as DialogPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { cn } from '@/lib/utils'\n\nfunction Dialog({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Root>) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />\n}\n\nfunction DialogTrigger({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />\n}\n\nfunction DialogPortal({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />\n}\n\nfunction DialogClose({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n  return (\n    <DialogPrimitive.Overlay\n      data-slot=\"dialog-overlay\"\n      className={cn(\n        'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n  showCloseButton?: boolean\n}) {\n  return (\n    <DialogPortal data-slot=\"dialog-portal\">\n      <DialogOverlay />\n      <DialogPrimitive.Content\n        data-slot=\"dialog-content\"\n        className={cn(\n          'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg',\n          className,\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Content>\n    </DialogPortal>\n  )\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      className={cn('flex flex-col gap-2 text-center sm:text-left', className)}\n      {...props}\n    />\n  )\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean\n}) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      className={cn(\n        'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close asChild>\n          <Button variant=\"outline\">Close</Button>\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  )\n}\n\nfunction DialogTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n  return (\n    <DialogPrimitive.Title\n      data-slot=\"dialog-title\"\n      className={cn('text-lg leading-none font-semibold', className)}\n      {...props}\n    />\n  )\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n  return (\n    <DialogPrimitive.Description\n      data-slot=\"dialog-description\"\n      className={cn('text-muted-foreground text-sm', className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n}\n"
  },
  {
    "path": "src/components/ui/dropdown-menu.tsx",
    "content": "import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'\nimport { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction DropdownMenu({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n  return <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n}\n\nfunction DropdownMenuPortal({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {\n  return (\n    <DropdownMenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />\n  )\n}\n\nfunction DropdownMenuTrigger({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n  return (\n    <DropdownMenuPrimitive.Trigger\n      data-slot=\"dropdown-menu-trigger\"\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuContent({\n  className,\n  sideOffset = 4,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n  return (\n    <DropdownMenuPrimitive.Portal>\n      <DropdownMenuPrimitive.Content\n        data-slot=\"dropdown-menu-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',\n          className,\n        )}\n        {...props}\n      />\n    </DropdownMenuPrimitive.Portal>\n  )\n}\n\nfunction DropdownMenuGroup({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n  return (\n    <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\n  )\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n  inset?: boolean\n  variant?: 'default' | 'destructive'\n}) {\n  return (\n    <DropdownMenuPrimitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        'focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*=\\'text-\\'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {\n  return (\n    <DropdownMenuPrimitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      className={cn(\n        'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4',\n        className,\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <DropdownMenuPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </DropdownMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </DropdownMenuPrimitive.CheckboxItem>\n  )\n}\n\nfunction DropdownMenuRadioGroup({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {\n  return (\n    <DropdownMenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {\n  return (\n    <DropdownMenuPrimitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      className={cn(\n        'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4',\n        className,\n      )}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <DropdownMenuPrimitive.ItemIndicator>\n          <CircleIcon className=\"size-2 fill-current\" />\n        </DropdownMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </DropdownMenuPrimitive.RadioItem>\n  )\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {\n  inset?: boolean\n}) {\n  return (\n    <DropdownMenuPrimitive.Label\n      data-slot=\"dropdown-menu-label\"\n      data-inset={inset}\n      className={cn(\n        'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n  return (\n    <DropdownMenuPrimitive.Separator\n      data-slot=\"dropdown-menu-separator\"\n      className={cn('bg-border -mx-1 my-1 h-px', className)}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      data-slot=\"dropdown-menu-shortcut\"\n      className={cn(\n        'text-muted-foreground ml-auto text-xs tracking-widest',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuSub({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n  return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {\n  inset?: boolean\n}) {\n  return (\n    <DropdownMenuPrimitive.SubTrigger\n      data-slot=\"dropdown-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*=\\'text-\\'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4',\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto size-4\" />\n    </DropdownMenuPrimitive.SubTrigger>\n  )\n}\n\nfunction DropdownMenuSubContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n  return (\n    <DropdownMenuPrimitive.SubContent\n      data-slot=\"dropdown-menu-sub-content\"\n      className={cn(\n        'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuPortal,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n}\n"
  },
  {
    "path": "src/components/ui/empty.tsx",
    "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\nimport { cn } from '@/lib/utils'\n\nfunction Empty({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"empty\"\n      className={cn(\n        'flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"empty-header\"\n      className={cn(\n        'flex max-w-sm flex-col items-center gap-2 text-center',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nconst emptyMediaVariants = cva(\n  'flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'bg-transparent',\n        icon: 'bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*=\\'size-\\'])]:size-6',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  },\n)\n\nfunction EmptyMedia({\n  className,\n  variant = 'default',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof emptyMediaVariants>) {\n  return (\n    <div\n      data-slot=\"empty-icon\"\n      data-variant={variant}\n      className={cn(emptyMediaVariants({ variant, className }))}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"empty-title\"\n      className={cn('text-lg font-medium tracking-tight', className)}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <div\n      data-slot=\"empty-description\"\n      className={cn(\n        'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"empty-content\"\n      className={cn(\n        'flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Empty,\n  EmptyContent,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyMedia,\n  EmptyTitle,\n}\n"
  },
  {
    "path": "src/components/ui/field.tsx",
    "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\nimport { useMemo } from 'react'\n\nimport { Label } from '@/components/ui/label'\nimport { Separator } from '@/components/ui/separator'\nimport { cn } from '@/lib/utils'\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n  return (\n    <fieldset\n      data-slot=\"field-set\"\n      className={cn(\n        'flex flex-col gap-6',\n        'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldLegend({\n  className,\n  variant = 'legend',\n  ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n  return (\n    <legend\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      className={cn(\n        'mb-3 font-medium',\n        'data-[variant=legend]:text-base',\n        'data-[variant=label]:text-sm',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"field-group\"\n      className={cn(\n        'group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nconst fieldVariants = cva(\n  'group/field flex w-full gap-3 data-[invalid=true]:text-destructive',\n  {\n    variants: {\n      orientation: {\n        vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'],\n        horizontal: [\n          'flex-row items-center',\n          '[&>[data-slot=field-label]]:flex-auto',\n          'has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n        ],\n        responsive: [\n          'flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto',\n          '@md/field-group:[&>[data-slot=field-label]]:flex-auto',\n          '@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n        ],\n      },\n    },\n    defaultVariants: {\n      orientation: 'vertical',\n    },\n  },\n)\n\nfunction Field({\n  className,\n  orientation = 'vertical',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"field\"\n      data-orientation={orientation}\n      className={cn(fieldVariants({ orientation }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"field-content\"\n      className={cn(\n        'group/field-content flex flex-1 flex-col gap-1.5 leading-snug',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      data-slot=\"field-label\"\n      className={cn(\n        'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',\n        'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4',\n        'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"field-label\"\n      className={cn(\n        'flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <p\n      data-slot=\"field-description\"\n      className={cn(\n        'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',\n        'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',\n        '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'div'> & {\n  children?: React.ReactNode\n}) {\n  return (\n    <div\n      data-slot=\"field-separator\"\n      data-content={!!children}\n      className={cn(\n        'relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2',\n        className,\n      )}\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"bg-background text-muted-foreground relative mx-auto block w-fit px-2\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  )\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<'div'> & {\n  errors?: Array<{ message?: string } | undefined>\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children\n    }\n\n    if (!errors?.length) {\n      return null\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map(error => [error?.message, error])).values(),\n    ]\n\n    if (uniqueErrors?.length == 1) {\n      return uniqueErrors[0]?.message\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={index}>{error.message}</li>,\n        )}\n      </ul>\n    )\n  }, [children, errors])\n\n  if (!content) {\n    return null\n  }\n\n  return (\n    <div\n      role=\"alert\"\n      data-slot=\"field-error\"\n      className={cn('text-destructive text-sm font-normal', className)}\n      {...props}\n    >\n      {content}\n    </div>\n  )\n}\n\nexport {\n  Field,\n  FieldContent,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLabel,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldTitle,\n}\n"
  },
  {
    "path": "src/components/ui/input.tsx",
    "content": "import * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <input\n      type={type}\n      data-slot=\"input\"\n      className={cn(\n        'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',\n        'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',\n        'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Input }\n"
  },
  {
    "path": "src/components/ui/item.tsx",
    "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\nimport { Slot } from 'radix-ui'\nimport * as React from 'react'\n\nimport { Separator } from '@/components/ui/separator'\nimport { cn } from '@/lib/utils'\n\nfunction ItemGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      role=\"list\"\n      data-slot=\"item-group\"\n      className={cn('group/item-group flex flex-col', className)}\n      {...props}\n    />\n  )\n}\n\nfunction ItemSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"item-separator\"\n      orientation=\"horizontal\"\n      className={cn('my-0', className)}\n      {...props}\n    />\n  )\n}\n\nconst itemVariants = cva(\n  'group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',\n  {\n    variants: {\n      variant: {\n        default: 'bg-transparent',\n        outline: 'border-border',\n        muted: 'bg-muted/50',\n      },\n      size: {\n        default: 'p-4 gap-4 ',\n        sm: 'py-3 px-4 gap-2.5',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n)\n\nfunction Item({\n  className,\n  variant = 'default',\n  size = 'default',\n  asChild = false,\n  ...props\n}: React.ComponentProps<'div'>\n  & VariantProps<typeof itemVariants> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : 'div'\n  return (\n    <Comp\n      data-slot=\"item\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(itemVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nconst itemMediaVariants = cva(\n  'flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5',\n  {\n    variants: {\n      variant: {\n        default: 'bg-transparent',\n        icon: 'size-8 border rounded-sm bg-muted [&_svg:not([class*=\\'size-\\'])]:size-4',\n        image:\n          'size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  },\n)\n\nfunction ItemMedia({\n  className,\n  variant = 'default',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>) {\n  return (\n    <div\n      data-slot=\"item-media\"\n      data-variant={variant}\n      className={cn(itemMediaVariants({ variant, className }))}\n      {...props}\n    />\n  )\n}\n\nfunction ItemContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"item-content\"\n      className={cn(\n        'flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"item-title\"\n      className={cn(\n        'flex w-fit items-center gap-2 text-sm leading-snug font-medium',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <p\n      data-slot=\"item-description\"\n      className={cn(\n        'text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance',\n        '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemActions({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"item-actions\"\n      className={cn('flex items-center gap-2', className)}\n      {...props}\n    />\n  )\n}\n\nfunction ItemHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"item-header\"\n      className={cn(\n        'flex basis-full items-center justify-between gap-2',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"item-footer\"\n      className={cn(\n        'flex basis-full items-center justify-between gap-2',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemFooter,\n  ItemGroup,\n  ItemHeader,\n  ItemMedia,\n  ItemSeparator,\n  ItemTitle,\n}\n"
  },
  {
    "path": "src/components/ui/label.tsx",
    "content": "import { Label as LabelPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Label({\n  className,\n  ...props\n}: React.ComponentProps<typeof LabelPrimitive.Root>) {\n  return (\n    <LabelPrimitive.Root\n      data-slot=\"label\"\n      className={cn(\n        'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Label }\n"
  },
  {
    "path": "src/components/ui/pagination.tsx",
    "content": "import type { Button } from '@/components/ui/button'\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  MoreHorizontalIcon,\n} from 'lucide-react'\n\nimport * as React from 'react'\nimport { buttonVariants } from '@/components/ui/button'\nimport { cn } from '@/lib/utils'\n\nfunction Pagination({ className, ...props }: React.ComponentProps<'nav'>) {\n  return (\n    <nav\n      role=\"navigation\"\n      aria-label=\"pagination\"\n      data-slot=\"pagination\"\n      className={cn('mx-auto flex w-full justify-center', className)}\n      {...props}\n    />\n  )\n}\n\nfunction PaginationContent({\n  className,\n  ...props\n}: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      data-slot=\"pagination-content\"\n      className={cn('flex flex-row items-center gap-1', className)}\n      {...props}\n    />\n  )\n}\n\nfunction PaginationItem({ ...props }: React.ComponentProps<'li'>) {\n  return <li data-slot=\"pagination-item\" {...props} />\n}\n\ntype PaginationLinkProps = {\n  isActive?: boolean\n} & Pick<React.ComponentProps<typeof Button>, 'size'>\n& React.ComponentProps<'a'>\n\nfunction PaginationLink({\n  className,\n  isActive,\n  size = 'icon',\n  ...props\n}: PaginationLinkProps) {\n  return (\n    <a\n      aria-current={isActive ? 'page' : undefined}\n      data-slot=\"pagination-link\"\n      data-active={isActive}\n      className={cn(\n        buttonVariants({\n          variant: isActive ? 'outline' : 'ghost',\n          size,\n        }),\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction PaginationPrevious({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) {\n  return (\n    <PaginationLink\n      aria-label=\"Go to previous page\"\n      size=\"default\"\n      className={cn('gap-1 px-2.5 sm:pl-2.5', className)}\n      {...props}\n    >\n      <ChevronLeftIcon />\n      <span className=\"hidden sm:block\">Previous</span>\n    </PaginationLink>\n  )\n}\n\nfunction PaginationNext({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) {\n  return (\n    <PaginationLink\n      aria-label=\"Go to next page\"\n      size=\"default\"\n      className={cn('gap-1 px-2.5 sm:pr-2.5', className)}\n      {...props}\n    >\n      <span className=\"hidden sm:block\">Next</span>\n      <ChevronRightIcon />\n    </PaginationLink>\n  )\n}\n\nfunction PaginationEllipsis({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      aria-hidden\n      data-slot=\"pagination-ellipsis\"\n      className={cn('flex size-9 items-center justify-center', className)}\n      {...props}\n    >\n      <MoreHorizontalIcon className=\"size-4\" />\n      <span className=\"sr-only\">More pages</span>\n    </span>\n  )\n}\n\nexport {\n  Pagination,\n  PaginationContent,\n  PaginationEllipsis,\n  PaginationItem,\n  PaginationLink,\n  PaginationNext,\n  PaginationPrevious,\n}\n"
  },
  {
    "path": "src/components/ui/popover.tsx",
    "content": "import { Popover as PopoverPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Popover({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Root>) {\n  return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />\n}\n\nfunction PopoverTrigger({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {\n  return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />\n}\n\nfunction PopoverContent({\n  className,\n  align = 'center',\n  sideOffset = 4,\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>) {\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverPrimitive.Content\n        data-slot=\"popover-content\"\n        align={align}\n        sideOffset={sideOffset}\n        className={cn(\n          'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',\n          className,\n        )}\n        {...props}\n      />\n    </PopoverPrimitive.Portal>\n  )\n}\n\nfunction PopoverAnchor({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {\n  return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />\n}\n\nfunction PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"popover-header\"\n      className={cn('flex flex-col gap-1 text-sm', className)}\n      {...props}\n    />\n  )\n}\n\nfunction PopoverTitle({ className, ...props }: React.ComponentProps<'h2'>) {\n  return (\n    <div\n      data-slot=\"popover-title\"\n      className={cn('font-medium', className)}\n      {...props}\n    />\n  )\n}\n\nfunction PopoverDescription({\n  className,\n  ...props\n}: React.ComponentProps<'p'>) {\n  return (\n    <p\n      data-slot=\"popover-description\"\n      className={cn('text-muted-foreground', className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Popover,\n  PopoverAnchor,\n  PopoverContent,\n  PopoverDescription,\n  PopoverHeader,\n  PopoverTitle,\n  PopoverTrigger,\n}\n"
  },
  {
    "path": "src/components/ui/progress.tsx",
    "content": "'use client'\n\nimport { Progress as ProgressPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Progress({\n  className,\n  value,\n  ...props\n}: React.ComponentProps<typeof ProgressPrimitive.Root>) {\n  return (\n    <ProgressPrimitive.Root\n      data-slot=\"progress\"\n      className={cn(\n        'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full',\n        className,\n      )}\n      {...props}\n    >\n      <ProgressPrimitive.Indicator\n        data-slot=\"progress-indicator\"\n        className=\"bg-primary h-full w-full flex-1 transition-all\"\n        style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n      />\n    </ProgressPrimitive.Root>\n  )\n}\n\nexport { Progress }\n"
  },
  {
    "path": "src/components/ui/radio-group.tsx",
    "content": "import { CircleIcon } from 'lucide-react'\nimport { RadioGroup as RadioGroupPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction RadioGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {\n  return (\n    <RadioGroupPrimitive.Root\n      data-slot=\"radio-group\"\n      className={cn('grid gap-3', className)}\n      {...props}\n    />\n  )\n}\n\nfunction RadioGroupItem({\n  className,\n  ...props\n}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {\n  return (\n    <RadioGroupPrimitive.Item\n      data-slot=\"radio-group-item\"\n      className={cn(\n        'border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',\n        className,\n      )}\n      {...props}\n    >\n      <RadioGroupPrimitive.Indicator\n        data-slot=\"radio-group-indicator\"\n        className=\"relative flex items-center justify-center\"\n      >\n        <CircleIcon className=\"fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2\" />\n      </RadioGroupPrimitive.Indicator>\n    </RadioGroupPrimitive.Item>\n  )\n}\n\nexport { RadioGroup, RadioGroupItem }\n"
  },
  {
    "path": "src/components/ui/scroll-area.tsx",
    "content": "import { ScrollArea as ScrollAreaPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction ScrollArea({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {\n  return (\n    <ScrollAreaPrimitive.Root\n      data-slot=\"scroll-area\"\n      className={cn('relative', className)}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Viewport\n        data-slot=\"scroll-area-viewport\"\n        className=\"focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1\"\n      >\n        {children}\n      </ScrollAreaPrimitive.Viewport>\n      <ScrollBar />\n      <ScrollAreaPrimitive.Corner />\n    </ScrollAreaPrimitive.Root>\n  )\n}\n\nfunction ScrollBar({\n  className,\n  orientation = 'vertical',\n  ...props\n}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {\n  return (\n    <ScrollAreaPrimitive.ScrollAreaScrollbar\n      data-slot=\"scroll-area-scrollbar\"\n      orientation={orientation}\n      className={cn(\n        'flex touch-none p-px transition-colors select-none',\n        orientation === 'vertical'\n        && 'h-full w-2.5 border-l border-l-transparent',\n        orientation === 'horizontal'\n        && 'h-2.5 flex-col border-t border-t-transparent',\n        className,\n      )}\n      {...props}\n    >\n      <ScrollAreaPrimitive.ScrollAreaThumb\n        data-slot=\"scroll-area-thumb\"\n        className=\"bg-border relative flex-1 rounded-full\"\n      />\n    </ScrollAreaPrimitive.ScrollAreaScrollbar>\n  )\n}\n\nexport { ScrollArea, ScrollBar }\n"
  },
  {
    "path": "src/components/ui/select.tsx",
    "content": "'use client'\n\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'\nimport { Select as SelectPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Select({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Root>) {\n  return <SelectPrimitive.Root data-slot=\"select\" {...props} />\n}\n\nfunction SelectGroup({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Group>) {\n  return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />\n}\n\nfunction SelectValue({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Value>) {\n  return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {\n  size?: 'sm' | 'default'\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      data-slot=\"select-trigger\"\n      data-size={size}\n      className={cn(\n        'border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*=\\'text-\\'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4',\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon asChild>\n        <ChevronDownIcon className=\"size-4 opacity-50\" />\n      </SelectPrimitive.Icon>\n    </SelectPrimitive.Trigger>\n  )\n}\n\nfunction SelectContent({\n  className,\n  children,\n  position = 'item-aligned',\n  align = 'center',\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Content\n        data-slot=\"select-content\"\n        className={cn(\n          'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',\n          position === 'popper'\n          && 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n          className,\n        )}\n        position={position}\n        align={align}\n        {...props}\n      >\n        <SelectScrollUpButton />\n        <SelectPrimitive.Viewport\n          className={cn(\n            'p-1',\n            position === 'popper'\n            && 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1',\n          )}\n        >\n          {children}\n        </SelectPrimitive.Viewport>\n        <SelectScrollDownButton />\n      </SelectPrimitive.Content>\n    </SelectPrimitive.Portal>\n  )\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Label>) {\n  return (\n    <SelectPrimitive.Label\n      data-slot=\"select-label\"\n      className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item>) {\n  return (\n    <SelectPrimitive.Item\n      data-slot=\"select-item\"\n      className={cn(\n        'focus:bg-accent focus:text-accent-foreground [&_svg:not([class*=\\'text-\\'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2',\n        className,\n      )}\n      {...props}\n    >\n      <span\n        data-slot=\"select-item-indicator\"\n        className=\"absolute right-2 flex size-3.5 items-center justify-center\"\n      >\n        <SelectPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </SelectPrimitive.ItemIndicator>\n      </span>\n      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n    </SelectPrimitive.Item>\n  )\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n  return (\n    <SelectPrimitive.Separator\n      data-slot=\"select-separator\"\n      className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {\n  return (\n    <SelectPrimitive.ScrollUpButton\n      data-slot=\"select-scroll-up-button\"\n      className={cn(\n        'flex cursor-default items-center justify-center py-1',\n        className,\n      )}\n      {...props}\n    >\n      <ChevronUpIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollUpButton>\n  )\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {\n  return (\n    <SelectPrimitive.ScrollDownButton\n      data-slot=\"select-scroll-down-button\"\n      className={cn(\n        'flex cursor-default items-center justify-center py-1',\n        className,\n      )}\n      {...props}\n    >\n      <ChevronDownIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollDownButton>\n  )\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n}\n"
  },
  {
    "path": "src/components/ui/separator.tsx",
    "content": "import { Separator as SeparatorPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  decorative = true,\n  ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n  return (\n    <SeparatorPrimitive.Root\n      data-slot=\"separator\"\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Separator }\n"
  },
  {
    "path": "src/components/ui/sheet.tsx",
    "content": "'use client'\n\nimport { XIcon } from 'lucide-react'\nimport { Dialog as SheetPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />\n}\n\nfunction SheetTrigger({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />\n}\n\nfunction SheetClose({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Close>) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />\n}\n\nfunction SheetPortal({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Portal>) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />\n}\n\nfunction SheetOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {\n  return (\n    <SheetPrimitive.Overlay\n      data-slot=\"sheet-overlay\"\n      className={cn(\n        'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Content> & {\n  side?: 'top' | 'right' | 'bottom' | 'left'\n  showCloseButton?: boolean\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Content\n        data-slot=\"sheet-content\"\n        className={cn(\n          'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',\n          side === 'right'\n          && 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',\n          side === 'left'\n          && 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',\n          side === 'top'\n          && 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',\n          side === 'bottom'\n          && 'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',\n          className,\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none\">\n            <XIcon className=\"size-4\" />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Content>\n    </SheetPortal>\n  )\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sheet-header\"\n      className={cn('flex flex-col gap-1.5 p-4', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sheet-footer\"\n      className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Title>) {\n  return (\n    <SheetPrimitive.Title\n      data-slot=\"sheet-title\"\n      className={cn('text-foreground font-semibold', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Description>) {\n  return (\n    <SheetPrimitive.Description\n      data-slot=\"sheet-description\"\n      className={cn('text-muted-foreground text-sm', className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Sheet,\n  SheetClose,\n  SheetContent,\n  SheetDescription,\n  SheetFooter,\n  SheetHeader,\n  SheetTitle,\n  SheetTrigger,\n}\n"
  },
  {
    "path": "src/components/ui/sidebar.tsx",
    "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\nimport { PanelLeftIcon } from 'lucide-react'\nimport { Slot } from 'radix-ui'\nimport * as React from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { Input } from '@/components/ui/input'\nimport { Separator } from '@/components/ui/separator'\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from '@/components/ui/sheet'\nimport { Skeleton } from '@/components/ui/skeleton'\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from '@/components/ui/tooltip'\nimport { useIsMobile } from '@/hooks/use-mobile'\nimport { cn } from '@/lib/utils'\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state'\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7\nconst SIDEBAR_WIDTH = '16rem'\nconst SIDEBAR_WIDTH_MOBILE = '18rem'\nconst SIDEBAR_WIDTH_ICON = '3rem'\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b'\n\ninterface SidebarContextProps {\n  state: 'expanded' | 'collapsed'\n  open: boolean\n  setOpen: (open: boolean) => void\n  openMobile: boolean\n  setOpenMobile: (open: boolean) => void\n  isMobile: boolean\n  toggleSidebar: () => void\n}\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null)\n\nfunction useSidebar() {\n  const context = React.use(SidebarContext)\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.')\n  }\n\n  return context\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean\n  open?: boolean\n  onOpenChange?: (open: boolean) => void\n}) {\n  const isMobile = useIsMobile()\n  const [openMobile, setOpenMobile] = React.useState(false)\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen)\n  const open = openProp ?? _open\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value\n      if (setOpenProp) {\n        setOpenProp(openState)\n      }\n      else {\n        _setOpen(openState)\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`\n    },\n    [setOpenProp, open],\n  )\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile(open => !open) : setOpen(open => !open)\n  }, [isMobile, setOpen, setOpenMobile])\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT\n        && (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault()\n        toggleSidebar()\n      }\n    }\n\n    window.addEventListener('keydown', handleKeyDown)\n    return () => window.removeEventListener('keydown', handleKeyDown)\n  }, [toggleSidebar])\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed'\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],\n  )\n\n  return (\n    <SidebarContext value={contextValue}>\n      <TooltipProvider delayDuration={0}>\n        <div\n          data-slot=\"sidebar-wrapper\"\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH,\n              '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n              ...style,\n            } as React.CSSProperties\n          }\n          className={cn(\n            'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex h-screen overflow-hidden w-full',\n            className,\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </TooltipProvider>\n    </SidebarContext>\n  )\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  className,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right'\n  variant?: 'sidebar' | 'floating' | 'inset'\n  collapsible?: 'offcanvas' | 'icon' | 'none'\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar()\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        data-slot=\"sidebar\"\n        className={cn(\n          'bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col',\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    )\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n        <SheetContent\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          data-mobile=\"true\"\n          className=\"bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden\"\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n          side={side}\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    )\n  }\n\n  return (\n    <div\n      className=\"group peer text-sidebar-foreground hidden md:block\"\n      data-state={state}\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-variant={variant}\n      data-side={side}\n      data-slot=\"sidebar\"\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        data-slot=\"sidebar-gap\"\n        className={cn(\n          'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n          'group-data-[collapsible=offcanvas]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',\n        )}\n      />\n      <div\n        data-slot=\"sidebar-container\"\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',\n          side === 'left'\n            ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'\n            : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className,\n        )}\n        {...props}\n      >\n        <div\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n          className=\"bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar()\n\n  return (\n    <Button\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      variant=\"ghost\"\n      size=\"icon\"\n      className={cn('size-7', className)}\n      onClick={(event) => {\n        onClick?.(event)\n        toggleSidebar()\n      }}\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  )\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar()\n\n  return (\n    <button\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      aria-label=\"Toggle Sidebar\"\n      tabIndex={-1}\n      onClick={toggleSidebar}\n      title=\"Toggle Sidebar\"\n      className={cn(\n        'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      data-slot=\"sidebar-inset\"\n      className={cn(\n        'bg-background relative flex w-full flex-1 flex-col',\n        'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      data-slot=\"sidebar-input\"\n      data-sidebar=\"input\"\n      className={cn('bg-background h-8 w-full shadow-none', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-header\"\n      data-sidebar=\"header\"\n      className={cn('flex flex-col gap-2 p-2', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-footer\"\n      data-sidebar=\"footer\"\n      className={cn('flex flex-col gap-2 p-2', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"sidebar-separator\"\n      data-sidebar=\"separator\"\n      className={cn('bg-sidebar-border mx-2 w-auto', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-content\"\n      data-sidebar=\"content\"\n      className={cn(\n        'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-group\"\n      data-sidebar=\"group\"\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarGroupLabel({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<'div'> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : 'div'\n\n  return (\n    <Comp\n      data-slot=\"sidebar-group-label\"\n      data-sidebar=\"group-label\"\n      className={cn(\n        'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',\n        'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarGroupAction({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<'button'> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : 'button'\n\n  return (\n    <Comp\n      data-slot=\"sidebar-group-action\"\n      data-sidebar=\"group-action\"\n      className={cn(\n        'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',\n        // Increases the hit area of the button on mobile.\n        'after:absolute after:-inset-2 md:after:hidden',\n        'group-data-[collapsible=icon]:hidden',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-group-content\"\n      data-sidebar=\"group-content\"\n      className={cn('w-full text-sm', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu\"\n      data-sidebar=\"menu\"\n      className={cn('flex w-full min-w-0 flex-col gap-1', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-item\"\n      data-sidebar=\"menu-item\"\n      className={cn('group/menu-item relative', className)}\n      {...props}\n    />\n  )\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden 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',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          '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))]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n)\n\nfunction SidebarMenuButton({\n  asChild = false,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: React.ComponentProps<'button'> & {\n  asChild?: boolean\n  isActive?: boolean\n  tooltip?: string | React.ComponentProps<typeof TooltipContent>\n} & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const Comp = asChild ? Slot.Root : 'button'\n  const { isMobile, state } = useSidebar()\n\n  const button = (\n    <Comp\n      data-slot=\"sidebar-menu-button\"\n      data-sidebar=\"menu-button\"\n      data-size={size}\n      data-active={isActive}\n      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n      {...props}\n    />\n  )\n\n  if (!tooltip) {\n    return button\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip,\n    }\n  }\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>{button}</TooltipTrigger>\n      <TooltipContent\n        side=\"right\"\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        {...tooltip}\n      />\n    </Tooltip>\n  )\n}\n\nfunction SidebarMenuAction({\n  className,\n  asChild = false,\n  showOnHover = false,\n  ...props\n}: React.ComponentProps<'button'> & {\n  asChild?: boolean\n  showOnHover?: boolean\n}) {\n  const Comp = asChild ? Slot.Root : 'button'\n\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-action\"\n      data-sidebar=\"menu-action\"\n      className={cn(\n        'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',\n        // Increases the hit area of the button on mobile.\n        'after:absolute after:-inset-2 md:after:hidden',\n        'peer-data-[size=sm]/menu-button:top-1',\n        'peer-data-[size=default]/menu-button:top-1.5',\n        'peer-data-[size=lg]/menu-button:top-2.5',\n        'group-data-[collapsible=icon]:hidden',\n        showOnHover\n        && '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',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-menu-badge\"\n      data-sidebar=\"menu-badge\"\n      className={cn(\n        'text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none',\n        'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',\n        'peer-data-[size=sm]/menu-button:top-1',\n        'peer-data-[size=default]/menu-button:top-1.5',\n        'peer-data-[size=lg]/menu-button:top-2.5',\n        'group-data-[collapsible=icon]:hidden',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean\n}) {\n  // Random width between 50 to 90%.\n  const width = React.useMemo(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`\n  }, [])\n\n  return (\n    <div\n      data-slot=\"sidebar-menu-skeleton\"\n      data-sidebar=\"menu-skeleton\"\n      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  )\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu-sub\"\n      data-sidebar=\"menu-sub\"\n      className={cn(\n        'border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5',\n        'group-data-[collapsible=icon]:hidden',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-sub-item\"\n      data-sidebar=\"menu-sub-item\"\n      className={cn('group/menu-sub-item relative', className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenuSubButton({\n  asChild = false,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: React.ComponentProps<'a'> & {\n  asChild?: boolean\n  size?: 'sm' | 'md'\n  isActive?: boolean\n}) {\n  const Comp = asChild ? Slot.Root : 'a'\n\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-sub-button\"\n      data-sidebar=\"menu-sub-button\"\n      data-size={size}\n      data-active={isActive}\n      className={cn(\n        '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-hidden 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',\n        'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',\n        size === 'sm' && 'text-xs',\n        size === 'md' && 'text-sm',\n        'group-data-[collapsible=icon]:hidden',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n}\n"
  },
  {
    "path": "src/components/ui/skeleton.tsx",
    "content": "import { cn } from '@/lib/utils'\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"skeleton\"\n      className={cn('bg-accent animate-pulse rounded-md', className)}\n      {...props}\n    />\n  )\n}\n\nexport { Skeleton }\n"
  },
  {
    "path": "src/components/ui/slider.tsx",
    "content": "import { Slider as SliderPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Slider({\n  className,\n  defaultValue,\n  value,\n  min = 0,\n  max = 100,\n  ...props\n}: React.ComponentProps<typeof SliderPrimitive.Root>) {\n  const _values = React.useMemo(\n    () =>\n      Array.isArray(value)\n        ? value\n        : Array.isArray(defaultValue)\n          ? defaultValue\n          : [min, max],\n    [value, defaultValue, min, max],\n  )\n\n  return (\n    <SliderPrimitive.Root\n      data-slot=\"slider\"\n      defaultValue={defaultValue}\n      value={value}\n      min={min}\n      max={max}\n      className={cn(\n        'relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',\n        className,\n      )}\n      {...props}\n    >\n      <SliderPrimitive.Track\n        data-slot=\"slider-track\"\n        className={cn(\n          'bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5',\n        )}\n      >\n        <SliderPrimitive.Range\n          data-slot=\"slider-range\"\n          className={cn(\n            'bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full',\n          )}\n        />\n      </SliderPrimitive.Track>\n      {Array.from({ length: _values.length }, (_, index) => (\n        <SliderPrimitive.Thumb\n          data-slot=\"slider-thumb\"\n          key={index}\n          className=\"border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\"\n        />\n      ))}\n    </SliderPrimitive.Root>\n  )\n}\n\nexport { Slider }\n"
  },
  {
    "path": "src/components/ui/sonner.tsx",
    "content": "'use client'\n\nimport type { ToasterProps } from 'sonner'\nimport {\n  CircleCheckIcon,\n  InfoIcon,\n  Loader2Icon,\n  OctagonXIcon,\n  TriangleAlertIcon,\n} from 'lucide-react'\nimport { useTheme } from 'next-themes'\nimport { Toaster as Sonner } from 'sonner'\n\nfunction Toaster({ ...props }: ToasterProps) {\n  const { theme = 'system' } = useTheme()\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps['theme']}\n      className=\"toaster group\"\n      icons={{\n        success: <CircleCheckIcon className=\"size-4\" />,\n        info: <InfoIcon className=\"size-4\" />,\n        warning: <TriangleAlertIcon className=\"size-4\" />,\n        error: <OctagonXIcon className=\"size-4\" />,\n        loading: <Loader2Icon className=\"size-4 animate-spin\" />,\n      }}\n      style={\n        {\n          '--normal-bg': 'var(--popover)',\n          '--normal-text': 'var(--popover-foreground)',\n          '--normal-border': 'var(--border)',\n          '--border-radius': 'var(--radius)',\n        } as React.CSSProperties\n      }\n      {...props}\n    />\n  )\n}\n\nexport { Toaster }\n"
  },
  {
    "path": "src/components/ui/spinner.tsx",
    "content": "import { Loader2Icon } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Spinner({ className, ...props }: React.ComponentProps<'svg'>) {\n  return (\n    <Loader2Icon\n      role=\"status\"\n      aria-label=\"Loading\"\n      className={cn('size-4 animate-spin', className)}\n      {...props}\n    />\n  )\n}\n\nexport { Spinner }\n"
  },
  {
    "path": "src/components/ui/switch.tsx",
    "content": "import { Switch as SwitchPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Switch({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<typeof SwitchPrimitive.Root> & {\n  size?: 'sm' | 'default'\n}) {\n  return (\n    <SwitchPrimitive.Root\n      data-slot=\"switch\"\n      data-size={size}\n      className={cn(\n        'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6',\n        className,\n      )}\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        data-slot=\"switch-thumb\"\n        className={cn(\n          'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0',\n        )}\n      />\n    </SwitchPrimitive.Root>\n  )\n}\n\nexport { Switch }\n"
  },
  {
    "path": "src/components/ui/table.tsx",
    "content": "import * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\n  return (\n    <div\n      data-slot=\"table-container\"\n      className=\"relative w-full overflow-x-auto\"\n    >\n      <table\n        data-slot=\"table\"\n        className={cn('w-full caption-bottom text-sm', className)}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n  return (\n    <thead\n      data-slot=\"table-header\"\n      className={cn('[&_tr]:border-b', className)}\n      {...props}\n    />\n  )\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n  return (\n    <tbody\n      data-slot=\"table-body\"\n      className={cn('[&_tr:last-child]:border-0', className)}\n      {...props}\n    />\n  )\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n  return (\n    <tfoot\n      data-slot=\"table-footer\"\n      className={cn(\n        'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n  return (\n    <tr\n      data-slot=\"table-row\"\n      className={cn(\n        'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n  return (\n    <th\n      data-slot=\"table-head\"\n      className={cn(\n        'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n  return (\n    <td\n      data-slot=\"table-cell\"\n      className={cn(\n        'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<'caption'>) {\n  return (\n    <caption\n      data-slot=\"table-caption\"\n      className={cn('text-muted-foreground mt-4 text-sm', className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Table,\n  TableBody,\n  TableCaption,\n  TableCell,\n  TableFooter,\n  TableHead,\n  TableHeader,\n  TableRow,\n}\n"
  },
  {
    "path": "src/components/ui/tabs.tsx",
    "content": "'use client'\n\nimport type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\nimport { Tabs as TabsPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Tabs({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Root>) {\n  return (\n    <TabsPrimitive.Root\n      data-slot=\"tabs\"\n      data-orientation={orientation}\n      orientation={orientation}\n      className={cn(\n        'group/tabs flex gap-2 data-[orientation=horizontal]:flex-col',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nconst tabsListVariants = cva(\n  'rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col',\n  {\n    variants: {\n      variant: {\n        default: 'bg-muted',\n        line: 'gap-1 bg-transparent',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  },\n)\n\nfunction TabsList({\n  className,\n  variant = 'default',\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.List>\n  & VariantProps<typeof tabsListVariants>) {\n  return (\n    <TabsPrimitive.List\n      data-slot=\"tabs-list\"\n      data-variant={variant}\n      className={cn(tabsListVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction TabsTrigger({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {\n  return (\n    <TabsPrimitive.Trigger\n      data-slot=\"tabs-trigger\"\n      className={cn(\n        'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4',\n        'group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent',\n        'data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground',\n        'after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TabsContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Content>) {\n  return (\n    <TabsPrimitive.Content\n      data-slot=\"tabs-content\"\n      className={cn('flex-1 outline-none', className)}\n      {...props}\n    />\n  )\n}\n\nexport { Tabs, TabsContent, TabsList, tabsListVariants, TabsTrigger }\n"
  },
  {
    "path": "src/components/ui/textarea.tsx",
    "content": "import * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      data-slot=\"textarea\"\n      className={cn(\n        'border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Textarea }\n"
  },
  {
    "path": "src/components/ui/tooltip.tsx",
    "content": "import { Tooltip as TooltipPrimitive } from 'radix-ui'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nfunction TooltipProvider({\n  delayDuration = 0,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delayDuration={delayDuration}\n      {...props}\n    />\n  )\n}\n\nfunction Tooltip({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n}\n\nfunction TooltipTrigger({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />\n}\n\nfunction TooltipContent({\n  className,\n  sideOffset = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Content\n        data-slot=\"tooltip-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          'bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',\n          className,\n        )}\n        {...props}\n      >\n        {children}\n        <TooltipPrimitive.Arrow className=\"bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\" />\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  )\n}\n\nexport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }\n"
  },
  {
    "path": "src/components/user-avatar.tsx",
    "content": "import { User } from 'lucide-react'\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'\nimport { cn } from '@/lib/utils'\n\ninterface UserAvatarProps {\n  src?: string\n  alt?: string\n  className?: string\n  size?: 'default' | 'sm' | 'lg'\n}\n\nexport default function UserAvatar({\n  src,\n  alt,\n  className,\n  size = 'default',\n}: UserAvatarProps) {\n  return (\n    <Avatar className={cn('cursor-pointer', className)} size={size}>\n      <AvatarImage src={src} alt={alt} />\n      <AvatarFallback className=\"bg-transparent\">\n        <User className=\"h-5 w-5 text-white\" />\n      </AvatarFallback>\n    </Avatar>\n  )\n}\n"
  },
  {
    "path": "src/components/widget-sync-item.tsx",
    "content": "import type { Widget } from '@widget-js/core'\nimport { WidgetApi } from '@widget-js/core'\nimport { Clock } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemHeader,\n  ItemTitle,\n} from '@/components/ui/item'\n\ninterface WidgetSyncItemProps {\n  widget: Widget\n}\n\nexport default function WidgetSyncItem({ widget }: WidgetSyncItemProps) {\n  const { i18n } = useTranslation()\n  const [latestSyncAt, setLatestSyncAt] = useState<string>('')\n\n  const getLocalizedText = (text: any) => {\n    if (typeof text === 'string') { return text }\n    if (!text) { return '' }\n    return text[i18n.language] || text['zh-CN'] || Object.values(text)[0] || ''\n  }\n\n  useEffect(() => {\n    WidgetApi.getSyncInfo(widget.name).then((it) => {\n      if (it && it.latestSyncTime) {\n        setLatestSyncAt(`${new Date(it.latestSyncTime).toLocaleString()}`)\n      }\n      else {\n        setLatestSyncAt('暂无同步')\n      }\n    })\n  }, [widget.name])\n\n  return (\n    <Item className=\"bg-muted/50 rounded-lg p-3\">\n      <ItemContent>\n        <ItemHeader>\n          <ItemTitle>{getLocalizedText(widget.title) || widget.name}</ItemTitle>\n\n        </ItemHeader>\n        <ItemDescription title={widget.name} className=\"text-xs\">\n          {widget.name}\n        </ItemDescription>\n\n      </ItemContent>\n      <ItemActions>\n        <div className=\"text-xs text-muted-foreground flex gap-1 items-center\">\n          <Clock className=\"w-3 h-3\" />\n          <span>{latestSyncAt}</span>\n        </div>\n      </ItemActions>\n    </Item>\n  )\n}\n"
  },
  {
    "path": "src/hooks/use-app-broadcast.ts",
    "content": "import type { BroadcastEvent, BroadcastEventType } from '@widget-js/core'\nimport { BroadcastApi, Channel } from '@widget-js/core'\nimport { useEffect, useRef } from 'react'\nimport { useIpcListener } from './use-ipc-listener'\n\nexport function useAppBroadcast(\n  events: BroadcastEventType[],\n  callback: (event: BroadcastEvent) => void,\n) {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  }, [callback])\n\n  useEffect(() => {\n    BroadcastApi.register(...events)\n    return () => {\n      BroadcastApi.unregister(...events)\n    }\n  }, [JSON.stringify(events)])\n\n  useIpcListener(Channel.BROADCAST, (...args: any[]) => {\n    const event = args[0] as BroadcastEvent\n    if (events.includes(event.event)) {\n      callbackRef.current(event)\n    }\n  })\n}\n"
  },
  {
    "path": "src/hooks/use-app-language.ts",
    "content": "import type { BroadcastEvent, LanguageCode } from '@widget-js/core'\nimport { AppApi, AppApiEvent } from '@widget-js/core'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { useAppBroadcast } from './use-app-broadcast'\n\nexport interface UseAppLanguageOption {\n  onLoad?: (code: string) => void\n  onChange?: (code: string) => void\n}\n\nexport function useAppLanguage(options?: UseAppLanguageOption) {\n  const [languageCode, setLanguageCode] = useState<string>(navigator.language)\n  const loadedRef = useRef(false)\n\n  useEffect(() => {\n    AppApi.getLanguageCode().then((result) => {\n      setLanguageCode(result)\n      loadedRef.current = true\n      options?.onLoad?.(result)\n    })\n  }, []) // Empty dependency array means this runs once on mount\n\n  const handleBroadcast = useCallback((event: BroadcastEvent) => {\n    if (event.event === AppApiEvent.LANGUAGE_CHANGED) {\n      if (languageCode !== event.payload && typeof event.payload === 'string') {\n        setLanguageCode(event.payload)\n        options?.onChange?.(event.payload)\n      }\n    }\n  }, [languageCode, options])\n\n  useAppBroadcast([AppApiEvent.LANGUAGE_CHANGED], handleBroadcast)\n\n  const updateLanguageCode = async (newCode: string) => {\n    setLanguageCode(newCode)\n    if (loadedRef.current) {\n      await AppApi.setLanguageCode(newCode as LanguageCode)\n    }\n  }\n\n  return [languageCode, updateLanguageCode] as const\n}\n"
  },
  {
    "path": "src/hooks/use-app-runtime-info.ts",
    "content": "import type { AppRuntimeInfo } from '@widget-js/core'\nimport { AppApi } from '@widget-js/core'\nimport { useEffect, useMemo, useState } from 'react'\n\nexport type SimpleAppRuntimeInfo = Omit<AppRuntimeInfo, 'chrome' | 'node' | 'appPath' | 'platform' | 'v8'>\n\nexport function useAppRuntimeInfo() {\n  const [info, setInfo] = useState<AppRuntimeInfo>()\n\n  useEffect(() => {\n    AppApi.getRuntimeInfo().then((data) => {\n      const res = Object.keys(data).sort().reduce((obj: any, key: any) => {\n        obj[key] = (data as any)[key]\n        return obj\n      }, {})\n      setInfo(res as AppRuntimeInfo)\n    })\n  }, [])\n\n  const simpleInfo = useMemo<SimpleAppRuntimeInfo | undefined>(() => {\n    if (info) {\n      const { ...simple } = info\n      return simple\n    }\n    return undefined\n  }, [info])\n\n  return { info, simpleInfo }\n}\n"
  },
  {
    "path": "src/hooks/use-cell-size-config.ts",
    "content": "import { AppApi } from '@widget-js/core'\nimport { useEffect, useRef, useState } from 'react'\n\nexport function useCellSizeConfig() {\n  const [gridSize, setGridSize] = useState(80)\n  const loadedRef = useRef(false)\n\n  useEffect(() => {\n    AppApi.getGridCellSize().then((size) => {\n      setGridSize(size)\n      loadedRef.current = true\n    })\n  }, [])\n\n  const updateGridSize = async (newSize: number) => {\n    setGridSize(newSize)\n    if (loadedRef.current) {\n      await AppApi.setGridCellSize(newSize)\n    }\n  }\n\n  return [gridSize, updateGridSize] as const\n}\n"
  },
  {
    "path": "src/hooks/use-debounce.ts",
    "content": "import { useEffect, useState } from 'react'\n\nexport function useDebounce<T>(value: T, delay: number): T {\n  const [debouncedValue, setDebouncedValue] = useState(value)\n\n  useEffect(() => {\n    const handler = setTimeout(() => {\n      setDebouncedValue(value)\n    }, delay)\n\n    return () => {\n      clearTimeout(handler)\n    }\n  }, [value, delay])\n\n  return debouncedValue\n}\n"
  },
  {
    "path": "src/hooks/use-debug-config.ts",
    "content": "import { AppApi } from '@widget-js/core'\nimport { useEffect, useRef, useState } from 'react'\n\nexport function useDebugConfig(onLoad?: (debug: boolean) => void) {\n  const [debugMode, setDebugMode] = useState(false)\n  const loadedRef = useRef(false)\n\n  useEffect(() => {\n    AppApi.getDevMode().then((mode) => {\n      setDebugMode(mode)\n      loadedRef.current = true\n      onLoad?.(mode)\n    })\n  }, [onLoad])\n\n  const updateDebugMode = async (newMode: boolean) => {\n    setDebugMode(newMode)\n    if (loadedRef.current) {\n      await AppApi.setDevMode(newMode)\n    }\n  }\n\n  return [debugMode, updateDebugMode] as const\n}\n"
  },
  {
    "path": "src/hooks/use-ipc-listener.ts",
    "content": "import type { Channel } from '@widget-js/core'\nimport { ElectronApi } from '@widget-js/core'\nimport { useEffect, useRef } from 'react'\n\nexport function useIpcListener(channel: Channel | string, callback: (...args: any[]) => void) {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  }, [callback])\n\n  useEffect(() => {\n    const handler = (...args: any[]) => {\n      callbackRef.current(...args)\n    }\n    ElectronApi.addIpcListener(channel, handler)\n    return () => {\n      ElectronApi.removeIpcListener(channel)\n    }\n  }, [channel])\n}\n"
  },
  {
    "path": "src/hooks/use-launch-at-startup-config.ts",
    "content": "import { ApiConstants, AppApi } from '@widget-js/core'\nimport { useEffect, useRef, useState } from 'react'\n\nexport function useLaunchAtStartupConfig() {\n  const [launchAtStartup, setLaunchAtStartup] = useState(true)\n  const loadedRef = useRef(false)\n\n  useEffect(() => {\n    AppApi.getConfig(ApiConstants.CONFIG_LAUNCH_AT_STARTUP, true).then((startup) => {\n      setLaunchAtStartup(startup as boolean)\n      loadedRef.current = true\n    })\n  }, [])\n\n  const updateLaunchAtStartup = async (newValue: boolean) => {\n    setLaunchAtStartup(newValue)\n    if (loadedRef.current) {\n      await AppApi.setConfig(ApiConstants.CONFIG_LAUNCH_AT_STARTUP, newValue)\n    }\n  }\n\n  return [launchAtStartup, updateLaunchAtStartup] as const\n}\n"
  },
  {
    "path": "src/hooks/use-mobile.ts",
    "content": "import * as React from 'react'\n\nconst MOBILE_BREAKPOINT = 768\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)\n    }\n    mql.addEventListener('change', onChange)\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)\n    return () => mql.removeEventListener('change', onChange)\n  }, [])\n\n  return !!isMobile\n}\n"
  },
  {
    "path": "src/hooks/use-supabase-channel.ts",
    "content": "import consola from 'consola'\nimport { useEffect, useMemo, useRef } from 'react'\nimport { supabase } from '@/api/supabase'\n\nexport function useSupabaseChannel(channelName: string, onCallback: (payload: any) => void) {\n  // Use a ref to store the callback to avoid re-subscribing when the callback function changes\n  const onCallbackRef = useRef(onCallback)\n\n  // Update the ref whenever the callback changes\n  useEffect(() => {\n    onCallbackRef.current = onCallback\n  }, [onCallback])\n\n  // Create the channel instance using useMemo to keep it stable across renders\n  // unless channelName changes.\n  // If channelName is empty, we return null to skip subscription.\n  const channel = useMemo(() => {\n    if (!channelName) { return null }\n    return supabase.channel(channelName)\n  }, [channelName])\n\n  useEffect(() => {\n    if (!channel) { return }\n\n    // Subscribe to the broadcast event\n    channel\n      .on('broadcast', { event: 'wechat-login' }, (payload) => {\n        if (onCallbackRef.current) {\n          onCallbackRef.current(payload)\n        }\n      })\n      .subscribe((status) => {\n        consola.log('Supabase channel subscription status:', status)\n      })\n\n    // Cleanup function to unsubscribe when component unmounts or channel changes\n    return () => {\n      channel.unsubscribe()\n    }\n  }, [channel])\n\n  return {\n    channel,\n    // Provide a manual unsubscribe method if needed, similar to the 'teardown' in the Vue example\n    unsubscribe: () => channel?.unsubscribe(),\n  }\n}\n"
  },
  {
    "path": "src/hooks/use-user.ts",
    "content": "import type { User } from '@supabase/supabase-js'\nimport { UserApi } from '@widget-js/core'\nimport consola from 'consola'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { supabase } from '@/api/supabase'\n\n// Global state to mimic Vue's global ref behavior\nlet globalUser: User | null = null\nconst listeners = new Set<(user: User | null) => void>()\n\nfunction notifyListeners() {\n  listeners.forEach(listener => listener(globalUser))\n}\n\nfunction setGlobalUser(user: User | null) {\n  globalUser = user\n  notifyListeners()\n}\n\n// Initialize the auth listener once (outside the hook)\nsupabase.auth.onAuthStateChange((event, session) => {\n  consola.info('onAuthStateChange', event, session)\n  if (event === 'SIGNED_OUT') {\n    setGlobalUser(null)\n    UserApi.logout()\n  }\n  else if (event === 'USER_UPDATED') {\n    setGlobalUser(session?.user ?? null)\n    if (session?.user) {\n      UserApi.updateUser(session.user)\n    }\n  }\n  else if (event === 'SIGNED_IN') {\n    setGlobalUser(session?.user ?? null)\n    supabase.auth.startAutoRefresh()\n    if (session) {\n      UserApi.login(session)\n    }\n  }\n  else if (event === 'TOKEN_REFRESHED') {\n    if (session) {\n      UserApi.updateSession(session)\n    }\n  }\n})\n\nexport function useUser(onload?: (user?: User) => void) {\n  const [user, setUser] = useState<User | null>(globalUser)\n  const [loading, setLoading] = useState(false)\n\n  // Use a ref for the callback to avoid re-triggering effects if the callback is unstable\n  const onloadRef = useRef(onload)\n\n  useEffect(() => {\n    onloadRef.current = onload\n  }, [onload])\n\n  // Sync local state with global state\n  useEffect(() => {\n    const listener = (newUser: User | null) => {\n      setUser(newUser)\n    }\n    listeners.add(listener)\n\n    // Check if global state changed while we were setting up\n    if (globalUser !== user) {\n      setUser(globalUser)\n    }\n\n    return () => {\n      listeners.delete(listener)\n    }\n  }, [user])\n\n  const refreshUser = useCallback(() => {\n    setLoading(true)\n    supabase.auth.getUser().then(({ data }) => {\n      setGlobalUser(data.user)\n      onloadRef.current?.(data.user || undefined)\n    }).finally(() => {\n      setLoading(false)\n    })\n  }, [])\n\n  // Initial refresh on mount\n  useEffect(() => {\n    refreshUser()\n  }, [refreshUser])\n\n  const nickname = useMemo(() => {\n    if (user) {\n      if (user.user_metadata?.nickname) {\n        return user.user_metadata.nickname\n      }\n      if (user.email) {\n        return user.email.split('@')[0]\n      }\n      return 'User'\n    }\n    return '未登录'\n  }, [user])\n\n  const userId = useMemo(() => {\n    return user?.id || ''\n  }, [user])\n\n  const avatar = useMemo(() => {\n    return user?.user_metadata?.avatar || ''\n  }, [user])\n\n  return { user, refreshUser, loading, nickname, avatar, userId }\n}\n"
  },
  {
    "path": "src/hooks/use-widget-package.ts",
    "content": "import type { RemotePackageUrlInfo } from '@widget-js/core'\nimport { NotificationApi, WidgetPackageApi } from '@widget-js/core'\nimport consola from 'consola'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport semver from 'semver'\n\nconst upgradablePackages = new Set<string>()\nconst upgradingPackages = new Set<string>()\nconst checkingPackages = new Set<string>()\nconst listeners = new Map<string, Set<() => void>>()\n\nfunction subscribe(packageName: string, callback: () => void) {\n  if (!listeners.has(packageName)) {\n    listeners.set(packageName, new Set())\n  }\n  listeners.get(packageName)!.add(callback)\n}\n\nfunction unsubscribe(packageName: string, callback: () => void) {\n  const pkgListeners = listeners.get(packageName)\n  if (pkgListeners) {\n    pkgListeners.delete(callback)\n    if (pkgListeners.size === 0) {\n      listeners.delete(packageName)\n    }\n  }\n}\n\nfunction notify(packageName: string) {\n  listeners.get(packageName)?.forEach(cb => cb())\n}\n\nexport function useWidgetPackage(packageName: string, remoteVersion: string, remoteUrlInfo?: RemotePackageUrlInfo) {\n  const { t } = useTranslation()\n  const [upgradable, setUpgradable] = useState(upgradablePackages.has(packageName))\n  const [upgrading, setUpgrading] = useState(upgradingPackages.has(packageName))\n  // We don't necessarily need to expose 'checking' as a return value based on the Vue type,\n  // but we can track it internally or use the global set to prevent double checking.\n\n  useEffect(() => {\n    const updateState = () => {\n      setUpgradable(upgradablePackages.has(packageName))\n      setUpgrading(upgradingPackages.has(packageName))\n    }\n\n    // Initial sync in case it changed before effect ran\n    updateState()\n\n    subscribe(packageName, updateState)\n    return () => {\n      unsubscribe(packageName, updateState)\n    }\n  }, [packageName])\n\n  const checkUpgrade = useCallback(async () => {\n    if (checkingPackages.has(packageName)) {\n      return false\n    }\n\n    checkingPackages.add(packageName)\n    // We don't trigger re-render for checking status as it's not in the return interface\n\n    try {\n      const widgetPackage = await WidgetPackageApi.getPackage(packageName)\n      if (widgetPackage) {\n        if (widgetPackage.name === 'widget.js.fun') {\n          consola.log(remoteVersion)\n        }\n\n        const isUpgradable = semver.gt(remoteVersion, widgetPackage.version ?? '1.0.0')\n\n        if (isUpgradable) {\n          if (!upgradablePackages.has(packageName)) {\n            upgradablePackages.add(packageName)\n            notify(packageName)\n          }\n        }\n        else {\n          if (upgradablePackages.has(packageName)) {\n            upgradablePackages.delete(packageName)\n            notify(packageName)\n          }\n        }\n        return isUpgradable\n      }\n\n      if (upgradablePackages.has(packageName)) {\n        upgradablePackages.delete(packageName)\n        notify(packageName)\n      }\n    }\n    catch (e) {\n      consola.error(e)\n    }\n    finally {\n      checkingPackages.delete(packageName)\n    }\n\n    return false\n  }, [packageName, remoteVersion])\n\n  const upgradePackage = useCallback(async () => {\n    upgradingPackages.add(packageName)\n    notify(packageName)\n\n    try {\n      if (!remoteUrlInfo) {\n        NotificationApi.error(t('update.packageNotConfigured'))\n        return\n      }\n\n      await WidgetPackageApi.upgrade(packageName, remoteUrlInfo)\n\n      upgradingPackages.delete(packageName)\n      upgradablePackages.delete(packageName)\n      NotificationApi.success(t('update.packageSuccess'))\n      notify(packageName)\n    }\n    catch (e) {\n      consola.error(e)\n      // Remove from upgrading if failed\n      upgradingPackages.delete(packageName)\n      notify(packageName)\n    }\n  }, [packageName, remoteUrlInfo])\n\n  return {\n    upgradable,\n    upgrading,\n    checkUpgrade,\n    upgradePackage,\n  }\n}\n"
  },
  {
    "path": "src/i18n/config.ts",
    "content": "import i18n from 'i18next'\nimport LanguageDetector from 'i18next-browser-languagedetector'\nimport { initReactI18next } from 'react-i18next'\nimport enTranslation from './locales/en/translation.json'\nimport zhTranslation from './locales/zh/translation.json'\n\ni18n\n  .use(LanguageDetector)\n  .use(initReactI18next)\n  .init({\n    fallbackLng: 'zh',\n    debug: true,\n    resources: {\n      en: {\n        translation: enTranslation,\n      },\n      zh: {\n        translation: zhTranslation,\n      },\n    },\n    interpolation: {\n      escapeValue: false, // not needed for react as it escapes by default\n    },\n  })\n\nexport default i18n\n"
  },
  {
    "path": "src/i18n/locales/en/translation.json",
    "content": "{\n  \"network\": {\n    \"failed\": \"Load failed: {{msg}}\",\n    \"offline\": \"Network offline\"\n  },\n  \"loading\": \"Loading...\",\n  \"manager\": {\n    \"title\": \"Title\",\n    \"name\": \"Name\",\n    \"remove\": \"Remove\",\n    \"removeDesc\": \"Are you sure to remove {{name}}?\",\n    \"confirmRemove\": \"Are you sure to remove?\"\n  },\n  \"settings\": {\n    \"title\": \"Settings\",\n    \"widgetPackage\": {\n      \"managerTitle\": \"Installed Management\",\n      \"title\": \"Title:\",\n      \"name\": \"Name:\",\n      \"installPath\": \"Install Path:\",\n      \"uninstall\": \"Uninstall\",\n      \"uninstallConfirm\": \"Are you sure you want to uninstall?\",\n      \"uninstallDesc\": \"Uninstalling {{name}} cannot be undone.\",\n      \"cancel\": \"Cancel\",\n      \"confirm\": \"Confirm\"\n    }\n  },\n  \"sidebar\": {\n    \"addWidget\": \"Add Widget\",\n    \"packageManagement\": \"Widget Package\",\n    \"generalSettings\": \"General Settings\",\n    \"globalTheme\": \"Global Theme\",\n    \"proxySettings\": \"Proxy\",\n    \"ai\": \"AI\",\n    \"widgetManagement\": \"Widget Management\",\n    \"settings\": \"Settings\",\n    \"loading\": \"Loading...\",\n    \"widgetHub\": \"Widget Hub\",\n    \"dev\": \"Widget Dev\"\n  },\n  \"dev\": {\n    \"emptyTitle\": \"Develop Your Own Desktop Widget\",\n    \"viewDocs\": \"View Documentation\"\n  },\n  \"general\": {\n    \"title\": \"General Settings\",\n    \"appSettings\": \"App Settings\",\n    \"launchAtStartup\": \"Launch at Startup\",\n    \"language\": \"Language\",\n    \"selectLanguage\": \"Select Language\",\n    \"developerMode\": \"Developer Mode\",\n    \"currentVersion\": \"Current Version\",\n    \"desktopSettings\": \"Desktop Settings\",\n    \"gridSize\": \"Grid Size\",\n    \"socialAccounts\": \"Social Accounts\"\n  },\n  \"proxy\": {\n    \"title\": \"Proxy Settings\",\n    \"warning\": \"Configuring a proxy server may cause network connection interruptions, please proceed with caution.\",\n    \"protocol\": \"Protocol\",\n    \"server\": \"Server\",\n    \"port\": \"Port\",\n    \"clearProxy\": \"Clear Proxy\"\n  },\n  \"theme\": {\n    \"title\": \"Global Theme\",\n    \"presets\": \"Presets\",\n    \"createPreset\": {\n      \"button\": \"Create preset\",\n      \"title\": \"Create theme preset\",\n      \"description\": \"Save the current theme as a reusable preset.\",\n      \"namePlaceholder\": \"Enter a theme name\",\n      \"cancel\": \"Cancel\",\n      \"confirm\": \"Create\",\n      \"emptyName\": \"Please enter a theme name\",\n      \"duplicateName\": \"A preset with the same name already exists\",\n      \"success\": \"Theme preset created\"\n    },\n    \"customization\": \"Customization\",\n    \"fontSize\": \"Font Size\",\n    \"borderRadius\": \"Border Radius\",\n    \"fontFamily\": \"Font Family\",\n    \"systemDefault\": \"System Default\",\n    \"primaryColor\": \"Primary Color\",\n    \"textColor\": \"Text Color\",\n    \"backgroundColor\": \"Background Color\",\n    \"borderColor\": \"Border Color\",\n    \"shadowColor\": \"Shadow Color\",\n    \"dividerColor\": \"Divider Color\",\n    \"translucent\": \"Translucent\",\n    \"dark\": \"Dark\",\n    \"light\": \"Light\",\n    \"searchFont\": \"Search font...\",\n    \"fontNotFound\": \"No font found\",\n    \"colors\": {\n      \"title\": \"Colors\",\n      \"base\": \"Base\",\n      \"primary\": \"Primary\",\n      \"secondary\": \"Secondary\",\n      \"muted\": \"Muted\",\n      \"accent\": \"Accent\",\n      \"destructive\": \"Destructive\",\n      \"cardPopover\": \"Card & Popover\",\n      \"bordersInputs\": \"Borders & Inputs\",\n      \"background\": \"Background\",\n      \"foreground\": \"Foreground\",\n      \"card\": \"Card\",\n      \"cardForeground\": \"Card Foreground\",\n      \"popover\": \"Popover\",\n      \"popoverForeground\": \"Popover Foreground\",\n      \"primaryForeground\": \"Primary Foreground\",\n      \"secondaryForeground\": \"Secondary Foreground\",\n      \"mutedForeground\": \"Muted Foreground\",\n      \"accentForeground\": \"Accent Foreground\",\n      \"destructiveForeground\": \"Destructive Foreground\",\n      \"border\": \"Border\",\n      \"input\": \"Input\",\n      \"ring\": \"Ring\",\n      \"shadow\": \"Shadow\",\n      \"innerShadow\": \"Inner Shadow\"\n    },\n    \"typography\": {\n      \"title\": \"Typography\"\n    },\n    \"radius\": {\n      \"title\": \"Radius\",\n      \"sm\": \"Small Radius (sm)\",\n      \"md\": \"Medium Radius (md)\",\n      \"lg\": \"Large Radius (lg/Widget Usage)\",\n      \"full\": \"Full Radius\"\n    },\n    \"shadow\": {\n      \"title\": \"Shadow\",\n      \"sm\": \"Small Shadow (sm)\",\n      \"md\": \"Medium Shadow (md)\",\n      \"lg\": \"Large Shadow (lg)\"\n    },\n    \"preview\": {\n      \"title\": \"Theme Preview\",\n      \"description\": \"This is how your widgets and app components will look with the current theme settings.\",\n      \"cardTitle\": \"Example Component\",\n      \"cardDesc\": \"A descriptive subtitle for the card.\",\n      \"email\": \"Email Address\",\n      \"emailPlaceholder\": \"name@example.com\",\n      \"enableNotifications\": \"Enable notifications\",\n      \"cancel\": \"Cancel\",\n      \"save\": \"Save Changes\",\n      \"buttons\": \"Buttons\",\n      \"primary\": \"Primary\",\n      \"secondary\": \"Secondary\",\n      \"destructive\": \"Destructive\",\n      \"outline\": \"Outline\",\n      \"ghost\": \"Ghost\",\n      \"inputRing\": \"Input & Focus Ring\",\n      \"inputPlaceholder\": \"Focus me to see the ring color...\",\n      \"slider\": \"Slider\"\n    }\n  },\n  \"dashboard\": {\n    \"home\": \"Home\",\n    \"dashboard\": \"Dashboard\"\n  },\n  \"appInfo\": {\n    \"title\": \"App Info\",\n    \"loading\": \"Loading...\"\n  },\n  \"user\": {\n    \"account\": \"Account\",\n    \"logout\": \"Log out\"\n  },\n  \"tray\": {\n    \"appVersion\": \"Version\",\n    \"systemVersion\": \"System\",\n    \"runningWidgets\": \"Running Widgets\",\n    \"addWidget\": \"Add Widget\",\n    \"settings\": {\n      \"title\": \"Settings\"\n    },\n    \"checkUpdates\": \"Check Updates\",\n    \"shareApp\": \"Share\",\n    \"restartWidgets\": \"Restart\",\n    \"exit\": \"Exit\",\n    \"suggestions\": \"Suggestions\",\n    \"downloadLinkCopied\": \"Download link copied\",\n    \"infoCopied\": \"Info copied\",\n    \"restartWidgetsConfirm\": \"Are you sure to restart all widgets?\",\n    \"yes\": \"Yes\",\n    \"no\": \"No\"\n  },\n  \"notification\": {\n    \"enableDevMode\": \"Developer mode enabled\"\n  },\n  \"drop\": {\n    \"title\": \"Install Local Widget Package\",\n    \"desc\": \"Drag and drop .zip widget package here to install\",\n    \"dismiss\": \"Got it\"\n  },\n  \"update\": {\n    \"upgradeToNew\": \"Please upgrade app to use this widget\",\n    \"success\": \"Update successful\",\n    \"packageNotConfigured\": \"Widget package info not configured\",\n    \"packageSuccess\": \"Widget package upgraded successfully\",\n    \"failed\": \"Update failed\",\n    \"windowTitle\": \"Check for Updates\",\n    \"alreadyLatestVersion\": \"Already latest version\",\n    \"newVersionDetect\": \"New version detected\",\n    \"ignore\": \"Ignore\",\n    \"storePage\": \"Store Page\",\n    \"update\": \"Update\",\n    \"check\": \"Check for Updates\"\n  },\n  \"search\": {\n    \"title\": \"Add Widget\",\n    \"placeholder\": \"Search widgets\",\n    \"installedManagement\": \"Installed\",\n    \"developerTip\": \"Developer mode enabled\",\n    \"devDoc\": \"Developer Docs\",\n    \"enable\": \"Enable\",\n    \"disable\": \"Disable\",\n    \"desktop\": \"Desktop\",\n    \"overlap\": \"Overlap\",\n    \"tray\": \"Tray\",\n    \"upgrade\": \"Upgrade\",\n    \"installOffline\": \"Install Offline Widget\",\n    \"desktopShortcut\": \"Shortcut\"\n  },\n  \"tags\": {\n    \"all\": \"All\",\n    \"ai\": \"AI\",\n    \"tools\": \"Tools\",\n    \"productivity\": \"Productivity\",\n    \"news\": \"News\",\n    \"weather\": \"Weather\",\n    \"fun\": \"Fun\",\n    \"calendar\": \"Calendar\",\n    \"time\": \"Time\",\n    \"finance\": \"Finance\",\n    \"photo\": \"Photo\",\n    \"installed\": \"Installed\",\n    \"debug\": \"Debug\"\n  },\n  \"feature\": {\n    \"wishlist\": \"Wishlist\",\n    \"wishlistDesc\": \"Vote for features you want to see in the next version!\"\n  },\n  \"install\": {\n    \"success\": \"Install successful\",\n    \"failed\": \"Install failed\",\n    \"noUrl\": \"Download URL not found\",\n    \"action\": \"Install\"\n  },\n  \"error\": {\n    \"oops\": \"Oops!\",\n    \"unexpected\": \"Sorry, an unexpected error has occurred.\",\n    \"unknown\": \"Unknown error\",\n    \"backToHome\": \"Back to Home\"\n  },\n  \"aiPage\": {\n    \"loadPackagesFailed\": \"Failed to load packages\",\n    \"loadHistoryFailed\": \"Failed to load history\",\n    \"tokenPackages\": \"Token Packages\",\n    \"purchasePackage\": \"Purchase Package\",\n    \"expirationDate\": \"Expiration Date:\",\n    \"lifetime\": \"Lifetime\",\n    \"used\": \"Used:\",\n    \"total\": \"Total:\",\n    \"noPackages\": \"No packages available\",\n    \"usageHistory\": \"Usage History\",\n    \"time\": \"Time\",\n    \"model\": \"Model\",\n    \"type\": \"Type\",\n    \"tokens\": \"Tokens\",\n    \"remark\": \"Remark\",\n    \"loading\": \"Loading...\",\n    \"noHistory\": \"No history records\"\n  },\n  \"sizePage\": {\n    \"title\": \"Widget Size Settings\",\n    \"width\": \"Width\",\n    \"height\": \"Height\",\n    \"widthDesc\": \"Set the width of the widget ({{value}} px), range {{min}} - {{max}} px.\",\n    \"heightDesc\": \"Set the height of the widget ({{value}} px), range {{min}} - {{max}} px.\"\n  }\n}\n"
  },
  {
    "path": "src/i18n/locales/zh/translation.json",
    "content": "{\n  \"network\": {\n    \"failed\": \"加载失败: {{msg}}\",\n    \"offline\": \"网络已断开\"\n  },\n  \"loading\": \"加载中...\",\n  \"manager\": {\n    \"title\": \"标题\",\n    \"name\": \"名称\",\n    \"remove\": \"移除\",\n    \"removeDesc\": \"确定要移除 {{name}} 吗？\",\n    \"confirmRemove\": \"确定要删除吗？\"\n  },\n  \"settings\": {\n    \"title\": \"设置\",\n    \"widgetPackage\": {\n      \"managerTitle\": \"已安装管理\",\n      \"title\": \"标题：\",\n      \"name\": \"名称：\",\n      \"installPath\": \"安装路径：\",\n      \"uninstall\": \"卸载\",\n      \"uninstallConfirm\": \"确定要卸载吗？\",\n      \"uninstallDesc\": \"卸载 {{name}} 将无法恢复。\",\n      \"cancel\": \"取消\",\n      \"confirm\": \"确定\"\n    }\n  },\n  \"sidebar\": {\n    \"addWidget\": \"添加组件\",\n    \"packageManagement\": \"组件包管理\",\n    \"generalSettings\": \"常用设置\",\n    \"globalTheme\": \"全局主题\",\n    \"proxySettings\": \"代理设置\",\n    \"ai\": \"AI\",\n    \"widgetManagement\": \"组件管理\",\n    \"settings\": \"设置\",\n    \"loading\": \"加载中...\",\n    \"widgetHub\": \"桌面组件\",\n    \"dev\": \"开发组件\"\n  },\n  \"dev\": {\n    \"emptyTitle\": \"开发属于自己的桌面组件\",\n    \"viewDocs\": \"查看文档\"\n  },\n  \"general\": {\n    \"title\": \"常用设置\",\n    \"appSettings\": \"应用设置\",\n    \"launchAtStartup\": \"开机自启动\",\n    \"language\": \"语言\",\n    \"selectLanguage\": \"选择语言\",\n    \"developerMode\": \"开发者模式\",\n    \"currentVersion\": \"当前版本\",\n    \"desktopSettings\": \"桌面设置\",\n    \"gridSize\": \"网格大小\",\n    \"socialAccounts\": \"社交账号\"\n  },\n  \"proxy\": {\n    \"title\": \"代理设置\",\n    \"warning\": \"配置代理服务器可能会导致网络连接中断，请谨慎操作。\",\n    \"protocol\": \"协议\",\n    \"server\": \"服务器\",\n    \"port\": \"端口\",\n    \"clearProxy\": \"清除代理\"\n  },\n  \"theme\": {\n    \"title\": \"全局主题\",\n    \"presets\": \"预设\",\n    \"createPreset\": {\n      \"button\": \"创建预设\",\n      \"title\": \"创建主题预设\",\n      \"description\": \"为当前主题保存一个可重复使用的预设。\",\n      \"namePlaceholder\": \"请输入主题名称\",\n      \"cancel\": \"取消\",\n      \"confirm\": \"创建\",\n      \"emptyName\": \"请输入主题名称\",\n      \"duplicateName\": \"已存在同名主题预设\",\n      \"success\": \"主题预设已创建\"\n    },\n    \"customization\": \"自定义\",\n    \"fontSize\": \"字体大小\",\n    \"borderRadius\": \"圆角大小\",\n    \"fontFamily\": \"字体\",\n    \"systemDefault\": \"系统默认\",\n    \"primaryColor\": \"主色调\",\n    \"textColor\": \"文本颜色\",\n    \"backgroundColor\": \"背景颜色\",\n    \"borderColor\": \"边框颜色\",\n    \"shadowColor\": \"阴影颜色\",\n    \"dividerColor\": \"分割线颜色\",\n    \"translucent\": \"半透明\",\n    \"dark\": \"暗色\",\n    \"light\": \"亮色\",\n    \"searchFont\": \"搜索字体...\",\n    \"fontNotFound\": \"未找到字体\",\n    \"colors\": {\n      \"title\": \"颜色体系\",\n      \"base\": \"基础色调\",\n      \"primary\": \"主色调\",\n      \"secondary\": \"次色调\",\n      \"muted\": \"弱化色调\",\n      \"accent\": \"强调色调\",\n      \"destructive\": \"危险色调\",\n      \"cardPopover\": \"卡片与浮层\",\n      \"bordersInputs\": \"边框与输入\",\n      \"background\": \"背景色\",\n      \"foreground\": \"前景色\",\n      \"card\": \"卡片背景\",\n      \"cardForeground\": \"卡片文本\",\n      \"popover\": \"浮层背景\",\n      \"popoverForeground\": \"浮层文本\",\n      \"primaryForeground\": \"主文本色\",\n      \"secondaryForeground\": \"次文本色\",\n      \"mutedForeground\": \"弱化文本色\",\n      \"accentForeground\": \"强调文本色\",\n      \"destructiveForeground\": \"危险文本色\",\n      \"border\": \"边框色\",\n      \"input\": \"输入框背景\",\n      \"ring\": \"焦点环\",\n      \"shadow\": \"阴影色\",\n      \"innerShadow\": \"内阴影色\"\n    },\n    \"typography\": {\n      \"title\": \"排版\"\n    },\n    \"radius\": {\n      \"title\": \"圆角\",\n      \"sm\": \"小圆角 (sm)\",\n      \"md\": \"中圆角 (md)\",\n      \"lg\": \"大圆角 (lg/组件使用)\",\n      \"full\": \"全圆角 (full)\"\n    },\n    \"shadow\": {\n      \"title\": \"阴影\",\n      \"sm\": \"小阴影 (sm)\",\n      \"md\": \"中阴影 (md)\",\n      \"lg\": \"大阴影 (lg)\"\n    },\n    \"preview\": {\n      \"title\": \"主题预览\",\n      \"description\": \"您的组件和应用将以此主题设置呈现。\",\n      \"cardTitle\": \"示例组件\",\n      \"cardDesc\": \"卡片的描述性副标题。\",\n      \"email\": \"电子邮箱\",\n      \"emailPlaceholder\": \"name@example.com\",\n      \"enableNotifications\": \"启用通知\",\n      \"cancel\": \"取消\",\n      \"save\": \"保存更改\",\n      \"buttons\": \"按钮\",\n      \"primary\": \"主要\",\n      \"secondary\": \"次要\",\n      \"destructive\": \"危险\",\n      \"outline\": \"轮廓\",\n      \"ghost\": \"幽灵\",\n      \"inputRing\": \"输入框与焦点环\",\n      \"inputPlaceholder\": \"点击此处以查看焦点环颜色...\",\n      \"slider\": \"滑块\"\n    }\n  },\n  \"dashboard\": {\n    \"home\": \"首页\",\n    \"dashboard\": \"仪表盘\"\n  },\n  \"appInfo\": {\n    \"title\": \"应用信息\",\n    \"loading\": \"加载中...\"\n  },\n  \"user\": {\n    \"account\": \"账号\",\n    \"logout\": \"退出登录\"\n  },\n  \"tray\": {\n    \"appVersion\": \"版本\",\n    \"systemVersion\": \"系统\",\n    \"runningWidgets\": \"运行中组件\",\n    \"addWidget\": \"添加组件\",\n    \"settings\": {\n      \"title\": \"设置\"\n    },\n    \"checkUpdates\": \"检查更新\",\n    \"shareApp\": \"分享应用\",\n    \"restartWidgets\": \"重启组件\",\n    \"exit\": \"退出\",\n    \"suggestions\": \"反馈建议\",\n    \"downloadLinkCopied\": \"下载链接已复制\",\n    \"infoCopied\": \"信息已复制\",\n    \"restartWidgetsConfirm\": \"确定要重启所有组件吗？\",\n    \"yes\": \"是\",\n    \"no\": \"否\"\n  },\n  \"notification\": {\n    \"enableDevMode\": \"开发者模式已开启\"\n  },\n  \"drop\": {\n    \"title\": \"安装本地组件包\",\n    \"desc\": \"将组件包.zip拖放到此处即可安装\",\n    \"dismiss\": \"知道了\"\n  },\n  \"update\": {\n    \"upgradeToNew\": \"请升级应用以使用此组件\",\n    \"success\": \"更新成功\",\n    \"packageNotConfigured\": \"未配置组件包信息\",\n    \"packageSuccess\": \"组件包升级成功\",\n    \"failed\": \"更新失败\",\n    \"windowTitle\": \"检查更新\",\n    \"alreadyLatestVersion\": \"当前已是最新版本\",\n    \"newVersionDetect\": \"发现新版本\",\n    \"ignore\": \"忽略\",\n    \"storePage\": \"商店页面\",\n    \"update\": \"更新\",\n    \"check\": \"检查更新\"\n  },\n  \"search\": {\n    \"title\": \"添加组件\",\n    \"placeholder\": \"搜索组件\",\n    \"installedManagement\": \"已安装\",\n    \"developerTip\": \"开发模式已开启\",\n    \"devDoc\": \"开发文档\",\n    \"enable\": \"启用\",\n    \"disable\": \"禁用\",\n    \"desktop\": \"桌面\",\n    \"overlap\": \"悬浮窗\",\n    \"tray\": \"托盘\",\n    \"upgrade\": \"升级\",\n    \"installOffline\": \"安装离线组件\",\n    \"desktopShortcut\": \"桌面图标\"\n  },\n  \"tags\": {\n    \"all\": \"全部\",\n    \"ai\": \"AI\",\n    \"tools\": \"工具\",\n    \"productivity\": \"效率\",\n    \"news\": \"新闻\",\n    \"weather\": \"天气\",\n    \"fun\": \"娱乐\",\n    \"calendar\": \"日历\",\n    \"time\": \"时间\",\n    \"finance\": \"财经\",\n    \"photo\": \"照片\",\n    \"installed\": \"已安装\",\n    \"debug\": \"调试\"\n  },\n  \"feature\": {\n    \"wishlist\": \"心愿单\",\n    \"wishlistDesc\": \"投票选出你希望在下一个版本中看到的功能！\"\n  },\n  \"install\": {\n    \"success\": \"安装成功\",\n    \"failed\": \"安装失败\",\n    \"noUrl\": \"未找到下载链接\",\n    \"action\": \"安装\"\n  },\n  \"error\": {\n    \"oops\": \"哎呀！\",\n    \"unexpected\": \"抱歉，发生了意外错误。\",\n    \"unknown\": \"未知错误\",\n    \"backToHome\": \"返回首页\"\n  },\n  \"aiPage\": {\n    \"loadPackagesFailed\": \"加载套餐失败\",\n    \"loadHistoryFailed\": \"加载历史记录失败\",\n    \"tokenPackages\": \"Token套餐\",\n    \"purchasePackage\": \"购买套餐\",\n    \"expirationDate\": \"过期时间:\",\n    \"lifetime\": \"永久有效\",\n    \"used\": \"已用:\",\n    \"total\": \"总量:\",\n    \"noPackages\": \"暂无可用套餐\",\n    \"usageHistory\": \"使用记录\",\n    \"time\": \"时间\",\n    \"model\": \"模型\",\n    \"type\": \"类型\",\n    \"tokens\": \"Token数\",\n    \"remark\": \"备注\",\n    \"loading\": \"加载中...\",\n    \"noHistory\": \"暂无历史记录\"\n  },\n  \"sizePage\": {\n    \"title\": \"组件大小设置\",\n    \"width\": \"宽度 (Width)\",\n    \"height\": \"高度 (Height)\",\n    \"widthDesc\": \"设置组件的宽度 ({{value}} px)，取值范围 {{min}} - {{max}} px。\",\n    \"heightDesc\": \"设置组件的高度 ({{value}} px)，取值范围 {{min}} - {{max}} px。\"\n  }\n}\n"
  },
  {
    "path": "src/index.css",
    "content": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n@import \"shadcn/tailwind.css\";\n\n@custom-variant dark (&:is(.dark *));\n:root {\n  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n  color-scheme: light dark;\n  color: rgba(255, 255, 255, 0.87);\n  background-color: #242424;\n\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  --radius: 0.625rem;\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.145 0 0);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.145 0 0);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.145 0 0);\n  --primary: oklch(0.205 0 0);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.97 0 0);\n  --secondary-foreground: oklch(0.205 0 0);\n  --muted: oklch(0.97 0 0);\n  --muted-foreground: oklch(0.556 0 0);\n  --accent: oklch(0.97 0 0);\n  --accent-foreground: oklch(0.205 0 0);\n  --destructive: oklch(0.577 0.245 27.325);\n  --border: oklch(0.922 0 0);\n  --input: oklch(0.922 0 0);\n  --ring: oklch(0.708 0 0);\n  --chart-1: oklch(0.646 0.222 41.116);\n  --chart-2: oklch(0.6 0.118 184.704);\n  --chart-3: oklch(0.398 0.07 227.392);\n  --chart-4: oklch(0.828 0.189 84.429);\n  --chart-5: oklch(0.769 0.188 70.08);\n  --sidebar: oklch(0.985 0 0);\n  --sidebar-foreground: oklch(0.145 0 0);\n  --sidebar-primary: oklch(0.205 0 0);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.92 0 0);\n  --sidebar-accent-foreground: oklch(0.205 0 0);\n  --sidebar-border: oklch(0.922 0 0);\n  --sidebar-ring: oklch(0.708 0 0);\n}\n\n\n\n@theme inline {\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --radius-2xl: calc(var(--radius) + 8px);\n  --radius-3xl: calc(var(--radius) + 12px);\n  --radius-4xl: calc(var(--radius) + 16px);\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --color-chart-1: var(--chart-1);\n  --color-chart-2: var(--chart-2);\n  --color-chart-3: var(--chart-3);\n  --color-chart-4: var(--chart-4);\n  --color-chart-5: var(--chart-5);\n  --color-sidebar: var(--sidebar);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-ring: var(--sidebar-ring);\n}\n\n.dark {\n  --background: oklch(0.145 0 0);\n  --foreground: oklch(0.985 0 0);\n  --card: oklch(0.205 0 0);\n  --card-foreground: oklch(0.985 0 0);\n  --popover: oklch(0.205 0 0);\n  --popover-foreground: oklch(0.985 0 0);\n  --primary: oklch(0.922 0 0);\n  --primary-foreground: oklch(0.205 0 0);\n  --secondary: oklch(0.269 0 0);\n  --secondary-foreground: oklch(0.985 0 0);\n  --muted: oklch(0.269 0 0);\n  --muted-foreground: oklch(0.708 0 0);\n  --accent: oklch(0.269 0 0);\n  --accent-foreground: oklch(0.985 0 0);\n  --destructive: oklch(0.704 0.191 22.216);\n  --border: oklch(1 0 0 / 10%);\n  --input: oklch(1 0 0 / 15%);\n  --ring: oklch(0.556 0 0);\n  --chart-1: oklch(0.488 0.243 264.376);\n  --chart-2: oklch(0.696 0.17 162.48);\n  --chart-3: oklch(0.769 0.188 70.08);\n  --chart-4: oklch(0.627 0.265 303.9);\n  --chart-5: oklch(0.645 0.246 16.439);\n  --sidebar: oklch(0.205 0 0);\n  --sidebar-foreground: oklch(0.985 0 0);\n  --sidebar-primary: oklch(0.488 0.243 264.376);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.269 0 0);\n  --sidebar-accent-foreground: oklch(0.985 0 0);\n  --sidebar-border: oklch(1 0 0 / 10%);\n  --sidebar-ring: oklch(0.556 0 0);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n\n.draggable-region {\n  -webkit-app-region: drag;\n}\n\n.draggable-region button,\n.draggable-region a {\n  -webkit-app-region: no-drag;\n}\n\n[data-rmiz-modal-overlay=\"visible\"] {\n  background-color: rgba(0, 0, 0, 0.7) !important;\n}\n"
  },
  {
    "path": "src/lib/request.ts",
    "content": "import axios from 'axios'\nimport { supabase } from '@/api/supabase'\n\nconst service = axios.create({\n  baseURL: 'https://widgetjs.cn/api/v1',\n  timeout: 10000,\n})\n\nservice.interceptors.request.use(\n  async (config) => {\n    const { data: { session } } = await supabase.auth.getSession()\n    if (session?.access_token) {\n      config.headers.Authorization = `Bearer ${session.access_token}`\n    }\n    return config\n  },\n  (error) => {\n    return Promise.reject(error)\n  },\n)\n\nservice.interceptors.response.use(\n  (response) => {\n    const res = response.data\n    if (res.code !== 200) {\n      return Promise.reject(new Error(res.msg || res.message || 'Error'))\n    }\n    return res.data\n  },\n  (error) => {\n    return Promise.reject(error)\n  },\n)\n\nexport default service\n"
  },
  {
    "path": "src/lib/utils.ts",
    "content": "import type { ClassValue } from 'clsx'\nimport { clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
  },
  {
    "path": "src/main.tsx",
    "content": "import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { Toaster } from 'sonner'\nimport App from './app.tsx'\nimport './index.css'\nimport './i18n/config'\n\ncreateRoot(document.getElementById('root')!).render(\n  <StrictMode>\n    <App />\n    <Toaster />\n  </StrictMode>,\n)\n"
  },
  {
    "path": "src/model/app-version.ts",
    "content": "export interface AppVersion {\n  version: string\n  releaseNote: string\n  downloadUrl?: string\n  downloadLink?: string\n  force?: boolean\n}\n"
  },
  {
    "path": "src/pages/add/add-widget-page.tsx",
    "content": "import type { WidgetSearchOptions } from '@widget-js/web-api'\nimport { AppApi, NotificationApi, WidgetApi, WidgetPackageApi } from '@widget-js/core'\nimport { WebWidget } from '@widget-js/web-api'\nimport consola from 'consola'\nimport { FolderDown, Search } from 'lucide-react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { toast } from 'sonner'\nimport { WebWidgetApi } from '@/api/web-widget-api'\nimport { Button } from '@/components/ui/button'\nimport { Input } from '@/components/ui/input'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'\nimport { useDebounce } from '@/hooks/use-debounce'\n\nimport { SearchItem } from './components/search-item'\nimport { WidgetTags } from './components/widget-tags'\n\nexport default function AddWidgetPage() {\n  const { t } = useTranslation()\n  const [keyword, setKeyword] = useState('')\n  const [selectedCategory, setSelectedCategory] = useState('')\n  const [widgets, setWidgets] = useState<WebWidget[]>([])\n  const [_loading, setLoading] = useState(true)\n  const fileInputRef = useRef<HTMLInputElement>(null)\n\n  const debouncedKeyword = useDebounce(keyword, 1000)\n\n  const search = useCallback(async () => {\n    setLoading(true)\n    setWidgets([])\n\n    try {\n      if (selectedCategory === 'installed') {\n        const widgetPackages = await WidgetPackageApi.getPackages()\n        const installedPackages = widgetPackages.filter(it => !it.url.startsWith('http') || it.development)\n        const localWidgets = await WidgetApi.getWidgets()\n        const newWidgets: WebWidget[] = []\n\n        for (const widgetPackage of installedPackages) {\n          newWidgets.push(\n            ...localWidgets\n              .map(it => WebWidget.fromObject(it))\n              .filter(it => it.packageName === widgetPackage.name),\n          )\n        }\n        setWidgets(newWidgets)\n        setLoading(false)\n        return\n      }\n\n      const version = await AppApi.getVersion()\n      const options: WidgetSearchOptions = {\n        page: 1,\n        pageSize: 50,\n        category: selectedCategory,\n        keyword: debouncedKeyword,\n        appVersion: version,\n      }\n\n      let localWidgets = (await WidgetApi.getWidgets()).filter(it => !it.disabled)\n\n      if (selectedCategory) {\n        localWidgets = localWidgets.filter(it => it.categories && it.categories.includes(selectedCategory as any))\n      }\n\n      if (debouncedKeyword) {\n        localWidgets = localWidgets.filter((it) => {\n          const title = JSON.stringify(it.title)\n          const description = JSON.stringify(it.description)\n          return title.includes(debouncedKeyword) || description.includes(debouncedKeyword)\n        })\n      }\n\n      try {\n        const res = await WebWidgetApi.search(options)\n        const remoteWidgets = res.data\n          .map((it: any) => WebWidget.fromObject(it))\n          .filter((it: any) => it.name !== 'cn.widgetjs.widgets.dynamic_island')\n\n        const mergedWidgets = [...remoteWidgets]\n\n        for (const localWidget of localWidgets) {\n          if (mergedWidgets.some(it => it.name === localWidget.name)) {\n            continue\n          }\n          mergedWidgets.push(WebWidget.fromObject(localWidget))\n        }\n        setWidgets(mergedWidgets)\n      }\n      catch (e) {\n        setWidgets(localWidgets.map(it => WebWidget.fromObject(it)))\n      }\n    }\n    catch (e) {\n      consola.error(e)\n    }\n    finally {\n      setLoading(false)\n    }\n  }, [selectedCategory, debouncedKeyword])\n\n  useEffect(() => {\n    search()\n  }, [search])\n\n  useEffect(() => {\n    document.title = t('search.title')\n  }, [t])\n\n  const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {\n    const file = event.target.files?.[0]\n    if (!file) { return }\n\n    try {\n      consola.info(`开始安装组件包: `, file)\n      await WidgetPackageApi.install((file as any).path)\n      await NotificationApi.success('安装成功')\n      window.location.reload()\n    }\n    catch (e: any) {\n      toast.error(`安装失败: ${e.message}`)\n    }\n    event.target.value = ''\n  }\n\n  return (\n    <div className=\"h-full bg-background flex flex-col relative overflow-hidden\">\n      {/* Header */}\n      <div className=\"h-[54px] px-4 flex gap-2 items-center\">\n        <div className=\"relative flex-1\">\n          <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\n          <Input\n            value={keyword}\n            onChange={e => setKeyword(e.target.value)}\n            placeholder={t('search.placeholder')}\n            className=\"pl-9\"\n            onKeyDown={e => e.key === 'Enter' && search()}\n          />\n        </div>\n        <input\n          type=\"file\"\n          ref={fileInputRef}\n          className=\"hidden\"\n          accept=\".zip\"\n          onChange={handleFileChange}\n        />\n        <TooltipProvider>\n          <Tooltip>\n            <TooltipTrigger asChild>\n              <Button\n                variant=\"outline\"\n                size=\"icon\"\n                onClick={() => fileInputRef.current?.click()}\n              >\n                <FolderDown className=\"h-4 w-4\" />\n              </Button>\n            </TooltipTrigger>\n            <TooltipContent>\n              <p>{t('search.installOffline')}</p>\n            </TooltipContent>\n          </Tooltip>\n        </TooltipProvider>\n      </div>\n\n      <div className=\"px-4 pb-2\">\n        <WidgetTags value={selectedCategory} onChange={setSelectedCategory} />\n      </div>\n\n      <div className=\"flex-1 overflow-y-auto\">\n        <div className=\"px-4 pb-4\">\n          <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n            {widgets.map(widget => (\n              <SearchItem key={widget.name} widget={widget} />\n            ))}\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/add/components/feature-wall-list.tsx",
    "content": "import { Sparkles } from 'lucide-react'\nimport { useTranslation } from 'react-i18next'\nimport { Card, CardContent } from '@/components/ui/card'\n\nexport function FeatureWallList() {\n  const { t } = useTranslation()\n\n  return (\n    <Card className=\"w-full bg-muted/20 border-dashed\">\n      <CardContent className=\"flex flex-col items-center justify-center p-8 text-center text-muted-foreground\">\n        <Sparkles className=\"h-12 w-12 mb-4 text-yellow-500\" />\n        <h3 className=\"text-lg font-semibold mb-2\">{t('feature.wishlist', 'Wishlist Feature')}</h3>\n        <p className=\"text-sm max-w-sm\">\n          {t('feature.wishlistDesc', 'Vote for features you want to see in the next version!')}\n        </p>\n      </CardContent>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "src/pages/add/components/search-item.tsx",
    "content": "import type { WebWidget } from '@widget-js/web-api'\nimport {\n  AppApi,\n  BrowserWindowApi,\n  DeployedWidgetApi,\n  DeployMode,\n  NotificationApi,\n  WidgetApi,\n  WidgetPackageUtils,\n} from '@widget-js/core'\nimport { ArrowUpCircle, Loader2 } from 'lucide-react'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport semver from 'semver'\nimport { Button } from '@/components/ui/button'\nimport { Card } from '@/components/ui/card'\nimport { useDebugConfig } from '@/hooks/use-debug-config'\nimport { useWidgetPackage } from '@/hooks/use-widget-package'\nimport WidgetUtil from '@/utils/widget-util'\nimport WidgetContainer from './widget-container'\n\ninterface SearchItemProps {\n  widget: WebWidget\n}\n\nexport function SearchItem({ widget }: SearchItemProps) {\n  const { t, i18n } = useTranslation()\n  const [isDev] = useDebugConfig()\n  const [isWidgetHosted, setIsWidgetHosted] = useState(false)\n  const [adding, setAdding] = useState(false)\n\n  const updateWidgetAdd = useCallback(async () => {\n    if (widget.isSupportBackground()) {\n      const widgets = await DeployedWidgetApi.getDeployedWidgets(widget.name)\n      if (widgets) {\n        setIsWidgetHosted(widgets.length > 0)\n      }\n      else {\n        setIsWidgetHosted(false)\n      }\n    }\n  }, [widget])\n\n  useEffect(() => {\n    updateWidgetAdd()\n  }, [updateWidgetAdd])\n\n  const remotePackage = widget.package\n  const { upgradable, upgrading, checkUpgrade, upgradePackage } = useWidgetPackage(\n    widget.packageName!,\n    remotePackage?.version ?? '0.0.0',\n    remotePackage?.remote,\n  )\n\n  useEffect(() => {\n    checkUpgrade()\n  }, [checkUpgrade])\n\n  const removeWidget = async () => {\n    await DeployedWidgetApi.removeDeployedWidgetByName(widget.name)\n    await updateWidgetAdd()\n  }\n\n  const openDevTools = () => {\n    DeployedWidgetApi.openDevTools(widget.name)\n  }\n\n  const addWidget = async (mode: DeployMode) => {\n    try {\n      setAdding(true)\n      let versionPass = true\n      const appVersion = await AppApi.getVersion('app')\n\n      if (widget.requiredAppVersion && appVersion) {\n        if (semver.gte(appVersion, widget.requiredAppVersion)) {\n          versionPass = true\n        }\n        else {\n          versionPass = false\n          NotificationApi.error(t('update.upgradeToNew', 'Please upgrade app'))\n        }\n      }\n\n      if (versionPass) {\n        const url = widget.package?.remote\n          ? WidgetPackageUtils.getRemotePackageJsonUrl(widget.package?.remote)\n          : undefined\n\n        await DeployedWidgetApi.addWidget({\n          widgetName: widget.name,\n          deployMode: mode,\n          packageJsonUrl: url,\n        })\n      }\n    }\n    finally {\n      setAdding(false)\n    }\n  }\n\n  const addBackgroundWidget = async () => {\n    await addWidget(DeployMode.BACKGROUND)\n    await updateWidgetAdd()\n  }\n\n  const openBackgroundWidgetSettings = async () => {\n    WidgetApi.openConfigPageByName(widget.name)\n  }\n\n  const onClickAddNormal = async () => {\n    await addWidget(DeployMode.NORMAL)\n  }\n\n  // Helper to get localized text\n  const getLocalizedText = (obj: any) => {\n    if (typeof obj === 'string') { return obj }\n    if (!obj) { return '' }\n    return obj[i18n.language] || obj.en || Object.values(obj)[0] || ''\n  }\n\n  const title = getLocalizedText(widget.title)\n  const description = getLocalizedText(widget.description)\n\n  return (\n    <Card className=\"flex flex-col p-4 bg-white rounded-xl mb-5 items-center relative\">\n      {adding && (\n        <div className=\"absolute inset-0 bg-background/50 flex items-center justify-center z-10 rounded-xl backdrop-blur-[1px]\">\n          <Loader2 className=\"animate-spin h-8 w-8 text-primary\" />\n        </div>\n      )}\n\n      <div className=\"flex w-full\">\n        <div className=\"flex flex-col items-start\">\n          <span className=\"text-base font-bold text-primary mb-2\">{title}</span>\n          <span className=\"text-muted-foreground text-sm\">{description}</span>\n        </div>\n        {widget.socialLinks && (\n          <div className=\"ml-auto flex gap-2\">\n            {widget.socialLinks.slice(0, 3).map((social: any) => (\n              <img\n                key={social.name}\n                className=\"cursor-pointer h-6 w-6 object-contain hover:opacity-80 transition-opacity\"\n                src={WidgetUtil.getSocialLinkIcon(social.name)}\n                alt={social.name}\n                onClick={() => BrowserWindowApi.openUrl(social.link, { external: true })}\n              />\n            ))}\n          </div>\n        )}\n      </div>\n\n      <WidgetContainer className=\"m-4 w-full border-none shadow-none\" widget={widget} />\n\n      <div className=\"flex gap-4 flex-wrap justify-center\">\n        <div className=\"flex gap-2 flex-wrap justify-center\">\n          {widget.isSupportBackground()\n            ? (\n                <>\n                  {widget.isConfigurable() && (\n                    <Button onClick={openBackgroundWidgetSettings} className=\"rounded-full\">\n                      {t('settings.title', 'Settings')}\n                    </Button>\n                  )}\n                  {!isWidgetHosted\n                    ? (\n                        <Button onClick={addBackgroundWidget} className=\"rounded-full\">\n                          {t('search.enable', 'Enable')}\n                        </Button>\n                      )\n                    : (\n                        <Button variant=\"destructive\" onClick={removeWidget} className=\"rounded-full\">\n                          {t('search.disable', 'Disable')}\n                        </Button>\n                      )}\n                  {isWidgetHosted && isDev && (\n                    <Button variant=\"outline\" onClick={openDevTools} className=\"rounded-full border-yellow-500 text-yellow-600 hover:text-yellow-700 hover:bg-yellow-50\">\n                      DevTools\n                    </Button>\n                  )}\n                  <Button variant=\"outline\" onClick={() => DeployedWidgetApi.createDesktopShortcut(widget.name)} className=\"rounded-full\">\n                    {t('search.desktopShortcut', '桌面图标')}\n                  </Button>\n                </>\n              )\n            : (\n                <>\n                  {widget.isSupportNormal() && (\n                    <Button onClick={onClickAddNormal} className=\"rounded-full\">\n                      {t('search.desktop', 'Desktop')}\n                    </Button>\n                  )}\n                  {widget.isSupportOverlap() && (\n                    <Button onClick={() => addWidget(DeployMode.OVERLAP)} className=\"rounded-full\">\n                      {t('search.overlap', 'Overlap')}\n                    </Button>\n                  )}\n                  {widget.isSupportTray() && (\n                    <Button onClick={() => addWidget(DeployMode.TRAY)} className=\"rounded-full\">\n                      {t('search.tray', 'Tray')}\n                    </Button>\n                  )}\n                </>\n              )}\n        </div>\n\n        {upgradable && (\n          <Button\n            variant=\"secondary\"\n            className=\"rounded-full bg-yellow-500 hover:bg-yellow-600 text-white\"\n            onClick={upgradePackage}\n            disabled={upgrading}\n          >\n            {upgrading ? <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" /> : <ArrowUpCircle className=\"mr-2 h-4 w-4\" />}\n            {t('search.upgrade', 'Upgrade')}\n          </Button>\n        )}\n      </div>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "src/pages/add/components/widget-container.tsx",
    "content": "import type { WebWidget } from '@widget-js/web-api'\nimport { WidgetApi } from '@widget-js/core'\nimport { Image as ImageIcon, ImageOff } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { cn } from '@/lib/utils'\nimport { ZoomImage } from './zoom-image'\n\ninterface WidgetContainerProps {\n  widget: WebWidget\n  className?: string\n}\n\nexport default function WidgetContainer({ widget, className }: WidgetContainerProps) {\n  const [previewImage, setPreviewImage] = useState<string | null>(null)\n  const [loading, setLoading] = useState(true)\n  const [error, setError] = useState(false)\n\n  const cellSize = 72\n  const containerHeight = cellSize * 2\n\n  useEffect(() => {\n    const loadPreview = async () => {\n      if (!widget.previewImage) {\n        setLoading(false)\n        return\n      }\n\n      try {\n        setLoading(true)\n        setError(false)\n\n        if (widget.package && widget.package.remote) {\n          const remote = widget.package.remote\n          const url = `https://${remote.hostname}${remote.base}${widget.previewImage}`\n          setPreviewImage(url)\n        }\n        else if (widget.packageName) {\n          const url = await WidgetApi.getWidgetPackageUrl(widget.packageName)\n          setPreviewImage(url + widget.previewImage)\n        }\n      }\n      catch (e) {\n        console.error('Failed to load preview image', e)\n        setError(true)\n      }\n      finally {\n        setLoading(false)\n      }\n    }\n\n    loadPreview()\n  }, [widget])\n\n  return (\n    <div\n      className={cn('flex flex-col items-center justify-center', className)}\n      style={{ height: `${containerHeight}px` }}\n    >\n      {previewImage\n        ? (\n            <ZoomImage\n              src={previewImage}\n              alt=\"Widget Preview\"\n              className=\"w-full h-auto max-h-[128px] object-contain drop-shadow-md cursor-pointer hover:opacity-90 transition-opacity\"\n              onError={() => setError(true)}\n              style={{ display: error ? 'none' : 'block' }}\n            />\n          )\n        : null}\n\n      {/* Fallback states */}\n      {(error || (!previewImage && !loading)) && (\n        <div className=\"flex flex-col items-center justify-center text-muted-foreground\">\n          <ImageOff size=\"32\" className=\"mb-2\" />\n          <span className=\"text-xs\">Preview unavailable</span>\n        </div>\n      )}\n\n      {loading && (\n        <div className=\"flex flex-col items-center justify-center text-muted-foreground animate-pulse\">\n          <ImageIcon size=\"32\" className=\"mb-2\" />\n          <span className=\"text-xs\">Loading...</span>\n        </div>\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/add/components/widget-tags.tsx",
    "content": "import {\n  Bot,\n  Calendar,\n  Clock,\n  Download,\n  Gamepad2,\n  LayoutGrid,\n  Newspaper,\n  Sun,\n  TrendingUp,\n  Wrench,\n  Zap,\n} from 'lucide-react'\nimport { useTranslation } from 'react-i18next'\nimport { Button } from '@/components/ui/button'\nimport { cn } from '@/lib/utils'\n\ninterface WidgetTagsProps {\n  value: string\n  onChange: (value: string) => void\n  className?: string\n}\n\nexport function WidgetTags({ value, onChange, className }: WidgetTagsProps) {\n  const { t } = useTranslation()\n\n  const tags = [\n    { icon: LayoutGrid, label: t('tags.all', 'All'), value: '' },\n    { icon: Bot, label: t('tags.ai', 'AI'), value: 'ai' },\n    { icon: Wrench, label: t('tags.tools', 'Utilities'), value: 'utilities' },\n    { icon: Zap, label: t('tags.productivity', 'Productivity'), value: 'productivity' },\n    { icon: Newspaper, label: t('tags.news', 'News'), value: 'news' },\n    { icon: Sun, label: t('tags.weather', 'Weather'), value: 'weather' },\n    { icon: Gamepad2, label: t('tags.fun', 'Fun'), value: 'fun' },\n    { icon: Calendar, label: t('tags.calendar', 'Calendar'), value: 'calendar' },\n    { icon: Clock, label: t('tags.time', 'Time'), value: 'time' },\n    { icon: TrendingUp, label: t('tags.finance', 'Finance'), value: 'finance' },\n    { icon: Download, label: t('tags.installed', 'Installed'), value: 'installed' },\n  ]\n\n  return (\n    <div className={cn('flex gap-2 overflow-x-auto pb-2 scrollbar-hide items-center h-[50px]', className)}>\n      {tags.map(tag => (\n        <Button\n          key={tag.value}\n          variant={value === tag.value ? 'default' : 'secondary'}\n          size=\"sm\"\n          onClick={() => onChange(tag.value)}\n          className={cn(\n            'whitespace-nowrap rounded-full px-4 gap-2 transition-all duration-200',\n            value === tag.value ? 'shadow-md' : 'hover:bg-secondary/80',\n          )}\n        >\n          <tag.icon size={14} />\n          {tag.label}\n        </Button>\n      ))}\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/add/components/zoom-image.tsx",
    "content": "import { useCallback, useState } from 'react'\nimport { Controlled as ControlledZoom } from 'react-medium-image-zoom'\nimport 'react-medium-image-zoom/dist/styles.css'\n\ninterface ZoomImageProps {\n  src: string\n  alt?: string\n  className?: string\n  onError?: () => void\n  style?: React.CSSProperties\n}\n\nexport function ZoomImage({ src, alt, className, onError, style }: ZoomImageProps) {\n  const [isZoomed, setIsZoomed] = useState(false)\n\n  const handleZoomChange = useCallback((shouldZoom: boolean) => {\n    setIsZoomed(shouldZoom)\n  }, [])\n\n  return (\n    <>\n      <style>\n        {`\n          [data-rmiz-btn-unzoom] {\n            top: 80px !important;\n            right: 40px !important;\n          }\n        `}\n      </style>\n      <ControlledZoom\n        isZoomed={isZoomed}\n        onZoomChange={handleZoomChange}\n      >\n        <img\n          src={src}\n          alt={alt}\n          className={className}\n          onError={onError}\n          style={style}\n        />\n      </ControlledZoom>\n    </>\n  )\n}\n"
  },
  {
    "path": "src/pages/ai/ai-page.tsx",
    "content": "import type { AiTokenHistory, AiTokenPackage } from '@/api/ai'\nimport { History, Package, ShoppingCart } from 'lucide-react'\nimport * as React from 'react'\nimport { useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { toast } from 'sonner'\nimport { AiApi } from '@/api/ai'\nimport { LoginCheck } from '@/components/login-check'\nimport { PurchaseDialog } from '@/components/purchase-dialog'\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from '@/components/ui/card'\nimport {\n  Pagination,\n  PaginationContent,\n  PaginationItem,\n  PaginationLink,\n  PaginationNext,\n  PaginationPrevious,\n} from '@/components/ui/pagination'\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table'\nimport { useUser } from '@/hooks/use-user'\n\nexport default function AiPage() {\n  const { t } = useTranslation()\n  const { user } = useUser()\n  const [packages, setPackages] = useState<AiTokenPackage[]>([])\n  const [history, setHistory] = useState<AiTokenHistory[]>([])\n  const [historyTotal, setHistoryTotal] = useState(0)\n  const [page, setPage] = useState(1)\n  const [pageSize] = useState(10)\n  const [loading, setLoading] = useState(false)\n\n  // Purchase Dialog State\n  const [purchaseOpen, setPurchaseOpen] = useState(false)\n\n  const loadPackages = async () => {\n    try {\n      const res = await AiApi.getPackages({ page: 1, limit: 3 })\n      setPackages(res.items)\n    }\n    catch (error) {\n      console.error(error)\n      toast.error(t('aiPage.loadPackagesFailed', '加载套餐失败'))\n    }\n  }\n\n  useEffect(() => {\n    if (user) {\n      loadPackages()\n    }\n  }, [user])\n\n  const loadHistory = async () => {\n    try {\n      setLoading(true)\n      const res = await AiApi.getUsage({ page, limit: pageSize })\n      setHistory(res.items)\n      setHistoryTotal(res.total)\n    }\n    catch (error) {\n      console.error(error)\n      toast.error(t('aiPage.loadHistoryFailed', '加载历史记录失败'))\n    }\n    finally {\n      setLoading(false)\n    }\n  }\n\n  const totalPages = Math.ceil(historyTotal / pageSize)\n\n  useEffect(() => {\n    if (user) {\n      loadHistory()\n    }\n  }, [page, user])\n\n  return (\n    <LoginCheck>\n      <div className=\"flex flex-col gap-6 min-w-[800px] mx-auto mb-4\">\n        {/* Packages Section */}\n        <section>\n          <h2 className=\"text-2xl font-bold mb-4 flex items-center gap-2\">\n            <Package className=\"h-6 w-6\" />\n            {t('aiPage.tokenPackages', 'Token套餐')}\n          </h2>\n          <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4\">\n            <Card\n              className=\"cursor-pointer hover:border-primary transition-colors border-dashed flex flex-col items-center justify-center min-h-[180px]\"\n              onClick={() => setPurchaseOpen(true)}\n            >\n              <div className=\"flex flex-col items-center gap-2 text-muted-foreground hover:text-primary\">\n                <ShoppingCart className=\"h-8 w-8\" />\n                <span className=\"font-medium\">{t('aiPage.purchasePackage', '购买套餐')}</span>\n              </div>\n            </Card>\n\n            {packages.map(pkg => (\n              <Card key={pkg.id}>\n                <CardHeader className=\"pb-2\">\n                  <CardTitle className=\"text-lg\">{pkg.name}</CardTitle>\n                  <CardDescription>\n                    {t('aiPage.expirationDate', '过期时间:')}\n                    {' '}\n                    {pkg.expireTime ? new Date(pkg.expireTime).toLocaleDateString() : t('aiPage.lifetime', '永久有效')}\n                  </CardDescription>\n                </CardHeader>\n                <CardContent>\n                  <div className=\"flex flex-col gap-1\">\n                    <div className=\"flex justify-between text-sm\">\n                      <span className=\"text-muted-foreground\">{t('aiPage.used', '已用:')}</span>\n                      <span>{pkg.usedToken.toLocaleString()}</span>\n                    </div>\n                    <div className=\"flex justify-between text-sm\">\n                      <span className=\"text-muted-foreground\">{t('aiPage.total', '总量:')}</span>\n                      <span>{pkg.maxToken.toLocaleString()}</span>\n                    </div>\n                    <div className=\"w-full bg-secondary h-2 rounded-full mt-2 overflow-hidden\">\n                      <div\n                        className=\"bg-primary h-full rounded-full transition-all\"\n                        style={{ width: `${Math.min((pkg.usedToken / pkg.maxToken) * 100, 100)}%` }}\n                      />\n                    </div>\n                  </div>\n                </CardContent>\n              </Card>\n            ))}\n            {packages.length === 0 && (\n              <div className=\"col-span-3 flex text-center justify-center items-center text-muted-foreground py-8 border rounded-lg border-dashed\">\n                {t('aiPage.noPackages', '暂无可用套餐')}\n              </div>\n            )}\n          </div>\n        </section>\n\n        <PurchaseDialog\n          open={purchaseOpen}\n          onOpenChange={setPurchaseOpen}\n          onSuccess={loadPackages}\n        />\n\n        {/* History Section */}\n        <section>\n          <h2 className=\"text-2xl font-bold mb-4 flex items-center gap-2\">\n            <History className=\"h-6 w-6\" />\n            {t('aiPage.usageHistory', '使用记录')}\n          </h2>\n          <Card>\n            <CardContent className=\"p-0\">\n              <Table>\n                <TableHeader>\n                  <TableRow>\n                    <TableHead>{t('aiPage.time', '时间')}</TableHead>\n                    <TableHead>{t('aiPage.model', '模型')}</TableHead>\n                    <TableHead>{t('aiPage.type', '类型')}</TableHead>\n                    <TableHead>{t('aiPage.tokens', 'Token数')}</TableHead>\n                    <TableHead>{t('aiPage.remark', '备注')}</TableHead>\n                  </TableRow>\n                </TableHeader>\n                <TableBody>\n                  {loading\n                    ? (\n                        <TableRow>\n                          <TableCell colSpan={5} className=\"text-center py-8\">\n                            {t('aiPage.loading', '加载中...')}\n                          </TableCell>\n                        </TableRow>\n                      )\n                    : history.length > 0\n                      ? (\n                          history.map(item => (\n                            <TableRow key={item.id}>\n                              <TableCell>{new Date(item.create_time).toLocaleString()}</TableCell>\n                              <TableCell>{item.model || '-'}</TableCell>\n                              <TableCell>{item.request_type || '-'}</TableCell>\n                              <TableCell>{item.total_tokens?.toLocaleString() || 0}</TableCell>\n                              <TableCell>{item.remark || '-'}</TableCell>\n                            </TableRow>\n                          ))\n                        )\n                      : (\n                          <TableRow>\n                            <TableCell colSpan={5} className=\"text-center py-8 text-muted-foreground\">\n                              {t('aiPage.noHistory', '暂无历史记录')}\n                            </TableCell>\n                          </TableRow>\n                        )}\n                </TableBody>\n              </Table>\n            </CardContent>\n          </Card>\n\n          {/* Pagination */}\n          {totalPages > 1 && (\n            <div className=\"mt-4\">\n              <Pagination>\n                <PaginationContent>\n                  <PaginationItem>\n                    <PaginationPrevious\n                      href=\"#\"\n                      onClick={(e) => {\n                        e.preventDefault()\n                        if (page > 1) { setPage(p => p - 1) }\n                      }}\n                      className={page <= 1 ? 'pointer-events-none opacity-50' : ''}\n                    />\n                  </PaginationItem>\n\n                  {Array.from({ length: totalPages }, (_, i) => i + 1)\n                    .filter(p => p === 1 || p === totalPages || Math.abs(p - page) <= 1)\n                    .map((p, i, arr) => {\n                      // Add ellipsis if there are gaps\n                      const prev = arr[i - 1]\n                      const showEllipsis = prev && p - prev > 1\n\n                      return (\n                        <React.Fragment key={p}>\n                          {showEllipsis && (\n                            <PaginationItem>\n                              <span className=\"flex h-9 w-9 items-center justify-center\">...</span>\n                            </PaginationItem>\n                          )}\n                          <PaginationItem>\n                            <PaginationLink\n                              href=\"#\"\n                              isActive={page === p}\n                              onClick={(e) => {\n                                e.preventDefault()\n                                setPage(p)\n                              }}\n                            >\n                              {p}\n                            </PaginationLink>\n                          </PaginationItem>\n                        </React.Fragment>\n                      )\n                    })}\n\n                  <PaginationItem>\n                    <PaginationNext\n                      href=\"#\"\n                      onClick={(e) => {\n                        e.preventDefault()\n                        if (page < totalPages) { setPage(p => p + 1) }\n                      }}\n                      className={page >= totalPages ? 'pointer-events-none opacity-50' : ''}\n                    />\n                  </PaginationItem>\n                </PaginationContent>\n              </Pagination>\n            </div>\n          )}\n        </section>\n      </div>\n    </LoginCheck>\n  )\n}\n"
  },
  {
    "path": "src/pages/dev/dev-page.tsx",
    "content": "import { BrowserWindowApi, WidgetApi, WidgetPackageApi } from '@widget-js/core'\nimport { WebWidget } from '@widget-js/web-api'\nimport { Hammer } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Button } from '@/components/ui/button'\nimport {\n  Empty,\n  EmptyContent,\n  EmptyHeader,\n  EmptyMedia,\n  EmptyTitle,\n} from '@/components/ui/empty'\nimport { Spinner } from '@/components/ui/spinner'\nimport { SearchItem } from '@/pages/add/components/search-item'\n\nexport default function DevPage() {\n  const { t } = useTranslation()\n  const [widgets, setWidgets] = useState<WebWidget[]>([])\n  const [loading, setLoading] = useState(true)\n\n  const openDocs = () => {\n    BrowserWindowApi.openUrl('https://widgetjs.cn/guide/', { external: true })\n  }\n\n  useEffect(() => {\n    document.title = t('sidebar.dev')\n  }, [t])\n\n  useEffect(() => {\n    const loadWidgets = async () => {\n      try {\n        const widgetPackages = await WidgetPackageApi.getPackages()\n        const devPackages = widgetPackages.filter(it => it.development)\n        const localWidgets = await WidgetApi.getWidgets()\n        const devWidgets: WebWidget[] = []\n\n        for (const widgetPackage of devPackages) {\n          const matchingWidgets = localWidgets\n            .map(it => WebWidget.fromObject(it))\n            .filter(it => it.packageName === widgetPackage.name)\n          devWidgets.push(...matchingWidgets)\n        }\n        setWidgets(devWidgets)\n      }\n      catch (e) {\n        console.error('Failed to load widgets', e)\n      }\n      finally {\n        setLoading(false)\n      }\n    }\n    loadWidgets()\n  }, [])\n\n  if (loading) {\n    return (\n      <div className=\"flex h-full w-full items-center justify-center\">\n        <Spinner className=\"size-8 text-muted-foreground\" />\n      </div>\n    )\n  }\n\n  if (widgets.length > 0) {\n    return (\n      <div className=\"h-full w-full overflow-y-auto bg-background p-4\">\n        <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n          {widgets.map(widget => (\n            <SearchItem key={widget.name} widget={widget} />\n          ))}\n        </div>\n      </div>\n    )\n  }\n\n  return (\n    <div className=\"flex h-full w-full items-center justify-center p-4\">\n      <Empty>\n        <EmptyHeader>\n          <EmptyMedia variant=\"icon\">\n            <Hammer />\n          </EmptyMedia>\n          <EmptyTitle>{t('dev.emptyTitle')}</EmptyTitle>\n        </EmptyHeader>\n        <EmptyContent>\n          <Button onClick={openDocs}>{t('dev.viewDocs')}</Button>\n        </EmptyContent>\n      </Empty>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/error-page.tsx",
    "content": "import { useTranslation } from 'react-i18next'\nimport { Link, useLocation, useRouteError } from 'react-router-dom'\nimport { Button } from '@/components/ui/button'\n\nexport default function ErrorPage() {\n  const { t } = useTranslation()\n  const error: any = useRouteError()\n  const location = useLocation()\n  console.error(error)\n\n  return (\n    <div className=\"flex h-screen w-full flex-col items-center justify-center gap-4 text-center\">\n      <h1 className=\"text-4xl font-bold\">{t('error.oops')}</h1>\n      <p className=\"text-lg text-muted-foreground\">{t('error.unexpected')}</p>\n      <div className=\"flex flex-col gap-2\">\n        <p className=\"text-sm text-muted-foreground\">\n        </p>\n        <p className=\"text-sm text-muted-foreground\">\n          <i>{error?.statusText || error?.message || t('error.unknown')}</i>\n          {' '}\n          {location.pathname}\n        </p>\n      </div>\n      <div className=\"mt-4\">\n        <Button asChild>\n          <Link to=\"/\">{t('error.backToHome')}</Link>\n        </Button>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/packages/components/widget-package-item.tsx",
    "content": "import type { LanguageCode, WidgetPackage } from '@widget-js/core'\nimport { useTranslation } from 'react-i18next'\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from '@/components/ui/alert-dialog'\nimport { Button } from '@/components/ui/button'\nimport { Card } from '@/components/ui/card'\nimport { useAppLanguage } from '@/hooks/use-app-language'\n\ninterface WidgetPackageItemProps {\n  widgetPackage: WidgetPackage\n  onUninstall: (pkg: WidgetPackage) => void\n}\n\nexport function WidgetPackageItem({ widgetPackage, onUninstall }: WidgetPackageItemProps) {\n  const { t } = useTranslation()\n  const [languageCode] = useAppLanguage()\n\n  const getTitle = (): string => {\n    if (typeof widgetPackage.getTitle === 'function') {\n      return widgetPackage.getTitle(languageCode as LanguageCode) || widgetPackage.name\n    }\n    return widgetPackage.name\n  }\n\n  return (\n    <Card className=\"p-3 shadow-sm hover:shadow-md transition-shadow\">\n      <div className=\"flex items-center gap-4\">\n        <div className=\"flex flex-col gap-1 flex-1 min-w-0 text-left\">\n          <div className=\"flex items-center gap-2 text-sm\">\n            <span className=\"font-bold shrink-0\">{t('settings.widgetPackage.title')}</span>\n            <span className=\"truncate\">{getTitle()}</span>\n          </div>\n          <div className=\"flex items-center gap-2 text-sm\">\n            <span className=\"font-bold shrink-0\">{t('settings.widgetPackage.name')}</span>\n            <span className=\"truncate text-muted-foreground\">{widgetPackage.name}</span>\n          </div>\n          <div className=\"flex items-center gap-2 text-sm\">\n            <span className=\"font-bold shrink-0\">{t('settings.widgetPackage.installPath')}</span>\n            <span className=\"truncate text-muted-foreground max-w-[400px]\" title={widgetPackage.url}>\n              {widgetPackage.url}\n            </span>\n          </div>\n        </div>\n\n        <div className=\"ml-auto flex items-center\">\n          <AlertDialog>\n            <AlertDialogTrigger asChild>\n              <Button variant=\"destructive\" size=\"sm\">\n                {t('settings.widgetPackage.uninstall')}\n              </Button>\n            </AlertDialogTrigger>\n            <AlertDialogContent>\n              <AlertDialogHeader>\n                <AlertDialogTitle>{t('settings.widgetPackage.uninstallConfirm')}</AlertDialogTitle>\n                <AlertDialogDescription>\n                  {t('settings.widgetPackage.uninstallDesc', { name: getTitle() })}\n                </AlertDialogDescription>\n              </AlertDialogHeader>\n              <AlertDialogFooter>\n                <AlertDialogCancel>{t('settings.widgetPackage.cancel')}</AlertDialogCancel>\n                <AlertDialogAction onClick={() => onUninstall(widgetPackage)}>\n                  {t('settings.widgetPackage.confirm')}\n                </AlertDialogAction>\n              </AlertDialogFooter>\n            </AlertDialogContent>\n          </AlertDialog>\n        </div>\n      </div>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "src/pages/packages/widget-package-manager-page.tsx",
    "content": "import { WidgetPackage, WidgetPackageApi } from '@widget-js/core'\nimport consola from 'consola'\nimport { useEffect, useState } from 'react'\nimport { WidgetPackageItem } from './components/widget-package-item'\n\nexport default function WidgetPackageManagerPage() {\n  const [packages, setPackages] = useState<WidgetPackage[]>([])\n\n  const loadPackages = async () => {\n    try {\n      const list = await WidgetPackageApi.getPackages()\n      const parsedList = list.map((it) => {\n        if (WidgetPackage.parseObject) {\n          return WidgetPackage.parseObject(it)\n        }\n        return it as unknown as WidgetPackage\n      })\n      setPackages(parsedList)\n    }\n    catch (e) {\n      consola.error('Failed to load packages', e)\n    }\n  }\n\n  const handleUninstall = async (pkg: WidgetPackage) => {\n    try {\n      await WidgetPackageApi.uninstall(pkg.name)\n      setPackages(prev => prev.filter(it => it.name !== pkg.name))\n    }\n    catch (e) {\n      consola.error('Failed to uninstall package', e)\n    }\n  }\n\n  useEffect(() => {\n    loadPackages()\n  }, [])\n\n  return (\n    <div className=\"flex flex-col gap-6 mb-4 px-4 w-full\">\n      <div className=\"flex flex-col gap-2\">\n        {packages\n          .filter(item => item.name !== 'cn.widgetjs.widgets')\n          .map(item => (\n            <WidgetPackageItem\n              key={item.name}\n              widgetPackage={item}\n              onUninstall={handleUninstall}\n            />\n          ))}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/settings/app-info-page.tsx",
    "content": "import type { AppVersion } from '@/model/app-version'\nimport { ElectronUtils, SystemApi } from '@widget-js/core'\nimport consola from 'consola'\nimport { Copy } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { toast } from 'sonner'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Spinner } from '@/components/ui/spinner'\nimport { useAppRuntimeInfo } from '@/hooks/use-app-runtime-info'\nimport VersionUtils from '@/utils/version-utils'\n\nexport default function AppInfoPage() {\n  const { t } = useTranslation()\n  const { simpleInfo } = useAppRuntimeInfo()\n  const [loading, setLoading] = useState(false)\n  const [error, setError] = useState<string | null>(null)\n  const [appVersion, setAppVersion] = useState<AppVersion | null>(null)\n  const [hasNewVersion, setHasNewVersion] = useState(false)\n\n  const checkNewVersion = () => {\n    setLoading(true)\n    setError(null)\n    setAppVersion(null)\n    setHasNewVersion(false)\n\n    VersionUtils.checkNewVersion(\n      (version: AppVersion) => {\n        setAppVersion(version)\n        setHasNewVersion(true)\n      },\n      (err: any) => {\n        setError(err.message || 'Check update failed')\n        consola.error(err)\n      },\n      () => {\n        setLoading(false)\n      },\n    )\n  }\n\n  // Initial check on mount\n  useEffect(() => {\n    document.title = t('appInfo.title')\n    checkNewVersion()\n  }, [t])\n\n  const upgrade = async () => {\n    if (!appVersion) { return }\n    setLoading(true)\n    try {\n      await ElectronUtils.getAPI().invoke('upgradeApp', JSON.stringify(appVersion))\n    }\n    catch (e) {\n      consola.error(e)\n      setError('Upgrade failed')\n    }\n    finally {\n      setLoading(false)\n    }\n  }\n\n  const renderValue = (value: any) => {\n    if (typeof value === 'object' && value !== null) {\n      return JSON.stringify(value)\n    }\n    return String(value)\n  }\n\n  const copyInfo = () => {\n    if (!simpleInfo) { return }\n    const text = Object.entries(simpleInfo)\n      .map(([key, value]) => `${key}: ${renderValue(value)}`)\n      .join('\\n')\n    navigator.clipboard.writeText(text)\n    toast.success(t('tray.infoCopied'))\n  }\n\n  return (\n    <div className=\"flex flex-col gap-6 max-w-2xl mx-auto w-full p-4\">\n      <Card>\n        <CardHeader className=\"flex flex-row items-center justify-between\">\n          <CardTitle>{t('appInfo.title')}</CardTitle>\n          <Button variant=\"ghost\" size=\"icon\" onClick={copyInfo}>\n            <Copy className=\"size-4\" />\n          </Button>\n        </CardHeader>\n        <CardContent>\n          <div className=\"flex flex-col gap-4\">\n            {simpleInfo\n              ? (\n                  Object.keys(simpleInfo).map(key => (\n                    <div key={key} className=\"grid grid-cols-[120px_1fr] items-start gap-4 text-sm\">\n                      <span className=\"font-medium text-muted-foreground text-right\">\n                        {key}\n                        :\n                      </span>\n                      <span className=\"break-all font-mono\">\n                        {renderValue((simpleInfo as any)[key])}\n                      </span>\n                    </div>\n                  ))\n                )\n              : (\n                  <div className=\"flex justify-center p-4\">\n                    <Spinner className=\"size-6\" />\n                  </div>\n                )}\n          </div>\n        </CardContent>\n      </Card>\n\n      <Card>\n        <CardHeader>\n          <CardTitle>{t('update.windowTitle')}</CardTitle>\n        </CardHeader>\n        <CardContent>\n          <div className=\"min-h-[64px] flex items-center justify-center\">\n            {loading\n              ? (\n                  <Spinner className=\"size-6\" />\n                )\n              : error\n                ? (\n                    <div className=\"text-destructive text-center w-full\">{error}</div>\n                  )\n                : hasNewVersion && appVersion\n                  ? (\n                      <div className=\"space-y-4 w-full text-left\">\n                        <h4 className=\"font-medium text-lg\">\n                          {t('update.newVersionDetect')}\n                          {' '}\n                          {appVersion.version}\n                        </h4>\n                        <pre className=\"text-sm bg-muted p-4 rounded-md whitespace-pre-wrap font-sans\">\n                          {appVersion.releaseNote}\n                        </pre>\n                      </div>\n                    )\n                  : (\n                      <div className=\"text-center text-muted-foreground w-full\">\n                        {t('update.alreadyLatestVersion')}\n                      </div>\n                    )}\n          </div>\n        </CardContent>\n        <CardFooter className=\"flex justify-end gap-2 border-t pt-6\">\n          <Button variant=\"outline\" onClick={() => SystemApi.launchStoreDetailsPage()}>\n            {t('update.storePage')}\n          </Button>\n          {!loading && (\n            hasNewVersion\n              ? (\n                  <Button onClick={upgrade}>\n                    {t('update.update')}\n                  </Button>\n                )\n              : (\n                  <Button onClick={checkNewVersion}>\n                    {t('update.check')}\n                  </Button>\n                )\n          )}\n        </CardFooter>\n      </Card>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/settings/components/app-theme-form.tsx",
    "content": "import type { AppTheme, ThemeColors } from '@widget-js/core'\nimport { Sketch } from '@uiw/react-color'\nimport { useTranslation } from 'react-i18next'\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\nimport { Input } from '@/components/ui/input'\nimport { Label } from '@/components/ui/label'\nimport { Slider } from '@/components/ui/slider'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'\nimport { FontFamilyPicker } from './font-family-picker'\n\ninterface AppThemeFormProps {\n  value: AppTheme\n  onChange: (value: AppTheme) => void\n}\n\nexport function AppThemeForm({ value, onChange }: AppThemeFormProps) {\n  const { t } = useTranslation()\n\n  const handleNestedChange = (category: keyof AppTheme, key: string, val: any) => {\n    const newTheme = value.copy({\n      [category]: {\n        [key]: val,\n      },\n    })\n    onChange(newTheme)\n  }\n\n  const renderColorInput = (label: string, key: keyof ThemeColors) => (\n    <div className=\"grid grid-cols-[1fr_auto] items-center gap-4 py-1\" key={key}>\n      <Label htmlFor={key} className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n        {label}\n      </Label>\n      <div className=\"flex items-center gap-2\">\n        <span className=\"text-xs text-muted-foreground w-32 text-right font-mono truncate\">\n          {value.colors?.[key] as string}\n        </span>\n        <DropdownMenu>\n          <DropdownMenuTrigger asChild>\n            <div\n              className=\"h-8 w-8 rounded-md border border-input overflow-hidden relative cursor-pointer shadow-sm hover:shadow-md transition-shadow\"\n              style={{ backgroundColor: value.colors?.[key] as string || '#000000' }}\n            >\n            </div>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent className=\"w-auto p-0 border-none bg-transparent shadow-none\">\n            <Sketch\n              style={{ boxShadow: 'none' }}\n              color={value.colors?.[key] as string || '#000000'}\n              onChange={(color) => {\n                // color.hexa always exists and contains the alpha channel if supported by the picker\n                // e.g. \"#00000080\" for 50% opacity\n                handleNestedChange('colors', key, color.hexa || color.hex)\n              }}\n            />\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </div>\n    </div>\n  )\n\n  const renderBoxShadowInput = (key: 'sm' | 'md' | 'lg', label: string) => {\n    const shadow = value.shadow?.[key] || { offsetX: '0px', offsetY: '0px', blur: '0px' }\n\n    const handleChange = (field: keyof typeof shadow, val: string) => {\n      const newTheme = value.copy({\n        shadow: {\n          ...value.shadow,\n          [key]: {\n            ...shadow,\n            [field]: val,\n          },\n        },\n      })\n      onChange(newTheme)\n    }\n\n    return (\n      <div className=\"grid gap-2 border p-4 rounded-lg\" key={key}>\n        <Label className=\"font-semibold\">{label}</Label>\n        <div className=\"grid grid-cols-3 gap-4 mt-2\">\n          <div className=\"grid gap-2\">\n            <Label htmlFor={`shadow-${key}-offsetX`} className=\"text-xs text-muted-foreground\">OffsetX</Label>\n            <Input\n              id={`shadow-${key}-offsetX`}\n              value={shadow.offsetX}\n              onChange={e => handleChange('offsetX', e.target.value)}\n            />\n          </div>\n          <div className=\"grid gap-2\">\n            <Label htmlFor={`shadow-${key}-offsetY`} className=\"text-xs text-muted-foreground\">OffsetY</Label>\n            <Input\n              id={`shadow-${key}-offsetY`}\n              value={shadow.offsetY}\n              onChange={e => handleChange('offsetY', e.target.value)}\n            />\n          </div>\n          <div className=\"grid gap-2\">\n            <Label htmlFor={`shadow-${key}-blur`} className=\"text-xs text-muted-foreground\">Blur</Label>\n            <Input\n              id={`shadow-${key}-blur`}\n              value={shadow.blur}\n              onChange={e => handleChange('blur', e.target.value)}\n            />\n          </div>\n        </div>\n      </div>\n    )\n  }\n\n  const renderSlider = (category: keyof AppTheme, key: string, label: string, min: number, max: number) => {\n    const valStr = (value as any)[category]?.[key] as string || '0'\n    const numVal = Number.parseFloat(valStr) || 0\n    const unit = valStr.replace(/[0-9.]/g, '') || 'px'\n\n    return (\n      <div className=\"space-y-4\" key={key}>\n        <div className=\"flex items-center justify-between\">\n          <Label htmlFor={`${category}-${key}`}>{label}</Label>\n          <span className=\"text-sm text-muted-foreground w-16 text-right\">\n            {numVal}\n            {unit}\n          </span>\n        </div>\n        <Slider\n          id={`${category}-${key}`}\n          min={min}\n          max={max}\n          step={1}\n          value={[numVal]}\n          onValueChange={([val]) => handleNestedChange(category, key, `${val}${unit}`)}\n        />\n      </div>\n    )\n  }\n\n  return (\n    <Tabs defaultValue=\"colors\" className=\"w-full h-full\">\n      <TabsList className=\"grid w-full grid-cols-4\">\n        <TabsTrigger value=\"colors\">{t('theme.colors.title')}</TabsTrigger>\n        <TabsTrigger value=\"typography\">{t('theme.typography.title')}</TabsTrigger>\n        <TabsTrigger value=\"radius\">{t('theme.radius.title')}</TabsTrigger>\n        <TabsTrigger value=\"shadow\">{t('theme.shadow.title')}</TabsTrigger>\n      </TabsList>\n\n      <TabsContent value=\"colors\" className=\"mt-4 h-full\">\n        <div className=\"grid gap-2\">\n          <h4 className=\"text-sm font-semibold mt-2 mb-1\">{t('theme.colors.base')}</h4>\n          {renderColorInput(t('theme.colors.background'), 'background')}\n          {renderColorInput(t('theme.colors.foreground'), 'foreground')}\n          {renderColorInput(t('theme.colors.border'), 'border')}\n          {renderColorInput(t('theme.colors.shadow'), 'shadow')}\n          {renderColorInput(t('theme.colors.innerShadow'), 'innerShadow')}\n\n          <h4 className=\"text-sm font-semibold mt-4 mb-1\">{t('theme.colors.primary')}</h4>\n          {renderColorInput(t('theme.colors.primary'), 'primary')}\n          {renderColorInput(t('theme.colors.primaryForeground'), 'primaryForeground')}\n\n          <h4 className=\"text-sm font-semibold mt-4 mb-1\">{t('theme.colors.secondary')}</h4>\n          {renderColorInput(t('theme.colors.secondary'), 'secondary')}\n          {renderColorInput(t('theme.colors.secondaryForeground'), 'secondaryForeground')}\n\n          <h4 className=\"text-sm font-semibold mt-4 mb-1\">{t('theme.colors.muted')}</h4>\n          {renderColorInput(t('theme.colors.muted'), 'muted')}\n          {renderColorInput(t('theme.colors.mutedForeground'), 'mutedForeground')}\n\n          <h4 className=\"text-sm font-semibold mt-4 mb-1\">{t('theme.colors.accent')}</h4>\n          {renderColorInput(t('theme.colors.accent'), 'accent')}\n          {renderColorInput(t('theme.colors.accentForeground'), 'accentForeground')}\n\n          <h4 className=\"text-sm font-semibold mt-4 mb-1\">{t('theme.colors.destructive')}</h4>\n          {renderColorInput(t('theme.colors.destructive'), 'destructive')}\n          {renderColorInput(t('theme.colors.destructiveForeground'), 'destructiveForeground')}\n\n          <h4 className=\"text-sm font-semibold mt-4 mb-1\">{t('theme.colors.cardPopover')}</h4>\n          {renderColorInput(t('theme.colors.card'), 'card')}\n          {renderColorInput(t('theme.colors.cardForeground'), 'cardForeground')}\n          {renderColorInput(t('theme.colors.popover'), 'popover')}\n          {renderColorInput(t('theme.colors.popoverForeground'), 'popoverForeground')}\n\n          <h4 className=\"text-sm font-semibold mt-4 mb-1\">{t('theme.colors.bordersInputs')}</h4>\n          {renderColorInput(t('theme.colors.input'), 'input')}\n          {renderColorInput(t('theme.colors.ring'), 'ring')}\n        </div>\n      </TabsContent>\n\n      <TabsContent value=\"typography\" className=\"mt-4\">\n        <div className=\"grid gap-6\">\n          <FontFamilyPicker\n            label={t('theme.fontFamily')}\n            value={(value as any).typography?.fontFamily || ''}\n            onChange={val => handleNestedChange('typography', 'fontFamily', val)}\n          />\n          {renderSlider('typography', 'fontSize', t('theme.fontSize'), 8, 50)}\n        </div>\n      </TabsContent>\n\n      <TabsContent value=\"radius\" className=\"mt-4\">\n        <div className=\"grid gap-6\">\n          {renderSlider('radius', 'sm', t('theme.radius.sm'), 0, 50)}\n          {renderSlider('radius', 'md', t('theme.radius.md'), 0, 50)}\n          {renderSlider('radius', 'lg', t('theme.radius.lg'), 0, 50)}\n          {renderSlider('radius', 'full', t('theme.radius.full'), 0, 9999)}\n        </div>\n      </TabsContent>\n\n      <TabsContent value=\"shadow\" className=\"mt-4\">\n        <div className=\"grid gap-4\">\n          {renderBoxShadowInput('sm', t('theme.shadow.sm'))}\n          {renderBoxShadowInput('md', t('theme.shadow.md'))}\n          {renderBoxShadowInput('lg', t('theme.shadow.lg'))}\n        </div>\n      </TabsContent>\n    </Tabs>\n  )\n}\n"
  },
  {
    "path": "src/pages/settings/components/font-family-picker.tsx",
    "content": "import { Check, ChevronsUpDown, Loader2, Search } from 'lucide-react'\nimport { useEffect, useMemo, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Button } from '@/components/ui/button'\nimport { Label } from '@/components/ui/label'\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { cn } from '@/lib/utils'\n\nfunction getDefaultFonts(t: any) {\n  return [\n    { label: t('theme.systemDefault'), value: 'system-ui, sans-serif' },\n  ]\n}\n\ninterface FontFamilyPickerProps {\n  value: string\n  onChange: (val: string) => void\n  label: string\n}\n\nexport function FontFamilyPicker({ value, onChange, label }: FontFamilyPickerProps) {\n  const { t } = useTranslation()\n  const [open, setOpen] = useState(false)\n  const [search, setSearch] = useState('')\n  const defaultFonts = useMemo(() => getDefaultFonts(t), [t])\n  const [fonts, setFonts] = useState(defaultFonts)\n  const [isLoading, setIsLoading] = useState(false)\n\n  useEffect(() => {\n    const loadSystemFonts = async () => {\n      try {\n        if ('queryLocalFonts' in window) {\n          setIsLoading(true)\n          // Request permission and query local fonts\n          const localFonts = await (window as any).queryLocalFonts()\n          const systemFonts = localFonts.map((font: any) => ({\n            label: font.fullName,\n            value: `\"${font.family}\"`,\n          }))\n\n          // Filter out duplicates (based on family name) and merge with defaults\n          const uniqueSystemFonts = Array.from(new Map(systemFonts.map((item: any) => [item.value, item])).values()) as typeof defaultFonts\n\n          // Merge defaults with system fonts, prioritizing defaults at the top\n          const mergedFonts = [...defaultFonts]\n          for (const sysFont of uniqueSystemFonts) {\n            if (!mergedFonts.some(f => f.value.includes(sysFont.value.replace(/\"/g, '')))) {\n              mergedFonts.push(sysFont)\n            }\n          }\n\n          setFonts(mergedFonts)\n        }\n      }\n      catch (error) {\n        console.error('Failed to load system fonts:', error)\n      }\n      finally {\n        setIsLoading(false)\n      }\n    }\n\n    if (open && fonts.length === defaultFonts.length) {\n      loadSystemFonts()\n    }\n  }, [open, fonts.length, defaultFonts])\n\n  const filteredFonts = fonts.filter(f =>\n    f.label.toLowerCase().includes(search.toLowerCase())\n    || f.value.toLowerCase().includes(search.toLowerCase()),\n  )\n  const currentFont = fonts.find(f => f.value === value) || { label: value || 'Default', value }\n\n  return (\n    <div className=\"grid gap-2\">\n      <Label>{label}</Label>\n      <Popover open={open} onOpenChange={setOpen}>\n        <PopoverTrigger asChild>\n          <Button variant=\"outline\" role=\"combobox\" aria-expanded={open} className=\"w-full justify-between font-normal\">\n            <span className=\"truncate\">{currentFont.label}</span>\n            <ChevronsUpDown className=\"ml-2 h-4 w-4 shrink-0 opacity-50\" />\n          </Button>\n        </PopoverTrigger>\n        <PopoverContent className=\"w-[300px] p-0\" align=\"start\">\n          <div className=\"flex items-center border-b px-3\">\n            <Search className=\"mr-2 h-4 w-4 shrink-0 opacity-50\" />\n            <input\n              className=\"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 border-0 focus:ring-0\"\n              placeholder={t('theme.searchFont')}\n              value={search}\n              onChange={e => setSearch(e.target.value)}\n            />\n          </div>\n          <ScrollArea className=\"h-64 p-1\">\n            {isLoading\n              ? (\n                  <div className=\"flex items-center justify-center py-6 text-sm text-muted-foreground\">\n                    <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n                    加载中...\n                  </div>\n                )\n              : filteredFonts.length === 0\n                ? (\n                    <div className=\"py-6 text-center text-sm\">{t('theme.fontNotFound')}</div>\n                  )\n                : (\n                    filteredFonts.map(font => (\n                      <div\n                        key={font.value + font.label}\n                        className={cn(\n                          'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n                          value === font.value ? 'bg-accent text-accent-foreground' : '',\n                        )}\n                        onClick={() => {\n                          onChange(font.value)\n                          setOpen(false)\n                        }}\n                      >\n                        <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n                          {value === font.value && <Check className=\"h-4 w-4\" />}\n                        </span>\n                        <span className=\"truncate\" style={{ fontFamily: font.value }}>{font.label}</span>\n                      </div>\n                    ))\n                  )}\n          </ScrollArea>\n        </PopoverContent>\n      </Popover>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/settings/components/theme-preview.tsx",
    "content": "import type { AppTheme } from '@widget-js/core'\nimport { useTranslation } from 'react-i18next'\nimport wallpaper from '@/assets/images/wallpaper.jpg'\nimport { Button } from '@/components/ui/button'\nimport { Input } from '@/components/ui/input'\nimport { Label } from '@/components/ui/label'\nimport { Slider } from '@/components/ui/slider'\nimport { Switch } from '@/components/ui/switch'\nimport { cn } from '@/lib/utils'\n\ninterface ThemePreviewProps {\n  theme: AppTheme\n}\n\nexport function ThemePreview({ theme }: ThemePreviewProps) {\n  const { t } = useTranslation()\n  // Convert theme object to inline styles for the preview container\n  const cssVars = theme.toCSSVariables('--widget')\n\n  return (\n    <div\n      className={cn('w-full rounded-lg bg-background text-foreground overflow-hidden p-4 bg-cover bg-center', theme.mode === 'dark' ? 'dark' : '')}\n      style={{\n        ...cssVars as React.CSSProperties,\n        backgroundImage: `url(${wallpaper})`,\n      }}\n    >\n      {/* We apply the custom CSS variables to this specific container */}\n      <div\n        className=\"grid gap-6\"\n        style={{\n          // Mapping standard UI variables to the preview variables so the internal shadcn ui components pick them up\n          '--background': 'var(--widget-colors-background)',\n          '--foreground': 'var(--widget-colors-foreground)',\n          '--card': 'var(--widget-colors-card)',\n          '--card-foreground': 'var(--widget-colors-card-foreground)',\n          '--popover': 'var(--widget-colors-popover)',\n          '--popover-foreground': 'var(--widget-colors-popover-foreground)',\n          '--primary': 'var(--widget-colors-primary)',\n          '--primary-foreground': 'var(--widget-colors-primary-foreground)',\n          '--secondary': 'var(--widget-colors-secondary)',\n          '--secondary-foreground': 'var(--widget-colors-secondary-foreground)',\n          '--muted': 'var(--widget-colors-muted)',\n          '--muted-foreground': 'var(--widget-colors-muted-foreground)',\n          '--accent': 'var(--widget-colors-accent)',\n          '--accent-foreground': 'var(--widget-colors-accent-foreground)',\n          '--destructive': 'var(--widget-colors-destructive)',\n          '--destructive-foreground': 'var(--widget-colors-destructive-foreground)',\n          '--border': 'var(--widget-colors-border)',\n          '--input': 'var(--widget-colors-input)',\n          '--ring': 'var(--widget-colors-ring)',\n          '--radius': 'var(--widget-radius-md)',\n          'fontFamily': 'var(--widget-typography-font-family)',\n          'fontSize': 'var(--widget-typography-font-size)',\n        } as React.CSSProperties}\n      >\n        <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n          {/* Example Form Preview */}\n          <div\n            className=\"space-y-6 border\"\n            style={{\n              backgroundColor: 'var(--widget-colors-background)',\n              color: 'var(--widget-colors-foreground)',\n              borderColor: 'var(--widget-colors-border)',\n              borderRadius: 'var(--widget-radius-lg)',\n              boxShadow: 'var(--widget-shadow-md)',\n              padding: 'var(--widget-spacing, 1.5rem)',\n            }}\n          >\n            <div className=\"space-y-2\">\n              <h4 className=\"font-semibold tracking-tight\">{t('theme.preview.cardTitle')}</h4>\n              <p className=\"text-sm text-muted-foreground\">{t('theme.preview.cardDesc')}</p>\n            </div>\n            <div className=\"space-y-4\">\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"preview-input\">{t('theme.preview.email')}</Label>\n                <Input id=\"preview-input\" placeholder={t('theme.preview.emailPlaceholder')} className=\"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\" />\n              </div>\n              <div className=\"flex items-center space-x-2\">\n                <Switch id=\"preview-switch\" />\n                <Label htmlFor=\"preview-switch\">{t('theme.preview.enableNotifications')}</Label>\n              </div>\n            </div>\n            <div className=\"flex justify-between\">\n              <Button variant=\"outline\">{t('theme.preview.cancel')}</Button>\n              <Button>{t('theme.preview.save')}</Button>\n            </div>\n          </div>\n\n          {/* Interactive Elements Preview */}\n          <div className=\"space-y-6\">\n            <div className=\"space-y-4\">\n              <h4 className=\"text-sm font-medium\">{t('theme.preview.buttons')}</h4>\n              <div className=\"flex flex-wrap gap-2\">\n                <Button>{t('theme.preview.primary')}</Button>\n                <Button variant=\"secondary\">{t('theme.preview.secondary')}</Button>\n                <Button variant=\"destructive\">{t('theme.preview.destructive')}</Button>\n                <Button variant=\"outline\">{t('theme.preview.outline')}</Button>\n                <Button variant=\"ghost\">{t('theme.preview.ghost')}</Button>\n              </div>\n            </div>\n\n            <div className=\"space-y-4\">\n              <h4 className=\"text-sm font-medium\">{t('theme.preview.inputRing')}</h4>\n              <div className=\"space-y-2\">\n                <Input\n                  placeholder={t('theme.preview.inputPlaceholder')}\n                  className=\"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n                />\n              </div>\n            </div>\n\n            <div className=\"space-y-4\">\n              <h4 className=\"text-sm font-medium\">{t('theme.preview.slider')}</h4>\n              <Slider defaultValue={[50]} max={100} step={1} />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/settings/components/theme-tags.tsx",
    "content": "import type { FormEvent } from 'react'\nimport { AppTheme } from '@widget-js/core'\nimport { Check, Plus } from 'lucide-react'\nimport { useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Button } from '@/components/ui/button'\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from '@/components/ui/dialog'\nimport { Input } from '@/components/ui/input'\nimport { cn } from '@/lib/utils'\n\nexport interface ThemeTag {\n  name: string\n  value: string\n  theme: AppTheme\n}\n\nconst dark = new AppTheme({\n  useGlobalTheme: true,\n  mode: 'dark',\n  colors: {\n    background: '#000026',\n    foreground: '#f1f1f1',\n    card: '#111113',\n    cardForeground: '#FAFAFA',\n    popover: '#111113',\n    popoverForeground: '#FAFAFA',\n    primary: '#7C8CF8',\n    primaryForeground: '#0E0E10',\n    secondary: '#1A1A1D',\n    secondaryForeground: '#E4E4E7',\n    muted: '#1A1A1D',\n    mutedForeground: '#A1A1AA',\n    accent: '#1E1F25',\n    accentForeground: '#C7D2FE',\n    destructive: '#FF6369',\n    destructiveForeground: '#0E0E10',\n    border: '#6f6f94',\n    input: '#27272A',\n    ring: '#7C8CF8',\n    shadow: 'rgba(0,0,0,0.5)',\n    innerShadow: 'rgb(0 0 0 / 0.11)',\n  },\n  radius: {\n    sm: '6px',\n    md: '10px',\n    lg: '14px',\n    full: '9999px',\n  },\n  typography: {\n    fontFamily: `Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif`,\n    fontSize: '14px',\n  },\n  shadow: {\n    sm: { offsetX: '0px', offsetY: '1px', blur: '2px' },\n    md: { offsetX: '0px', offsetY: '6px', blur: '20px' },\n    lg: { offsetX: '0px', offsetY: '20px', blur: '40px' },\n  },\n  spacing: '0.25rem',\n})\nconst light = new AppTheme({\n  useGlobalTheme: true,\n  mode: 'light',\n  colors: {\n    background: '#ffffff',\n    foreground: '#272e39',\n    card: '#FFFFFF',\n    cardForeground: '#0A0A0A',\n    popover: '#FFFFFF',\n    popoverForeground: '#0A0A0A',\n    primary: '#5E6AD2',\n    primaryForeground: '#FFFFFF',\n    secondary: '#F4F4F5',\n    secondaryForeground: '#18181B',\n    muted: '#F9F9FB',\n    mutedForeground: '#71717A',\n    accent: '#EEF2FF',\n    accentForeground: '#4338CA',\n    destructive: '#E5484D',\n    destructiveForeground: '#FFFFFF',\n    border: '#E4E4E7',\n    input: '#E4E4E7',\n    ring: '#5E6AD2',\n    shadow: 'rgba(0,0,0,0.04)',\n    innerShadow: 'rgba(0,0,0,0.02)',\n  },\n  radius: {\n    sm: '6px',\n    md: '10px',\n    lg: '14px',\n    full: '9999px',\n  },\n  typography: {\n    fontFamily: `Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif`,\n    fontSize: '14px',\n  },\n  shadow: {\n    sm: { offsetX: '0px', offsetY: '1px', blur: '2px' },\n    md: { offsetX: '0px', offsetY: '4px', blur: '12px' },\n    lg: { offsetX: '0px', offsetY: '10px', blur: '30px' },\n  },\n  spacing: '0.25rem',\n})\nconst semiTransparent = new AppTheme({\n  mode: 'dark',\n  colors: {\n    background: 'rgba(0, 0, 0, 0.5)',\n    card: 'rgba(0, 0, 0, 0.5)',\n    popover: 'rgba(0, 0, 0, 0.5)',\n    border: 'rgb(145 145 145 / 0.5)',\n    innerShadow: 'rgb(208 208 208 / 0.25)',\n  },\n})\n\ninterface ThemeTagsProps {\n  value: string\n  presets: ThemeTag[]\n  onChange: (value: ThemeTag) => void\n  onCreatePreset: (name: string) => boolean\n}\n\nexport function ThemeTags({ value, presets, onChange, onCreatePreset }: ThemeTagsProps) {\n  const { t } = useTranslation()\n  const [open, setOpen] = useState(false)\n  const [presetName, setPresetName] = useState('')\n\n  const themes: ThemeTag[] = [\n    ...presets,\n    { name: t('theme.dark', '深色'), value: 'dark', theme: dark },\n    { name: t('theme.light', '浅色'), value: 'light', theme: light },\n    { name: t('theme.translucent', '半透明'), value: 'semi-transparent', theme: semiTransparent },\n  ]\n\n  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {\n    event.preventDefault()\n\n    if (onCreatePreset(presetName)) {\n      setOpen(false)\n      setPresetName('')\n    }\n  }\n\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      <Dialog\n        open={open}\n        onOpenChange={(nextOpen) => {\n          setOpen(nextOpen)\n          if (!nextOpen) {\n            setPresetName('')\n          }\n        }}\n      >\n        <Button\n          variant=\"secondary\"\n          size=\"icon-sm\"\n          type=\"button\"\n          className=\"rounded-full\"\n          onClick={() => setOpen(true)}\n          aria-label={t('theme.createPreset.button')}\n        >\n          <Plus />\n        </Button>\n        <DialogContent className=\"sm:max-w-md\">\n          <form className=\"space-y-4\" onSubmit={handleSubmit}>\n            <DialogHeader>\n              <DialogTitle>{t('theme.createPreset.title')}</DialogTitle>\n              <DialogDescription>{t('theme.createPreset.description')}</DialogDescription>\n            </DialogHeader>\n            <Input\n              value={presetName}\n              onChange={event => setPresetName(event.target.value)}\n              placeholder={t('theme.createPreset.namePlaceholder')}\n              autoFocus\n            />\n            <DialogFooter>\n              <DialogClose asChild>\n                <Button type=\"button\" variant=\"outline\">\n                  {t('theme.createPreset.cancel')}\n                </Button>\n              </DialogClose>\n              <Button type=\"submit\" disabled={!presetName.trim()}>\n                {t('theme.createPreset.confirm')}\n              </Button>\n            </DialogFooter>\n          </form>\n        </DialogContent>\n      </Dialog>\n      {themes.map(item => (\n        <button\n          key={item.value}\n          onClick={() => onChange(item)}\n          className={cn(\n            'inline-flex items-center justify-center rounded-full px-4 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background',\n            value === item.value\n              ? 'bg-primary text-primary-foreground hover:bg-primary/90'\n              : 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n          )}\n        >\n          {item.name}\n          {value === item.value && <Check className=\"ml-2 h-3.5 w-3.5\" />}\n        </button>\n      ))}\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/settings/general-page.tsx",
    "content": "import { useEffect } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { SettingSection } from '@/components/setting-section'\nimport { SocialLinks } from '@/components/tray/social-links'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Field, FieldLabel } from '@/components/ui/field'\nimport { Label } from '@/components/ui/label'\nimport { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@/components/ui/select'\nimport { Switch } from '@/components/ui/switch'\nimport { useAppLanguage } from '@/hooks/use-app-language'\nimport { useCellSizeConfig } from '@/hooks/use-cell-size-config'\nimport { useDebugConfig } from '@/hooks/use-debug-config'\nimport { useLaunchAtStartupConfig } from '@/hooks/use-launch-at-startup-config'\n\ninterface Language {\n  baseName: string\n  language: string\n  nativeName: string\n  region: string\n}\n\nconst languages: Language[] = [\n  { baseName: 'zh-CN', language: 'zh', nativeName: '简体中文', region: 'CN' },\n  { baseName: 'en-US', language: 'en', nativeName: 'English', region: 'US' },\n]\n\nexport default function GeneralPage() {\n  const { t, i18n } = useTranslation()\n  const [launchAtStartup, setLaunchAtStartup] = useLaunchAtStartupConfig()\n  const [languageCode, setLanguageCode] = useAppLanguage({\n    onLoad: (lang) => {\n      if (lang !== i18n.language) {\n        i18n.changeLanguage(lang)\n      }\n    },\n  })\n  const [debugMode, setDebugMode] = useDebugConfig()\n  const [gridSize, setGridSize] = useCellSizeConfig()\n\n  useEffect(() => {\n    document.title = t('general.title')\n  }, [t])\n\n  useEffect(() => {\n    if (languageCode !== i18n.language) {\n      i18n.changeLanguage(languageCode)\n    }\n  }, [languageCode, i18n])\n\n  return (\n    <div className=\"flex flex-col gap-6 max-w-2xl mx-auto w-full\">\n      <Card>\n        <CardHeader>\n          <CardTitle>{t('general.title')}</CardTitle>\n        </CardHeader>\n        <CardContent className=\"space-y-8\">\n          <Field orientation=\"horizontal\" className=\"justify-between\">\n            <FieldLabel>{t('general.language')}</FieldLabel>\n            <Select value={languageCode} onValueChange={setLanguageCode}>\n              <SelectTrigger className=\"w-[180px]\">\n                <SelectValue placeholder={t('general.selectLanguage')} />\n              </SelectTrigger>\n              <SelectContent>\n                {languages.map(lang => (\n                  <SelectItem key={lang.baseName} value={lang.language}>\n                    {lang.nativeName}\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </Field>\n\n          {gridSize != null && (\n            <Field orientation=\"horizontal\" className=\"justify-between\">\n              <FieldLabel>{t('general.gridSize')}</FieldLabel>\n              <RadioGroup\n                value={(gridSize || 60).toString()}\n                onValueChange={val => setGridSize(Number.parseInt(val, 10))}\n                className=\"flex gap-4\"\n              >\n                {[60, 70, 80, 90].map(size => (\n                  <div key={size} className=\"flex items-center space-x-2\">\n                    <RadioGroupItem value={size.toString()} id={`size-${size}`} />\n                    <Label htmlFor={`size-${size}`}>\n                      {size}\n                      px\n                    </Label>\n                  </div>\n                ))}\n              </RadioGroup>\n            </Field>\n          )}\n\n          <Field orientation=\"horizontal\" className=\"justify-between\">\n            <FieldLabel>{t('general.launchAtStartup')}</FieldLabel>\n            <Switch\n              id=\"launch-startup\"\n              checked={launchAtStartup}\n              onCheckedChange={setLaunchAtStartup}\n            />\n          </Field>\n\n          <Field orientation=\"horizontal\" className=\"justify-between\">\n            <FieldLabel>{t('general.developerMode')}</FieldLabel>\n            <Switch\n              id=\"debug-mode\"\n              checked={debugMode}\n              onCheckedChange={setDebugMode}\n            />\n          </Field>\n\n          <SettingSection title=\"交个朋友\">\n            <SocialLinks />\n          </SettingSection>\n        </CardContent>\n      </Card>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/settings/proxy-page.tsx",
    "content": "import type { ProxyConfig } from '@widget-js/core'\nimport { AppApi } from '@widget-js/core'\nimport consola from 'consola'\nimport { AlertTriangle } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { useDebounce } from '@/hooks/use-debounce'\n\nexport default function ProxyPage() {\n  const { t } = useTranslation()\n  const [protocol, setProtocol] = useState('http')\n  const [hostname, setHostname] = useState('')\n  const [port, setPort] = useState('')\n\n  const debouncedProtocol = useDebounce(protocol, 1000)\n  const debouncedHostname = useDebounce(hostname, 1000)\n  const debouncedPort = useDebounce(port, 1000)\n\n  // Load initial proxy settings\n  useEffect(() => {\n    AppApi.getProxy().then((value) => {\n      consola.info('proxy', value)\n      if (value && value.proxyRules) {\n        try {\n          const url = new URL(value.proxyRules)\n          setProtocol(url.protocol.replace(':', ''))\n          setHostname(url.hostname)\n          setPort(url.port)\n        }\n        catch (e) {\n          consola.error(e)\n        }\n      }\n    })\n  }, [])\n\n  // Update proxy settings when debounced values change\n  useEffect(() => {\n    const updateProxy = async () => {\n      if (debouncedProtocol && debouncedHostname && debouncedPort) {\n        const proxyRules = `${debouncedProtocol}://${debouncedHostname}:${debouncedPort}`\n        const proxyConfig: ProxyConfig = {\n          proxyRules,\n        }\n        await AppApi.setProxy(proxyConfig)\n      }\n    }\n    updateProxy()\n  }, [debouncedProtocol, debouncedHostname, debouncedPort])\n\n  const clearProxy = async () => {\n    setProtocol('')\n    setHostname('')\n    setPort('')\n    await AppApi.setProxy({})\n  }\n\n  return (\n    <div className=\"flex flex-col gap-4 max-w-2xl mx-auto w-full\">\n      <div className=\"bg-yellow-50 text-yellow-900 border border-yellow-200 px-4 py-3 rounded-md flex items-center gap-2 text-sm\">\n        <AlertTriangle className=\"h-4 w-4\" />\n        <span>{t('proxy.warning')}</span>\n      </div>\n\n      <Card>\n        <CardHeader>\n          <CardTitle>{t('proxy.title')}</CardTitle>\n        </CardHeader>\n        <CardContent className=\"space-y-6\">\n          <div className=\"space-y-2\">\n            <label className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n              {t('proxy.protocol')}\n            </label>\n            <div className=\"flex flex-wrap gap-2\">\n              {['http', 'https', 'sock4', 'sock5'].map(p => (\n                <Button\n                  key={p}\n                  variant={protocol === p ? 'default' : 'outline'}\n                  size=\"sm\"\n                  onClick={() => setProtocol(p)}\n                  className=\"w-20\"\n                >\n                  {p.toUpperCase()}\n                </Button>\n              ))}\n            </div>\n          </div>\n\n          <div className=\"space-y-2\">\n            <label className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n              {t('proxy.server')}\n            </label>\n            <Input\n              value={hostname}\n              onChange={e => setHostname(e.target.value)}\n              placeholder=\"127.0.0.1\"\n            />\n          </div>\n\n          <div className=\"space-y-2\">\n            <label className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n              {t('proxy.port')}\n            </label>\n            <div className=\"flex gap-2\">\n              <Input\n                value={port}\n                onChange={e => setPort(e.target.value)}\n                maxLength={5}\n                placeholder=\"7890\"\n                className=\"flex-1\"\n              />\n              <Button variant=\"destructive\" onClick={clearProxy}>\n                {t('proxy.clearProxy')}\n              </Button>\n            </div>\n          </div>\n        </CardContent>\n      </Card>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/settings/theme-page.tsx",
    "content": "import type { IAppTheme } from '@widget-js/core'\nimport type { ThemeTag } from '@/pages/settings/components/theme-tags'\nimport { AppApi, AppTheme } from '@widget-js/core'\nimport consola from 'consola'\nimport { useEffect, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { toast } from 'sonner'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport { useDebounce } from '@/hooks/use-debounce'\nimport { AppThemeForm } from '@/pages/settings/components/app-theme-form'\nimport { ThemePreview } from '@/pages/settings/components/theme-preview'\nimport { ThemeTags } from '@/pages/settings/components/theme-tags'\n\ninterface StoredThemeTag {\n  name: string\n  value: string\n  theme: IAppTheme\n}\n\nconst SELECT_THEME_STORAGE_KEY = 'selectTheme'\nconst THEME_PRESETS_STORAGE_KEY = 'widget.theme.presets'\n\nfunction loadStoredThemeTags() {\n  try {\n    const storedValue = localStorage.getItem(THEME_PRESETS_STORAGE_KEY)\n    if (!storedValue) {\n      return []\n    }\n\n    const parsed = JSON.parse(storedValue) as StoredThemeTag[]\n\n    if (!Array.isArray(parsed)) {\n      return []\n    }\n\n    return parsed.map(item => ({\n      name: item.name,\n      value: item.value,\n      theme: new AppTheme(item.theme),\n    }))\n  }\n  catch (error) {\n    consola.error('Failed to load theme presets:', error)\n    return []\n  }\n}\n\nfunction saveStoredThemeTags(tags: ThemeTag[]) {\n  const payload: StoredThemeTag[] = tags.map(tag => ({\n    name: tag.name,\n    value: tag.value,\n    theme: JSON.parse(JSON.stringify(tag.theme)) as IAppTheme,\n  }))\n\n  localStorage.setItem(THEME_PRESETS_STORAGE_KEY, JSON.stringify(payload))\n}\n\nexport default function ThemePage() {\n  const { t } = useTranslation()\n  const [appTheme, setAppTheme] = useState<AppTheme>(new AppTheme())\n  const [customThemeTags, setCustomThemeTags] = useState<ThemeTag[]>([])\n  const [selectedThemeTag, setSelectedThemeTag] = useState('dark')\n  const isFirstRun = useRef(true)\n\n  // Use debounced theme for saving to avoid excessive API calls\n  const debouncedTheme = useDebounce(appTheme, 500)\n\n  // Load initial theme\n  useEffect(() => {\n    const loadTheme = async () => {\n      try {\n        const css = await AppApi.getThemeCSS()\n        if (css) {\n          const loadedTheme = AppTheme.fromCSS(css)\n          setAppTheme(loadedTheme)\n          consola.info('Loaded app theme:', loadedTheme)\n        }\n\n        const storedThemeTags = loadStoredThemeTags()\n        setCustomThemeTags(storedThemeTags)\n\n        const storedTag = localStorage.getItem(SELECT_THEME_STORAGE_KEY)\n        if (storedTag) {\n          setSelectedThemeTag(storedTag)\n        }\n      }\n      catch (error) {\n        consola.error('Failed to load theme:', error)\n      }\n      finally {\n        isFirstRun.current = false\n      }\n    }\n    loadTheme()\n  }, [])\n\n  // Save theme when it changes (debounced)\n  useEffect(() => {\n    if (isFirstRun.current) { return }\n\n    const saveTheme = async () => {\n      try {\n        const css = debouncedTheme.toCSS(':root')\n        consola.info('Saving new CSS:', css)\n        await AppApi.setThemeCSS(css)\n      }\n      catch (error) {\n        consola.error('Failed to save theme:', error)\n      }\n    }\n    saveTheme()\n  }, [debouncedTheme])\n\n  const handleThemeTagChange = (tag: ThemeTag) => {\n    setSelectedThemeTag(tag.value)\n    localStorage.setItem(SELECT_THEME_STORAGE_KEY, tag.value)\n\n    const newTheme = appTheme.copy(tag.theme)\n    setAppTheme(newTheme)\n  }\n\n  const handleThemeChange = (newTheme: AppTheme) => {\n    setAppTheme(newTheme)\n\n    setCustomThemeTags((prevTags) => {\n      const isCustomPreset = prevTags.some(tag => tag.value === selectedThemeTag)\n      if (isCustomPreset) {\n        const nextTags = prevTags.map(tag =>\n          tag.value === selectedThemeTag ? { ...tag, theme: newTheme.copy() } : tag,\n        )\n        saveStoredThemeTags(nextTags)\n        return nextTags\n      }\n      return prevTags\n    })\n  }\n\n  const handleCreateThemePreset = (name: string) => {\n    const presetName = name.trim()\n\n    if (!presetName) {\n      toast.error(t('theme.createPreset.emptyName'))\n      return false\n    }\n\n    const hasSameName = customThemeTags.some(tag => tag.name.toLocaleLowerCase() === presetName.toLocaleLowerCase())\n    if (hasSameName) {\n      toast.error(t('theme.createPreset.duplicateName'))\n      return false\n    }\n\n    const newPreset: ThemeTag = {\n      name: presetName,\n      value: `preset-${Date.now()}`,\n      theme: appTheme.copy(),\n    }\n\n    const nextThemeTags = [...customThemeTags, newPreset]\n    setCustomThemeTags(nextThemeTags)\n    saveStoredThemeTags(nextThemeTags)\n    setSelectedThemeTag(newPreset.value)\n    localStorage.setItem(SELECT_THEME_STORAGE_KEY, newPreset.value)\n    toast.success(t('theme.createPreset.success'))\n    return true\n  }\n\n  return (\n    <div className=\"h-full overflow-y-auto\">\n      <div className=\"flex flex-col gap-6 max-w-5xl mx-auto min-h-full pb-4 w-[700px]\">\n        <Card className=\"w-full shrink-0\">\n          <CardHeader className=\"shrink-0\">\n            <CardTitle>{t('theme.preview.title')}</CardTitle>\n            <CardDescription>\n              {t('theme.preview.description')}\n            </CardDescription>\n          </CardHeader>\n          <CardContent>\n            <ThemePreview theme={appTheme} />\n          </CardContent>\n        </Card>\n        <Card className=\"flex-1 flex flex-col\">\n          <CardHeader className=\"shrink-0\">\n            <CardTitle className=\"mb-2\">{t('theme.presets')}</CardTitle>\n            <ThemeTags\n              value={selectedThemeTag}\n              presets={customThemeTags}\n              onChange={handleThemeTagChange}\n              onCreatePreset={handleCreateThemePreset}\n            />\n          </CardHeader>\n          <CardContent className=\"space-y-8 flex-1\">\n            <div className=\"flex flex-col gap-8\">\n              <div className=\"w-full space-y-8\">\n                <AppThemeForm\n                  value={appTheme}\n                  onChange={handleThemeChange}\n                />\n              </div>\n            </div>\n          </CardContent>\n        </Card>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/size/size-page.tsx",
    "content": "import type { BroadcastEvent } from '@widget-js/core'\nimport { DeployedWidgetApi, WidgetApiEvent } from '@widget-js/core'\nimport { useAppBroadcast, WindowControls } from '@widget-js/react'\nimport { useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useSearchParams } from 'react-router-dom'\n\nimport { Field, FieldDescription, FieldTitle } from '@/components/ui/field'\nimport { Slider } from '@/components/ui/slider'\n\nexport default function SizePage() {\n  const { t } = useTranslation()\n  const [searchParams] = useSearchParams()\n  const widgetId = searchParams.get('widgetId')\n\n  useAppBroadcast([WidgetApiEvent.WIDGET_REMOVED], (event: BroadcastEvent) => {\n    if (event.event === WidgetApiEvent.WIDGET_REMOVED && event.payload?.widgetId === widgetId) {\n      window.close()\n    }\n  })\n\n  const [width, setWidth] = useState(0)\n  const [height, setHeight] = useState(0)\n  const [minWidth, setMinWidth] = useState(50)\n  const [maxWidth, setMaxWidth] = useState(1000)\n  const [minHeight, setMinHeight] = useState(50)\n  const [maxHeight, setMaxHeight] = useState(1000)\n  const [loading, setLoading] = useState(true)\n\n  useEffect(() => {\n    document.title = t('sizePage.title', '组件大小设置')\n  }, [t])\n\n  useEffect(() => {\n    const loadWidgetInfo = async () => {\n      if (!widgetId) { return }\n\n      try {\n        const widget = await DeployedWidgetApi.getDeployedWidget(widgetId)\n        if (widget) {\n          setWidth(widget.width || 0)\n          setHeight(widget.height || 0)\n\n          if (widget.minWidth) { setMinWidth(widget.minWidth) }\n          if (widget.maxWidth) { setMaxWidth(widget.maxWidth) }\n          if (widget.minHeight) { setMinHeight(widget.minHeight) }\n          if (widget.maxHeight) { setMaxHeight(widget.maxHeight) }\n        }\n      }\n      catch (error) {\n        console.error('Failed to load widget info:', error)\n      }\n      finally {\n        setLoading(false)\n      }\n    }\n\n    loadWidgetInfo()\n  }, [widgetId])\n\n  const handleWidthChange = async (value: number[]) => {\n    const newWidth = value[0]\n    setWidth(newWidth)\n    if (widgetId) {\n      await DeployedWidgetApi.setSize(widgetId, newWidth, height)\n    }\n  }\n\n  const handleHeightChange = async (value: number[]) => {\n    const newHeight = value[0]\n    setHeight(newHeight)\n    if (widgetId) {\n      await DeployedWidgetApi.setSize(widgetId, width, newHeight)\n    }\n  }\n\n  if (loading) {\n    return <div className=\"p-4\">Loading...</div>\n  }\n\n  if (!widgetId) {\n    return <div className=\"p-4\">Missing widgetId parameter</div>\n  }\n\n  return (\n    <div className=\"flex flex-col min-h-screen bg-background\">\n      <header className=\"draggable-region flex h-[48px] shrink-0 items-center justify-between gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12 border-b\">\n        <div className=\"flex items-center gap-2 px-4\">\n          <div className=\"font-bold text-sm\">{t('sizePage.title', '组件大小设置')}</div>\n        </div>\n        <WindowControls />\n      </header>\n\n      <div className=\"flex flex-col gap-6 p-6 min-w-[400px] mx-auto w-full\">\n        <div className=\"flex flex-col gap-8\">\n          <Field className=\"w-full\">\n            <FieldTitle>{t('sizePage.width', '宽度 (Width)')}</FieldTitle>\n            <FieldDescription>\n              {t('sizePage.widthDesc', '设置组件的宽度 ({{value}} px)，取值范围 {{min}} - {{max}} px。', {\n                value: width,\n                min: minWidth,\n                max: maxWidth,\n              })}\n            </FieldDescription>\n            <Slider\n              value={[width]}\n              min={minWidth}\n              max={maxWidth}\n              step={10}\n              onValueChange={handleWidthChange}\n              className=\"mt-2 w-full\"\n            />\n          </Field>\n\n          <Field className=\"w-full\">\n            <FieldTitle>{t('sizePage.height', '高度 (Height)')}</FieldTitle>\n            <FieldDescription>\n              {t('sizePage.heightDesc', '设置组件的高度 ({{value}} px)，取值范围 {{min}} - {{max}} px。', {\n                value: height,\n                min: minHeight,\n                max: maxHeight,\n              })}\n            </FieldDescription>\n            <Slider\n              value={[height]}\n              min={minHeight}\n              max={maxHeight}\n              step={10}\n              onValueChange={handleHeightChange}\n              className=\"mt-2 w-full\"\n            />\n          </Field>\n        </div>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/pages/tray/tray-page.tsx",
    "content": "import { AppApi, BrowserWindowApi, NotificationApi, WidgetApi } from '@widget-js/core'\nimport consola from 'consola'\nimport { LogOut, Plus, Power, RefreshCcw, Settings, Share2 } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { supabase } from '@/api/supabase'\nimport DeployedWidgetList from '@/components/manager/deployed-widget-list'\nimport { SocialLinks } from '@/components/tray/social-links'\nimport { TrayMenuItem } from '@/components/tray/tray-menu-item'\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from '@/components/ui/alert-dialog'\nimport { TooltipProvider } from '@/components/ui/tooltip'\nimport UserAvatar from '@/components/user-avatar'\nimport { useAppRuntimeInfo } from '@/hooks/use-app-runtime-info'\nimport { useSupabaseChannel } from '@/hooks/use-supabase-channel'\nimport { useUser } from '@/hooks/use-user'\n\nexport default function TrayPage() {\n  const { t } = useTranslation()\n  const { simpleInfo } = useAppRuntimeInfo()\n  const [appVersion, setAppVersion] = useState<string>()\n  const { loading, user, nickname, avatar } = useUser()\n  const [loginState, setLoginState] = useState('')\n\n  useEffect(() => {\n    AppApi.getVersion('app').then(setAppVersion)\n    const storedState = localStorage.getItem('wechat_login_state')\n    if (storedState) { setLoginState(storedState) }\n  }, [])\n\n  const shareApp = () => {\n    navigator.clipboard.writeText('https://widgetjs.cn')\n    NotificationApi.success(t('tray.downloadLinkCopied'))\n  }\n\n  const copyAndReport = () => {\n    const text = JSON.stringify(simpleInfo, null, 2)\n    navigator.clipboard.writeText(text)\n    NotificationApi.success(t('tray.infoCopied'))\n    BrowserWindowApi.openUrl('https://faq.widgetjs.cn', { external: true })\n  }\n\n  const restartWidgets = () => {\n    WidgetApi.restartWidgets()\n  }\n\n  // Handle Supabase channel for login\n  useSupabaseChannel(loginState ? `wechat-login-${loginState}` : '', async (payload: any) => {\n    consola.info(payload)\n    const currentSession = payload.payload.session\n    const loginRes = await supabase.auth.setSession(currentSession)\n    if (loginRes.error) {\n      NotificationApi.error(loginRes.error.message)\n    }\n    else {\n      AppApi.showAppWindow('/user/profile', {\n        width: 1200,\n        height: 800,\n      })\n    }\n  })\n\n  const loginPage = () => {\n    if (user) {\n      AppApi.showAppWindow('/user/profile', {\n        width: 1200,\n        height: 800,\n      })\n    }\n    else {\n      const newState = crypto.randomUUID().replace(/-/g, '')\n      setLoginState(newState)\n      localStorage.setItem('wechat_login_state', newState)\n\n      BrowserWindowApi.openUrl(`https://open.weixin.qq.com/connect/qrconnect?appid=wxf91b19da281f23a9&redirect_uri=https%3A%2F%2Fwidgetjs.cn%2Fapi%2Fv1%2Fuser%2Flogin%2Fwechat%2Fcallback&response_type=code&scope=snsapi_login&state=${newState}#wechat_redirect`, {\n        width: 800,\n        height: 600,\n        frame: true,\n        transparent: false,\n        titleBarStyle: 'default',\n      })\n    }\n  }\n\n  return (\n    <TooltipProvider>\n      <div className=\"flex flex-col h-screen bg-background text-foreground select-none overflow-hidden\">\n        {/* Header */}\n        <div className=\"flex items-center w-full p-4 bg-muted/30\">\n          <div className=\"flex w-full gap-2\">\n            <div\n              className=\"flex gap-2 cursor-pointer items-center hover:opacity-80 transition-opacity\"\n              onClick={loginPage}\n            >\n              <UserAvatar src={avatar} className=\"w-10 h-10 rounded-lg bg-primary\" />\n              <div className=\"w-24 truncate font-medium text-sm flex items-center\">\n                {loading ? t('appInfo.loading') : nickname}\n              </div>\n            </div>\n            <div className=\"ml-auto flex flex-col items-end text-xs text-muted-foreground cursor-pointer hover:text-foreground transition-colors\" onClick={() => AppApi.openRuntimeInfoWindow()}>\n              <div>\n                {t('tray.appVersion')}\n                :\n                {appVersion}\n              </div>\n              <div>\n                {t('tray.systemVersion')}\n                :\n                {simpleInfo?.systemName?.replaceAll('Windows', 'Win')}\n              </div>\n            </div>\n          </div>\n        </div>\n\n        {/* Widgets List */}\n        <div className=\"flex flex-col gap-2 border-b flex-1 overflow-hidden\">\n          <div className=\"px-4 py-2 text-sm font-bold text-muted-foreground\">\n            {t('tray.runningWidgets')}\n          </div>\n          <div className=\"flex-1 overflow-y-auto px-4 pb-2 scrollbar-hide\">\n            <DeployedWidgetList />\n          </div>\n        </div>\n\n        {/* Menus */}\n        <div className=\"grid grid-cols-3 gap-2 p-4 bg-background\">\n          <TrayMenuItem\n            icon={Plus}\n            label={t('tray.addWidget')}\n            onClick={() => AppApi.showAppWindow('/widget/search', { width: 1200, height: 800 })}\n          />\n          <TrayMenuItem\n            icon={Settings}\n            label={t('tray.settings.title')}\n            onClick={() => AppApi.openSettingWindow()}\n          />\n          <TrayMenuItem\n            icon={RefreshCcw}\n            label={t('tray.checkUpdates')}\n            onClick={() => AppApi.openCheckUpdateWindow()}\n          />\n          <TrayMenuItem\n            icon={Share2}\n            label={t('tray.shareApp')}\n            onClick={shareApp}\n          />\n\n          <AlertDialog>\n            <AlertDialogTrigger asChild>\n              <TrayMenuItem\n                icon={Power}\n                label={t('tray.restartWidgets')}\n              />\n            </AlertDialogTrigger>\n            <AlertDialogContent>\n              <AlertDialogHeader>\n                <AlertDialogTitle>{t('tray.restartWidgets')}</AlertDialogTitle>\n                <AlertDialogDescription>\n                  {t('tray.restartWidgetsConfirm')}\n                </AlertDialogDescription>\n              </AlertDialogHeader>\n              <AlertDialogFooter>\n                <AlertDialogCancel>{t('tray.no')}</AlertDialogCancel>\n                <AlertDialogAction onClick={restartWidgets}>{t('tray.yes')}</AlertDialogAction>\n              </AlertDialogFooter>\n            </AlertDialogContent>\n          </AlertDialog>\n\n          <TrayMenuItem\n            icon={LogOut}\n            label={t('tray.exit')}\n            onClick={() => AppApi.exit()}\n          />\n        </div>\n\n        {/* Footer */}\n        <div className=\"flex items-center py-2 px-4 bg-muted/30 text-sm\">\n          <SocialLinks />\n          <div className=\"ml-auto cursor-pointer hover:underline text-muted-foreground hover:text-primary\" onClick={copyAndReport}>\n            {t('tray.suggestions')}\n          </div>\n        </div>\n      </div>\n    </TooltipProvider>\n  )\n}\n"
  },
  {
    "path": "src/pages/user/profile-page.tsx",
    "content": "import type { Widget } from '@widget-js/core'\nimport { NotificationApi, WidgetApi } from '@widget-js/core'\nimport consola from 'consola'\nimport { Check, Loader2, LogOut, Pencil } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { toast } from 'sonner'\n\nimport { getStorageLink, supabase } from '@/api/supabase'\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from '@/components/ui/alert-dialog'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport UserAvatar from '@/components/user-avatar'\nimport WidgetSyncItem from '@/components/widget-sync-item'\nimport { useUser } from '@/hooks/use-user'\n\nexport default function ProfilePage() {\n  const [nicknameEdit, setNicknameEdit] = useState('')\n  const [avatarEdit, setAvatarEdit] = useState('')\n  const [widgets, setWidgets] = useState<Widget[]>([])\n  const [signOutLoading, setSignOutLoading] = useState(false)\n  const [uploadLoading, setUploadLoading] = useState(false)\n\n  const { avatar, nickname, userId } = useUser((user) => {\n    if (user) {\n      setNicknameEdit(user.user_metadata?.nickname || '')\n      setAvatarEdit(user.user_metadata?.avatar || '')\n    }\n  })\n\n  // Sync state when user changes (e.g. initial load)\n  useEffect(() => {\n    if (nickname) { setNicknameEdit(nickname) }\n    if (avatar) { setAvatarEdit(avatar) }\n  }, [nickname, avatar])\n\n  useEffect(() => {\n    WidgetApi.getWidgets().then((arr) => {\n      const syncWidgets = arr.filter(it => it.synchronizable)\n      setWidgets(syncWidgets)\n      consola.info('Widgets:', syncWidgets)\n    })\n  }, [])\n\n  const pickImageAndUpload = () => {\n    const input = document.createElement('input')\n    input.type = 'file'\n    input.accept = 'image/jpg,image/jpeg,image/png,image/webp'\n    input.onchange = async (event) => {\n      const file = (event.target as HTMLInputElement).files?.[0]\n      if (!file) { return }\n\n      setUploadLoading(true)\n      const loadingToast = toast.loading('上传中...')\n\n      try {\n        if (!userId) {\n          toast.error('用户未登录')\n          return\n        }\n\n        const fileExtension = file.name.split('.').pop()\n        const fileName = `avatar.${fileExtension}`\n\n        const { data, error } = await supabase.storage\n          .from('widget-avatar')\n          .upload(`${userId}/${fileName}`, file, {\n            cacheControl: '3600',\n            upsert: true,\n          })\n\n        if (error) {\n          toast.error(`上传失败: ${error.message}`)\n        }\n        else {\n          const fullUrl = getStorageLink(data?.fullPath)\n          const updateRes = await supabase.auth.updateUser({\n            data: { avatar: fullUrl },\n          })\n\n          if (updateRes.error) {\n            toast.error(`更新头像失败:${updateRes.error.message}`)\n          }\n          else {\n            setAvatarEdit(fullUrl)\n            toast.success('头像更新成功')\n          }\n        }\n      }\n      catch (err) {\n        toast.error('上传过程中发生错误')\n        consola.error(err)\n      }\n      finally {\n        setUploadLoading(false)\n        toast.dismiss(loadingToast)\n      }\n    }\n    input.click()\n  }\n\n  const signOut = async () => {\n    setSignOutLoading(true)\n    try {\n      const { error } = await supabase.auth.signOut()\n      if (error) {\n        toast.error(`退出登录失败: ${error.message}`)\n      }\n      else {\n        NotificationApi.success('退出登录成功')\n        window.close()\n      }\n    }\n    finally {\n      setSignOutLoading(false)\n    }\n  }\n\n  const saveNickName = async () => {\n    if (!nicknameEdit.trim()) {\n      toast.error('昵称不能为空')\n      return\n    }\n\n    const { error } = await supabase.auth.updateUser({\n      data: { nickname: nicknameEdit.trim() },\n    })\n\n    if (error) {\n      toast.error(`更新昵称失败:${error.message}`)\n    }\n    else {\n      toast.success('昵称更新成功')\n    }\n  }\n\n  return (\n    <div className=\"flex flex-col gap-6 max-w-md mx-auto w-full\">\n      <Card>\n        <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n          <CardTitle>用户信息</CardTitle>\n          <AlertDialog>\n            <AlertDialogTrigger asChild>\n              <Button variant=\"ghost\" size=\"icon\" disabled={signOutLoading}>\n                {signOutLoading ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <LogOut className=\"h-4 w-4\" />}\n              </Button>\n            </AlertDialogTrigger>\n            <AlertDialogContent>\n              <AlertDialogHeader>\n                <AlertDialogTitle>确认退出登录?</AlertDialogTitle>\n                <AlertDialogDescription>\n                  退出登录后将无法使用同步功能和 AI 功能。\n                </AlertDialogDescription>\n              </AlertDialogHeader>\n              <AlertDialogFooter>\n                <AlertDialogCancel>取消</AlertDialogCancel>\n                <AlertDialogAction onClick={signOut}>确认退出</AlertDialogAction>\n              </AlertDialogFooter>\n            </AlertDialogContent>\n          </AlertDialog>\n        </CardHeader>\n        <CardContent className=\"space-y-8 pt-6\">\n          <div className=\"flex flex-col items-center gap-6\">\n            <div className=\"relative group cursor-pointer\" onClick={pickImageAndUpload}>\n              <UserAvatar\n                src={avatarEdit}\n                className=\"w-32 h-32 transition-opacity group-hover:opacity-80\"\n                size=\"lg\"\n              />\n              <div className=\"absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity bg-black/30 rounded-full\">\n                <Pencil className=\"h-4 w-4 text-white\" />\n              </div>\n              {uploadLoading && (\n                <div className=\"absolute inset-0 flex items-center justify-center bg-black/50 rounded-full\">\n                  <Loader2 className=\"h-8 w-8 text-white animate-spin\" />\n                </div>\n              )}\n            </div>\n\n            <div className=\"flex w-full max-w-sm items-center space-x-2\">\n              <Input\n                value={nicknameEdit}\n                onChange={e => setNicknameEdit(e.target.value)}\n                placeholder=\"昵称\"\n                maxLength={10}\n              />\n              <Button onClick={saveNickName} size=\"icon\">\n                <Check className=\"h-4 w-4\" />\n              </Button>\n            </div>\n          </div>\n        </CardContent>\n      </Card>\n\n      <Card>\n        <CardHeader>\n          <CardTitle>数据同步</CardTitle>\n        </CardHeader>\n        <CardContent>\n          <div className=\"space-y-4\">\n\n            <div className=\"grid gap-4\">\n              {widgets.map(widget => (\n                <WidgetSyncItem key={widget.name} widget={widget} />\n              ))}\n              {widgets.length === 0 && (\n                <p className=\"text-muted-foreground text-center text-sm py-4\">\n                  暂无同步小组件\n                </p>\n              )}\n            </div>\n          </div>\n        </CardContent>\n      </Card>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/router/index.tsx",
    "content": "import type { RouteObject } from 'react-router-dom'\nimport {\n  Bot,\n  Code2,\n  Globe,\n  Package,\n  Palette,\n  Plus,\n  Settings,\n} from 'lucide-react'\nimport { createHashRouter, Navigate } from 'react-router-dom'\nimport { DashboardLayout } from '@/components/dashboard-layout'\nimport AddWidgetPage from '@/pages/add/add-widget-page'\nimport AiPage from '@/pages/ai/ai-page'\nimport DevPage from '@/pages/dev/dev-page'\nimport ErrorPage from '@/pages/error-page'\nimport WidgetPackageManagerPage from '@/pages/packages/widget-package-manager-page'\nimport AppInfoPage from '@/pages/settings/app-info-page'\nimport GeneralPage from '@/pages/settings/general-page'\nimport ProxyPage from '@/pages/settings/proxy-page'\nimport ThemePage from '@/pages/settings/theme-page'\nimport SizePage from '@/pages/size/size-page'\nimport TrayPage from '@/pages/tray/tray-page'\nimport ProfilePage from '@/pages/user/profile-page'\n\nexport const routes: RouteObject[] = [\n  {\n    path: '/',\n    element: <DashboardLayout />,\n    errorElement: <ErrorPage />,\n    children: [\n      {\n        index: true,\n        element: <Navigate to=\"/widget/search\" replace />,\n      },\n      {\n        path: 'widget',\n        handle: {\n          title: 'sidebar.widgetManagement',\n          sidebarGroup: true,\n        },\n        children: [\n          {\n            index: true,\n            path: 'search',\n            element: <AddWidgetPage />,\n            handle: {\n              title: 'sidebar.addWidget',\n              icon: Plus,\n              sidebarMenu: true,\n            },\n          },\n          {\n            path: 'package',\n            element: <WidgetPackageManagerPage />,\n            handle: {\n              title: 'sidebar.packageManagement',\n              icon: Package,\n              sidebarMenu: true,\n            },\n          },\n          {\n            path: 'dev',\n            element: <DevPage />,\n            handle: {\n              title: 'sidebar.dev',\n              icon: Code2,\n              sidebarMenu: true,\n            },\n          },\n        ],\n      },\n      {\n        path: 'setting',\n        handle: {\n          title: 'sidebar.generalSettings',\n          sidebarGroup: true,\n        },\n        children: [\n          {\n            path: 'common',\n            element: <GeneralPage />,\n            handle: {\n              title: 'sidebar.generalSettings',\n              icon: Settings,\n              sidebarMenu: true,\n            },\n          },\n          {\n            path: 'theme',\n            element: <ThemePage />,\n            handle: {\n              title: 'sidebar.globalTheme',\n              icon: Palette,\n              sidebarMenu: true,\n            },\n          },\n          {\n            path: 'ai',\n            element: <AiPage />,\n            handle: {\n              title: 'sidebar.ai',\n              icon: Bot,\n              sidebarMenu: true,\n            },\n          },\n          {\n            path: 'proxy',\n            element: <ProxyPage />,\n            handle: {\n              title: 'sidebar.proxySettings',\n              icon: Globe,\n              group: 'settings',\n              sidebarMenu: true,\n            },\n          },\n          {\n            path: 'info',\n            element: <AppInfoPage />,\n            handle: {\n              title: 'appInfo.title',\n            },\n          },\n        ],\n      },\n      {\n        path: 'user/profile',\n        element: <ProfilePage />,\n        handle: { title: 'user.account' },\n      },\n    ],\n  },\n  {\n    path: '/tray/menu',\n    element: <TrayPage />,\n  },\n  {\n    path: '/size',\n    element: <SizePage />,\n  },\n  {\n    path: '*',\n    element: <ErrorPage />,\n  },\n]\n\nexport const router = createHashRouter(routes)\n"
  },
  {
    "path": "src/utils/version-utils.ts",
    "content": "import type { AppVersion } from '@/model/app-version'\nimport { AppApi } from '@widget-js/core'\nimport axios from 'axios'\nimport consola from 'consola'\nimport semver from 'semver'\n\nexport default class VersionUtils {\n  static checkNewVersion(onNewVersion: (version: AppVersion) => void, onError?: (error: any) => void, onFinally?: () => void) {\n    axios.get('https://widget-fun.oss-cn-hangzhou.aliyuncs.com/version/version.json')\n      .then(async (response) => {\n        // handle success\n        if (response.status == 200) {\n          const data = response.data as AppVersion\n          const currentVersion = await AppApi.getVersion()\n          consola.info('current:', currentVersion, 'server:', data.version)\n          if (semver.gt(data.version, currentVersion)) {\n            consola.info('New version detected:', data.version)\n            consola.info('Download Link:', data.downloadLink)\n            onNewVersion(data)\n          }\n        }\n      })\n      .catch((error) => {\n        onError?.(error)\n      })\n      .finally(() => {\n        onFinally?.()\n      })\n  }\n}\n"
  },
  {
    "path": "src/utils/widget-util.ts",
    "content": "import type { SocialType } from '@widget-js/core'\n\nexport default class WidgetUtil {\n  static getSocialLinkIcon(socialName: SocialType | string) {\n    switch (socialName) {\n      case 'github':\n        return 'https://widgetjs.cn/image/logo/github.png'\n      case 'bilibili':\n        return 'https://widgetjs.cn/image/logo/bilibili.png'\n      case 'discord':\n        return 'https://widgetjs.cn/image/logo/discord.png'\n      case 'tiktok':\n      case 'douyin':\n        return 'https://widgetjs.cn/image/logo/douyin.png'\n      case 'email':\n        return 'https://widgetjs.cn/image/logo/email.png'\n      case 'qq':\n        return 'https://widgetjs.cn/image/logo/qq.png'\n      case 'gitee':\n        return 'https://widgetjs.cn/image/logo/gitee.png'\n      case 'youtube':\n        return 'https://widgetjs.cn/image/logo/youtube.png'\n      case 'wechat':\n        return 'https://widgetjs.cn/image/logo/wechat.png'\n      default:\n        return ''\n    }\n  }\n}\n"
  },
  {
    "path": "tsconfig.app.json",
    "content": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n    \"target\": \"ES2023\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2023\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"types\": [\"vite/client\"],\n    \"skipLibCheck\": true,\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [\n    { \"path\": \"./tsconfig.app.json\" },\n    { \"path\": \"./tsconfig.node.json\" }\n  ],\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"target\": \"ES2023\",\n    \"lib\": [\"ES2023\"],\n    \"module\": \"ESNext\",\n    \"types\": [\"node\"],\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"erasableSyntaxOnly\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import path from 'node:path'\nimport tailwindcss from '@tailwindcss/vite'\nimport react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\n// https://vite.dev/config/\nexport default defineConfig((_config) => {\n  return {\n    base: './',\n    plugins: [react(), tailwindcss()],\n    resolve: {\n      alias: {\n        '@': path.resolve(__dirname, './src'),\n      },\n    },\n    server: {\n      port: 8085,\n    },\n  }\n})\n"
  }
]