From a43cfb4b5fdd8eee08405c6f176077da73358c6f Mon Sep 17 00:00:00 2001 From: abearxiong Date: Thu, 29 May 2025 22:16:18 +0800 Subject: [PATCH] temp --- astro.config.mjs | 5 + package.json | 1 + pnpm-lock.yaml | 45 ++++++ src/apps/ai-editor/content.tsx | 37 +++++ src/apps/ai-editor/index.tsx | 27 +++- .../ai-editor/modules/get-content-type.ts | 28 ++++ src/apps/ai-editor/sidebar.tsx | 135 ++++++++++++++++++ src/apps/ai-editor/store/menu.ts | 104 ++++++++++++++ src/pages/ai-editor/index.astro | 10 ++ src/pages/ai-editor/sidebar.astro | 10 ++ src/query/query-resources/index.ts | 71 +++++++++ 11 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 src/apps/ai-editor/content.tsx create mode 100644 src/apps/ai-editor/modules/get-content-type.ts create mode 100644 src/apps/ai-editor/sidebar.tsx create mode 100644 src/apps/ai-editor/store/menu.ts create mode 100644 src/pages/ai-editor/index.astro create mode 100644 src/pages/ai-editor/sidebar.astro create mode 100644 src/query/query-resources/index.ts diff --git a/astro.config.mjs b/astro.config.mjs index 969d65a..6319d6a 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -19,6 +19,10 @@ let proxy = { target: `${target}`, secure: false, }, + '/root/resources/': { + target: `${target}`, + secure: false, + }, '/user/login/': { target: `${target}`, secure: false, @@ -30,6 +34,7 @@ let proxy = { export default defineConfig({ // ... // site: 'https://kevisual.xiongxiao.me/root/astro', + // base: isDev ? undefined : pkgs.basename, base: isDev ? undefined : pkgs.basename, integrations: [ mdx(), diff --git a/package.json b/package.json index e10c647..f32b5ec 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "react-draggable": "^4.4.6", "react-hook-form": "^7.56.4", "react-i18next": "^15.5.2", + "react-resizable-panels": "^3.0.2", "react-sortablejs": "^6.1.4", "react-toastify": "^11.0.5", "sortablejs": "^1.15.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4dec81d..5140831 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,6 +116,9 @@ importers: react-i18next: specifier: ^15.5.2 version: 15.5.2(i18next@25.2.0(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) + react-resizable-panels: + specifier: ^3.0.2 + version: 3.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-sortablejs: specifier: ^6.1.4 version: 6.1.4(@types/sortablejs@1.15.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sortablejs@1.15.6) @@ -635,67 +638,79 @@ packages: resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} @@ -1190,56 +1205,67 @@ packages: resolution: {integrity: sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.40.1': resolution: {integrity: sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.40.1': resolution: {integrity: sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.40.1': resolution: {integrity: sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.40.1': resolution: {integrity: sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.40.1': resolution: {integrity: sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.40.1': resolution: {integrity: sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.40.1': resolution: {integrity: sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.40.1': resolution: {integrity: sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.40.1': resolution: {integrity: sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.40.1': resolution: {integrity: sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.40.1': resolution: {integrity: sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==} @@ -1318,24 +1344,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.7': resolution: {integrity: sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.7': resolution: {integrity: sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.7': resolution: {integrity: sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.7': resolution: {integrity: sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A==} @@ -2325,24 +2355,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.1: resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.1: resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.1: resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.1: resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} @@ -2901,6 +2935,12 @@ packages: '@types/react': optional: true + react-resizable-panels@3.0.2: + resolution: {integrity: sha512-j4RNII75fnHkLnbsTb5G5YsDvJsSEZrJK2XSF2z0Tc2jIonYlIVir/Yh/5LvcUFCfs1HqrMAoiBFmIrRjC4XnA==} + peerDependencies: + react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-sortablejs@6.1.4: resolution: {integrity: sha512-fc7cBosfhnbh53Mbm6a45W+F735jwZ1UFIYSrIqcO/gRIFoDyZeMtgKlpV4DdyQfbCzdh5LoALLTDRxhMpTyXQ==} peerDependencies: @@ -6499,6 +6539,11 @@ snapshots: optionalDependencies: '@types/react': 19.1.5 + react-resizable-panels@3.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-sortablejs@6.1.4(@types/sortablejs@1.15.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sortablejs@1.15.6): dependencies: '@types/sortablejs': 1.15.8 diff --git a/src/apps/ai-editor/content.tsx b/src/apps/ai-editor/content.tsx new file mode 100644 index 0000000..387dc7b --- /dev/null +++ b/src/apps/ai-editor/content.tsx @@ -0,0 +1,37 @@ +import { useShallow } from 'zustand/shallow'; +import { getCurrentContent, useMenuStore } from './store/menu'; +import { useEffect, useRef, useState } from 'react'; +import { checkText } from './modules/get-content-type'; +// import { TextEditor } from '@kevisual/markdown-editor/tiptap/editor.ts'; + +export const Content: React.FC = () => { + const { currentPath } = useMenuStore( + useShallow((state) => ({ + currentPath: state.currentPath, + })), + ); + const [content, setContent] = useState(null); + + useEffect(() => { + const fetchContent = async () => { + if (currentPath) { + const isText = checkText(currentPath); + if (!isText.isText) { + setContent('This file type is not supported for viewing.'); + return; + } + setContent('Loading content...'); + const content = await getCurrentContent(currentPath); + setContent(content); + } + }; + fetchContent(); + }, [currentPath]); + const ref = useRef(null); + + return ( +
+
{content}
+
+ ); +}; diff --git a/src/apps/ai-editor/index.tsx b/src/apps/ai-editor/index.tsx index c2772e0..93183a2 100644 --- a/src/apps/ai-editor/index.tsx +++ b/src/apps/ai-editor/index.tsx @@ -1,4 +1,7 @@ import { ToastProvider } from '@/modules/toast/Provider'; +import Sidebar from './sidebar'; +import { Content } from './content'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; export const App = () => { return ( @@ -8,11 +11,33 @@ export const App = () => { ); }; +export const SidebarApp = () => { + return ( + + + + ); +}; + export const AIEditor = () => { return (
AI Editor
-
This is a placeholder for the AI Editor application.
+
+
+ + + + + + +
+ +
+
+
+
+
); }; diff --git a/src/apps/ai-editor/modules/get-content-type.ts b/src/apps/ai-editor/modules/get-content-type.ts new file mode 100644 index 0000000..88cee57 --- /dev/null +++ b/src/apps/ai-editor/modules/get-content-type.ts @@ -0,0 +1,28 @@ +export const checkText = (filepath: string) => { + const textExtensions = [ + '.txt', + '.md', + '.json', + '.js', + '.ts', + '.html', + '.css', + '.xml', + '.csv', + + '.tsx', + '.jsx', + '.mjs', + '.cjs', + '.vue', + + '.yaml', + '.yml', + '.log', + '.conf', + '.env', + '.example', // + ]; + const extension = filepath.split('.').pop()?.toLowerCase(); + return { isText: textExtensions.includes(`.${extension}`) }; +}; diff --git a/src/apps/ai-editor/sidebar.tsx b/src/apps/ai-editor/sidebar.tsx new file mode 100644 index 0000000..4fcc92d --- /dev/null +++ b/src/apps/ai-editor/sidebar.tsx @@ -0,0 +1,135 @@ +import React, { useState, useMemo, useEffect } from 'react'; +import { File, Folder, ChevronRight, ChevronDown, Loader2 } from 'lucide-react'; +import { useMenuStore, init } from './store/menu.ts'; +import type { MenuItem } from './store/menu.ts'; + +// 辅助函数:获取目录的直接子项 +const getDirectChildren = (path: string, menu: MenuItem[]): MenuItem[] => { + const pathPrefix = path ? path + '/' : ''; + + return menu.filter((item) => { + if (!item.path.startsWith(pathPrefix)) return false; + if (item.path === path) return false; + + const relativePath = item.path.slice(pathPrefix.length); + return !relativePath.includes('/'); + }); +}; + +interface DirectoryItemProps { + item: MenuItem; + level: number; + prefix?: string; +} + +const DirectoryItem: React.FC = ({ item, level, prefix }) => { + const [expanded, setExpanded] = useState(false); + const { menu } = useMenuStore(); + const children = useMemo(() => { + const children = getDirectChildren(item.path, menu); + return children.sort((a, b) => { + if (a.type === 'directory' && b.type === 'file') return -1; // 目录排在前面 + if (a.type === 'file' && b.type === 'directory') return 1; // 文件排在后面 + return a.path.localeCompare(b.path); // 同类型按路径排序 + }); + }, [menu, item.path]); + const directory = item.path.replace(prefix || '', '').replace(/\/$/, '') || '/'; // 去掉前缀和末尾斜杠 + const newPrefix = item.path + '/'; + return ( +
+
setExpanded(!expanded)}> + {children.length > 0 ? ( + expanded ? ( + + ) : ( + + ) + ) : ( +
+ )} + + {directory} +
+ + {expanded && children.length > 0 && ( +
+ {children.map((child) => + child.type === 'directory' ? ( + + ) : ( + + ), + )} +
+ )} +
+ ); +}; + +interface FileItemProps { + item: MenuItem; + level: number; + prefix?: string; +} + +const FileItem: React.FC = ({ item, level, prefix }) => { + const { currentPath, setCurrentPath } = useMenuStore(); + + return ( +
setCurrentPath(item.path)}> +
{/* 占位,保持对齐 */} + + {item.path.replace(prefix || '', '')} +
+ ); +}; + +export const Sidebar: React.FC = () => { + const { menu, isLoading } = useMenuStore(); + useEffect(() => { + // 初始化菜单数据 + init(); + }, []); + // 获取顶层项(根目录下的文件和文件夹) + const rootItems = useMemo(() => { + // return menu.filter((item) => !item.path.includes('/')); + const _menu = menu.filter((item) => { + return !item.path.includes('/'); + }); + return _menu.sort((a, b) => { + if (a.type === 'directory' && b.type === 'file') return -1; // 目录排在前面 + if (a.type === 'file' && b.type === 'directory') return 1; // 文件排在后面 + return a.path.localeCompare(b.path); // 同类型按路径排序 + }); + }, [menu]); + + return ( +
+
+ {/*

文件

*/} + {isLoading && } +
+
+ {menu.length === 0 && !isLoading ? ( +
暂无文件
+ ) : ( +
+ {rootItems.map((item) => + item.type === 'directory' ? : , + )} +
+ )} +
+
+ ); +}; + +export default Sidebar; diff --git a/src/apps/ai-editor/store/menu.ts b/src/apps/ai-editor/store/menu.ts new file mode 100644 index 0000000..59f81f6 --- /dev/null +++ b/src/apps/ai-editor/store/menu.ts @@ -0,0 +1,104 @@ +import { create } from 'zustand'; +import { query } from '@/modules/query'; +import { QueryLoginBrowser } from '@/query/query-login/query-login-browser'; +import { QueryResources } from '@/query/query-resources/index'; + +export const queryLogin = new QueryLoginBrowser({ query }); +export const queryResources = new QueryResources({ + prefix: '/root/resources/', +}); + +export type MenuItem = { + type: 'file' | 'directory'; + name?: string; // type === 'file' + prefix?: string; // type === 'directory' + etag?: string; + lastModified?: string; // ISO date string + size: number; + url: string; + path: string; // a/b/file.md + pathname: string; +}; + +export type Menu = MenuItem[]; + +interface MenuState { + menu: Menu; + currentPath: string | null; + isLoading: boolean; + setMenu: (menu: Menu) => void; + setCurrentPath: (path: string | null) => void; + setLoading: (status: boolean) => void; +} + +export const useMenuStore = create((set) => ({ + menu: [], + currentPath: null, + isLoading: false, + setMenu: (menu) => set({ menu }), + setCurrentPath: (currentPath) => set({ currentPath }), + setLoading: (isLoading) => set({ isLoading }), +})); + +class Status { + isInitialized = false; + username = ''; +} +const status = new Status(); + +export const init = async (prefix: string = '') => { + const { setMenu, setCurrentPath, setLoading } = useMenuStore.getState(); + let me = await queryLogin.checkLocalUser(); + const isInitialized = status.isInitialized; + if (!isInitialized && me) { + status.isInitialized = true; + status.username = me.username!; + queryResources.setUsername(status.username); + } + let recursive = true; + const data = {}; + if (recursive) { + data['recursive'] = recursive; + } + const res = await queryResources.getList(prefix, data); + if (res.code === 200) { + const menu = res.data!.map((item: any) => { + if (item.prefix) { + item.type = 'directory'; + } else { + item.type = 'file'; + } + return item; + }); + console.log('init menu', menu); + if (recursive) { + const obj: Record = {}; + menu.forEach((item) => { + const parts = item.path.split('/'); + const dirParts = parts.slice(0, -1); + for (let i = 0; i < dirParts.length; i++) { + const dir = dirParts.slice(0, i + 1).join('/'); + if (!dir) continue; // skip root + obj[dir] = obj[dir] || { type: 'directory', path: dir }; + } + }); + Object.keys(obj).forEach((key) => { + const item = obj[key]; + menu.push(item); + }); + } + setMenu(menu); + setCurrentPath(''); + } +}; + +export const getCurrentContent = async (currentPath: string): Promise => { + if (!currentPath) return null; + const res = await queryResources.fetchFile(currentPath); + if (res.success) { + return res.data; + } else { + console.error('Error fetching content:', res.message); + return null; + } +}; diff --git a/src/pages/ai-editor/index.astro b/src/pages/ai-editor/index.astro new file mode 100644 index 0000000..fca3329 --- /dev/null +++ b/src/pages/ai-editor/index.astro @@ -0,0 +1,10 @@ +--- +import '@/styles/global.css'; +import '@/styles/theme.css'; +import Blank from '@/components/html/blank.astro'; +import { App } from '@/apps/ai-editor'; +--- + + + + diff --git a/src/pages/ai-editor/sidebar.astro b/src/pages/ai-editor/sidebar.astro new file mode 100644 index 0000000..9ec2c1b --- /dev/null +++ b/src/pages/ai-editor/sidebar.astro @@ -0,0 +1,10 @@ +--- +import '@/styles/global.css'; +import '@/styles/theme.css'; +import Blank from '@/components/html/blank.astro'; +import { SidebarApp } from '@/apps/ai-editor'; +--- + + + + diff --git a/src/query/query-resources/index.ts b/src/query/query-resources/index.ts new file mode 100644 index 0000000..a913183 --- /dev/null +++ b/src/query/query-resources/index.ts @@ -0,0 +1,71 @@ +import { adapter, DataOpts, Result } from '@kevisual/query'; + +type QueryResourcesOptions = { + prefix?: string; + storage?: Storage; + username?: string; + [key: string]: any; +}; +export class QueryResources { + prefix: string; // root/resources + storage: Storage; + constructor(opts: QueryResourcesOptions) { + if (opts.username) { + this.prefix = `/${opts.username}/resources/`; + } else { + this.prefix = opts.prefix || ''; + } + this.storage = opts.storage || localStorage; + } + setUsername(username: string) { + this.prefix = `/${username}/resources/`; + } + header(headers?: Record, json = true): Record { + const token = this.storage.getItem('token'); + const _headers: Record = { + 'Content-Type': 'application/json', + ...headers, + }; + if (!json) { + delete _headers['Content-Type']; + } + if (!token) { + return _headers; + } + return { + ..._headers, + Authorization: `Bearer ${token}`, + }; + } + async get(data: any, opts: DataOpts): Promise { + return adapter({ + url: opts.url!, + method: 'GET', + body: data, + ...opts, + headers: this.header(opts?.headers), + }); + } + async getList(prefix: string, data?: { recursive?: boolean }, opts?: DataOpts): Promise> { + return this.get(data, { + url: `${this.prefix}${prefix}`, + body: data, + ...opts, + }); + } + async fetchFile(filepath: string, opts?: DataOpts): Promise> { + return fetch(`${this.prefix}${filepath}`, { + method: 'GET', + headers: this.header(opts?.headers, false), + }).then(async (res) => { + if (!res.ok) { + return { + code: 500, + success: false, + message: `Failed to fetch file: ${res.status} ${res.statusText}`, + } as Result; + } + return { code: 200, data: await res.text(), success: true } as Result; + }); + } +}