diff --git a/index.html b/index.html
index dab4a40..fca2209 100644
--- a/index.html
+++ b/index.html
@@ -1,5 +1,5 @@
-
+
diff --git a/package.json b/package.json
index bbe9233..9cf9827 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
"@emotion/styled": "^11.14.0",
"@icon-park/react": "^1.4.2",
"@kevisual/center-components": "workspace:*",
- "@kevisual/codemirror": "^0.0.2",
+ "@kevisual/codemirror": "workspace:*",
"@kevisual/container": "1.0.0",
"@kevisual/query": "^0.0.8",
"@kevisual/resources": "workspace:*",
@@ -36,12 +36,16 @@
"d3": "^7.9.0",
"dayjs": "^1.11.13",
"eventemitter3": "^5.0.1",
+ "i18next": "^24.2.3",
+ "i18next-http-backend": "^3.0.2",
"immer": "^10.1.1",
"lodash-es": "^4.17.21",
"marked": "^15.0.7",
"nanoid": "^5.1.4",
"react": "19.0.0",
"react-dom": "19.0.0",
+ "react-hook-form": "^7.54.2",
+ "react-i18next": "^15.4.1",
"react-resizable-panels": "^2.1.7",
"react-router": "^7.3.0",
"react-router-dom": "^7.3.0",
diff --git a/packages/codemirror/.gitignore b/packages/codemirror/.gitignore
new file mode 100644
index 0000000..e440e68
--- /dev/null
+++ b/packages/codemirror/.gitignore
@@ -0,0 +1,27 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+tsconfig.app.tsbuildinfo
+tsconfig.node.tsbuildinfo
\ No newline at end of file
diff --git a/packages/codemirror/index.html b/packages/codemirror/index.html
new file mode 100644
index 0000000..2df0f35
--- /dev/null
+++ b/packages/codemirror/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+AI Apps
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json
new file mode 100644
index 0000000..f00cb15
--- /dev/null
+++ b/packages/codemirror/package.json
@@ -0,0 +1,70 @@
+{
+ "name": "@kevisual/codemirror",
+ "version": "0.0.1",
+ "description": "",
+ "main": "index.js",
+ "basename": "/root/codemirror",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "pub": "envision deploy ./dist -k codemirror -v 0.0.1 -u -o root"
+ },
+ "files": [
+ "src"
+ ],
+ "keywords": [],
+ "author": "abearxiong ",
+ "license": "MIT",
+ "type": "module",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.18.6",
+ "@codemirror/basic-setup": "^0.20.0",
+ "@codemirror/commands": "^6.8.0",
+ "@codemirror/history": "^0.19.2",
+ "@codemirror/lang-css": "^6.3.1",
+ "@codemirror/lang-html": "^6.4.9",
+ "@codemirror/lang-javascript": "^6.2.3",
+ "@codemirror/lang-json": "^6.0.1",
+ "@codemirror/lang-markdown": "^6.3.2",
+ "@codemirror/lang-yaml": "^6.1.2",
+ "@codemirror/language": "^6.11.0",
+ "@codemirror/state": "^6.5.2",
+ "@codemirror/view": "^6.36.4",
+ "@emotion/react": "^11.14.0",
+ "@emotion/styled": "^11.14.0",
+ "@kevisual/center-components": "workspace:*",
+ "@kevisual/router": "^0.0.9",
+ "@kevisual/store": "^0.0.2",
+ "@mui/material": "^6.4.8",
+ "@types/lodash-es": "^4.17.12",
+ "@types/nprogress": "^0.2.3",
+ "@uiw/codemirror-theme-duotone": "^4.23.10",
+ "@uiw/codemirror-theme-vscode": "^4.23.10",
+ "@vitejs/plugin-basic-ssl": "^2.0.0",
+ "codemirror": "^6.0.1",
+ "dayjs": "^1.11.13",
+ "highlight.js": "^11.11.1",
+ "immer": "^10.1.1",
+ "lodash-es": "^4.17.21",
+ "lucide-react": "^0.483.0",
+ "marked": "^15.0.7",
+ "marked-highlight": "^2.2.1",
+ "nanoid": "^5.1.5",
+ "nprogress": "^0.2.0",
+ "prettier": "^3.5.3",
+ "pretty-bytes": "^6.1.1",
+ "react": "19.0.0",
+ "react-datepicker": "^8.2.1",
+ "react-dom": "19.0.0",
+ "react-dropzone": "^14.3.8",
+ "react-toastify": "^11.0.5",
+ "zustand": "^5.0.3"
+ },
+ "devDependencies": {
+ "@kevisual/types": "^0.0.6"
+ },
+ "exports": {
+ ".": "./src/index.tsx",
+ "./*": "./src/*"
+ }
+}
\ No newline at end of file
diff --git a/packages/codemirror/src/assets/style.css b/packages/codemirror/src/assets/style.css
new file mode 100644
index 0000000..552e40c
--- /dev/null
+++ b/packages/codemirror/src/assets/style.css
@@ -0,0 +1,3 @@
+.cm-editor {
+ height: 100%;
+}
diff --git a/packages/codemirror/src/editor/editor.ts b/packages/codemirror/src/editor/editor.ts
new file mode 100644
index 0000000..6a908cd
--- /dev/null
+++ b/packages/codemirror/src/editor/editor.ts
@@ -0,0 +1,120 @@
+import { EditorView, keymap } from '@codemirror/view';
+import { javascript } from '@codemirror/lang-javascript';
+import { markdown } from '@codemirror/lang-markdown';
+import { html } from '@codemirror/lang-html';
+import { css } from '@codemirror/lang-css';
+import { json } from '@codemirror/lang-json';
+import { yaml } from '@codemirror/lang-yaml';
+import { history } from '@codemirror/history';
+import { vscodeLight } from '@uiw/codemirror-theme-vscode';
+import { formatKeymap } from './modules/keymap';
+import { Compartment, EditorState, Extension } from '@codemirror/state';
+import { defaultKeymap } from '@codemirror/commands';
+import { autocompletion, Completion } from '@codemirror/autocomplete';
+import { getFileType } from './utils/get-file-type';
+type BaseEditorOpts = {
+ language?: string;
+ filename: string;
+ autoComplete?: {
+ props?: any;
+ open?: boolean;
+ overrideList?: Completion[];
+ };
+};
+export class BaseEditor {
+ editor?: EditorView;
+ onChangeCompartment?: Compartment;
+ language: string;
+ filename: string;
+ el?: HTMLElement;
+ autoComplete?: {
+ props?: any;
+ open?: boolean;
+ overrideList?: Completion[];
+ };
+
+ constructor(opts?: BaseEditorOpts) {
+ this.filename = opts?.filename || '';
+ this.language = opts?.language || 'typescript';
+ if (this.filename && !opts?.language) {
+ // 根据文件名自动判断语言
+ const fileType = getFileType(this.filename);
+ this.language = fileType || 'typescript';
+ }
+ this.autoComplete = opts?.autoComplete || { open: true, overrideList: [] };
+ this.onChangeCompartment = new Compartment(); // 创建一个用于 onChange 的独立扩展
+ }
+ onEditorChange(view: EditorView) {
+ console.log('onChange', view);
+ }
+ getFileType(filename: string) {
+ return getFileType(filename);
+ }
+ createEditor(el: HTMLElement) {
+ this.el = el;
+ const onChangeCompartment = this.onChangeCompartment!;
+ const language = this.language;
+ const extensions: Extension[] = [
+ vscodeLight,
+ formatKeymap,
+ keymap.of(defaultKeymap), //
+ // history(),
+ ];
+ if (this.autoComplete?.open) {
+ extensions.push(
+ autocompletion({
+ activateOnTyping: true, // 输入时触发补全
+ closeOnBlur: true, // 失去焦点时关闭补全
+ activateOnTypingDelay: 2000, // 输入时触发补全
+ override: this.autoComplete?.overrideList || [],
+ ...this.autoComplete?.props,
+ }),
+ );
+ }
+ if (language === 'typescript') {
+ extensions.push(javascript({ typescript: true }));
+ } else if (language === 'javascript') {
+ extensions.push(javascript());
+ } else if (language === 'markdown') {
+ extensions.push(markdown());
+ } else if (language === 'html') {
+ extensions.push(html());
+ } else if (language === 'css') {
+ extensions.push(css());
+ } else if (language === 'json') {
+ extensions.push(json());
+ } else if (language === 'yaml') {
+ extensions.push(yaml());
+ }
+
+ this.editor = new EditorView({
+ parent: el,
+ extensions: [
+ ...extensions, //
+ onChangeCompartment.of([]),
+ ],
+ });
+ }
+ resetEditor(el?: HTMLElement) {
+ this.editor?.destroy?.();
+ this.createEditor(el || (this.el as HTMLElement));
+ }
+ setLanguage(language: string, el?: HTMLElement) {
+ this.language = language;
+ this.editor?.destroy?.();
+ this.createEditor(el || (this.el as HTMLElement));
+ }
+ setContent(content: string) {
+ this.editor?.dispatch({
+ changes: { from: 0, to: this.editor?.state.doc.length, insert: content },
+ });
+ }
+ getContent() {
+ return this.editor?.state.doc.toString();
+ }
+ destroyEditor() {
+ this.editor?.destroy();
+ }
+}
+
+export default BaseEditor;
diff --git a/packages/codemirror/src/editor/modules/ai-completions.ts b/packages/codemirror/src/editor/modules/ai-completions.ts
new file mode 100644
index 0000000..7ac3fc9
--- /dev/null
+++ b/packages/codemirror/src/editor/modules/ai-completions.ts
@@ -0,0 +1,94 @@
+import { CompletionResult, CompletionSource } from '@codemirror/autocomplete';
+import { Transaction } from '@codemirror/state';
+
+export async function fetchAICompletions(context) {
+ const userInput = context.matchBefore(/\w*/);
+ if (!userInput) return null;
+
+ const docText = context.state.doc.toString(); // 获取完整文档内容
+ const cursorPos = context.pos; // 获取光标位置
+
+ // 添加提示词
+ const promptText = `请根据以下代码和光标位置提供代码补全建议:
+
+代码:
+${docText}
+
+光标位置:${cursorPos}
+
+请提供适当的代码补全。`;
+
+ const aiResponse = await fetch('http://192.168.31.220:11434/api/generate', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ prompt: promptText,
+ // 如果 Ollama 支持,可以传递其他参数,如模型名称
+ // model: 'your-model-name',
+ model: 'qwen2.5-coder:14b',
+ // model: 'qwen2.5:14b',
+ stream: false,
+ }),
+ });
+
+ const result = await aiResponse.json();
+ const data = result;
+ // 提取字段
+ const createdAt = new Date(data.created_at); // 转换为日期对象
+ const isDone = data.done;
+ const doneReason = data.done_reason;
+ const modelName = data.model;
+ const responseText = data.response;
+ const matchCodeBlock = responseText.match(/```javascript\n([\s\S]*?)\n```/);
+ let codeBlock = '';
+ if (matchCodeBlock) {
+ const codeBlock = matchCodeBlock[1]; // 提取代码块
+ console.log('补全代码:', codeBlock);
+ }
+
+ const suggestions = [
+ {
+ label: codeBlock,
+ type: 'text',
+ },
+ ];
+ return {
+ from: userInput.from,
+ options: suggestions,
+ validFor: /^\w*$/,
+ };
+}
+
+// type AiCompletion = Readonly;
+export const testAiCompletion = async (context: any): Promise => {
+ const userInput = context.matchBefore(/\w*/);
+ if (!userInput) return null;
+ const docText = context.state.doc.toString(); // 获取完整文档内容
+ const cursorPos = context.pos; // 获取光标位置
+ console.log('testAiCompletion', userInput, docText, cursorPos);
+
+ return {
+ from: userInput.from,
+ options: [
+ {
+ label: '123 测试 skflskdf ',
+ type: 'text',
+ apply: (state, completion, from, to) => {
+ console.log('apply', state, completion, from, to);
+ const newText = '123 测试 skflskdf ';
+ state.dispatch({
+ changes: { from, to, insert: newText },
+ });
+ },
+ info: '这是一个长文本上的反馈是冷酷的父母离开时的父母 这是一个长文本上的反馈是冷酷的父母离开时的父母 这是一个长文本上的反馈是冷酷的父母离开时的父母这是一个长文本上的反馈是冷酷的父母离开时的父母 这是一个长文本上的反馈是冷酷的父母离开时的父母这是一个长文本上的反馈是冷酷的父母离开时的父母 这是一个长文本上的反馈是冷酷的父母离开时的父母',
+ },
+ {
+ label: '456',
+ type: 'text',
+ },
+ ],
+ validFor: /^\w*$/,
+ };
+};
diff --git a/packages/codemirror/src/editor/modules/keymap.ts b/packages/codemirror/src/editor/modules/keymap.ts
new file mode 100644
index 0000000..c6d48ff
--- /dev/null
+++ b/packages/codemirror/src/editor/modules/keymap.ts
@@ -0,0 +1,92 @@
+import { EditorView, keymap } from '@codemirror/view';
+import { defaultKeymap, indentSelection, indentWithTab, undo, history } from '@codemirror/commands';
+import { indentUnit } from '@codemirror/language';
+import prettier from 'prettier';
+// import parserBabel from 'prettier/plugins/babel';
+import parserEstree from 'prettier/plugins/estree';
+// import parserHtml from 'prettier/plugins/html';
+import parserTypescript from 'prettier/plugins/typescript';
+import { autocompletion, CompletionContext } from '@codemirror/autocomplete';
+
+// 格式化函数
+// Function to format the code using Prettier
+async function formatCode(view: EditorView) {
+ const editor = view;
+ const code = editor.state.doc.toString();
+ try {
+ const formattedCode = await prettier.format(code, {
+ parser: 'typescript',
+ plugins: [parserEstree, parserTypescript],
+ });
+
+ editor.dispatch({
+ changes: {
+ from: 0,
+ to: editor.state.doc.length,
+ insert: formattedCode.trim(),
+ },
+ });
+ } catch (error) {
+ console.error('Error formatting code:', error);
+ }
+}
+// export const TabFn = (view: EditorView) => {
+// const { state } = view;
+// const selection = state.selection.main;
+// // 判断是否存在补全上下文
+// if (state.field(autocompletion).active?.length) {
+// // 插入当前选中的补全内容
+// const active = state.field(autocompletion).active[0];
+// const selected = active?.options[active.selected]?.label;
+// if (selected) {
+// view.dispatch({
+// changes: { from: selection.from, to: selection.to, insert: selected },
+// });
+// return true;
+// }
+// }
+// return false;
+// };
+
+// 自定义 Tab 键行为
+function customIndentWithTab(view: EditorView) {
+ const { state, dispatch } = view;
+ const { from, to } = state.selection.main;
+ const _indentUnit = state.facet(indentUnit) as string;
+
+ // 计算插入的缩进
+ const indent = _indentUnit.repeat(1);
+
+ dispatch({
+ changes: { from, to, insert: indent },
+ selection: { anchor: from + indent.length },
+ });
+
+ return true; // 表示按键事件被处理
+}
+export const formatKeymap = keymap.of([
+ {
+ // bug, 必须小写
+ key: 'alt-shift-f', // 快捷键绑定
+ // mac: 'cmd-shift-f',
+ run: (view) => {
+ formatCode(view);
+ return true; // 表示按键事件被处理
+ },
+ },
+ {
+ key: 'Tab',
+ run: customIndentWithTab, // 使用自定义的缩进函数
+ },
+ {
+ key: 'Ctrl-z', // Windows 撤销快捷键
+ // key: 'Alt-z',
+ mac: 'Cmd-z', // Mac 撤销快捷键
+ run: (view) => {
+ console.log('undo', view);
+ return undo(view); // 表示按键事件被处理
+ },
+ },
+ ...defaultKeymap, // 默认快捷键
+]);
+// console.log('formatKeymap', defaultKeymap);
diff --git a/packages/codemirror/src/editor/utils/get-file-type.ts b/packages/codemirror/src/editor/utils/get-file-type.ts
new file mode 100644
index 0000000..87de5e9
--- /dev/null
+++ b/packages/codemirror/src/editor/utils/get-file-type.ts
@@ -0,0 +1,31 @@
+export const getExtension = (filename: string) => {
+ const extension = filename.split('.').pop();
+ if (!extension) return '';
+ return extension;
+};
+export const supportedExtensions = ['ts', 'js', 'md', 'json', 'yaml', 'yml', 'css', 'html'];
+
+export function getFileType(filename: string) {
+ const extension = getExtension(filename);
+ if (!supportedExtensions.includes(extension)) return '';
+ switch (extension) {
+ case 'ts':
+ return 'typescript';
+ case 'js':
+ return 'javascript';
+ case 'md':
+ return 'markdown';
+ case 'json':
+ return 'json';
+ case 'yaml':
+ return 'yaml';
+ case 'yml':
+ return 'yaml';
+ case 'css':
+ return 'css';
+ case 'html':
+ return 'html';
+ default:
+ return '';
+ }
+}
diff --git a/packages/codemirror/src/global.css b/packages/codemirror/src/global.css
new file mode 100644
index 0000000..5920072
--- /dev/null
+++ b/packages/codemirror/src/global.css
@@ -0,0 +1,3 @@
+@import 'tailwindcss';
+@import '@kevisual/center-components/theme/wind-theme.css';
+@import './style.css';
diff --git a/packages/codemirror/src/main.ts b/packages/codemirror/src/main.ts
new file mode 100644
index 0000000..aca5a4a
--- /dev/null
+++ b/packages/codemirror/src/main.ts
@@ -0,0 +1,4 @@
+import { render } from './pages/Bootstrap';
+import './styles.css';
+
+render('#ai-root');
diff --git a/packages/codemirror/src/pages/App.tsx b/packages/codemirror/src/pages/App.tsx
new file mode 100644
index 0000000..f3c823d
--- /dev/null
+++ b/packages/codemirror/src/pages/App.tsx
@@ -0,0 +1,40 @@
+import { useMemo } from 'react';
+import { CustomThemeProvider } from '@kevisual/center-components/theme/index.tsx';
+import { ToastContainer } from 'react-toastify';
+import { FileEditor } from './file-editor/FileEditor';
+
+export const InitProvider = ({ children }: { children: React.ReactNode }) => {
+ return <>{children}>;
+};
+
+type AppProvider = {
+ key: string;
+ use: boolean;
+};
+export type AppProps = {
+ providers?: AppProvider[];
+ noProvider?: boolean;
+ /**
+ * 是否是单个应用
+ * 默认是单个应用模块。
+ */
+ isSingleApp?: boolean;
+};
+export const App = ({ providers, noProvider, isSingleApp = true }: AppProps) => {
+ const children = useMemo(() => {
+ return (
+
+
+
+ );
+ }, []);
+ if (noProvider) {
+ return <>{children}>;
+ }
+ return (
+
+ {children}
+
+
+ );
+};
diff --git a/packages/codemirror/src/pages/Bootstrap.tsx b/packages/codemirror/src/pages/Bootstrap.tsx
new file mode 100644
index 0000000..87ba14c
--- /dev/null
+++ b/packages/codemirror/src/pages/Bootstrap.tsx
@@ -0,0 +1,88 @@
+import { createRoot } from 'react-dom/client';
+import { App, AppProps } from './App';
+import React from 'react';
+
+export class ReactRenderer {
+ component: any;
+ element: HTMLElement;
+ ref: React.RefObject;
+ props: any;
+ root: any;
+ constructor(component: any, { props, className }: any) {
+ this.component = component;
+ const el = document.createElement('div');
+ this.element = el;
+ this.ref = React.createRef();
+ this.props = {
+ ...props,
+ ref: this.ref,
+ };
+ el.className = className;
+ this.root = createRoot(this.element);
+ this.render();
+ }
+
+ updateProps(props: any) {
+ this.props = {
+ ...this.props,
+ ...props,
+ };
+ this.render();
+ }
+
+ render() {
+ this.root.render(React.createElement(this.component, this.props));
+ }
+
+ destroy() {
+ this.root.unmount();
+ }
+}
+
+export default ReactRenderer;
+export const randomId = () => {
+ return Math.random().toString(36).substring(2, 15);
+};
+export const render = (el: HTMLElement | string, props?: AppProps) => {
+ const root = typeof el === 'string' ? document.querySelector(el) : el;
+ if (!root) {
+ console.error('root not found');
+ return;
+ }
+ const hasResourceApp = window.context?.codemirrorApp;
+ if (hasResourceApp) {
+ const render = hasResourceApp as ReactRenderer;
+ render.updateProps({
+ props: { t: randomId(), ...props },
+ });
+ root.innerHTML = '';
+ root.appendChild(render.element);
+ } else {
+ const renderer = new ReactRenderer(App, {
+ props: { ...props },
+ className: 'codemirror-root w-full h-full',
+ });
+ if (window.context) {
+ window.context.codemirrorApp = renderer;
+ } else {
+ window.context = {
+ codemirrorApp: renderer,
+ };
+ }
+ root.appendChild(renderer.element);
+ }
+};
+
+export const unmount = (el: HTMLElement | string) => {
+ const root = typeof el === 'string' ? document.querySelector(el) : el;
+ if (!root) {
+ console.error('root not found');
+ return;
+ }
+ const hasResourceApp = window.context?.codemirrorApp;
+ if (hasResourceApp) {
+ const render = hasResourceApp as ReactRenderer;
+ render.destroy();
+ window.context.codemirrorApp = null;
+ }
+};
diff --git a/packages/codemirror/src/pages/file-editor/FileEditor.tsx b/packages/codemirror/src/pages/file-editor/FileEditor.tsx
new file mode 100644
index 0000000..14ca28f
--- /dev/null
+++ b/packages/codemirror/src/pages/file-editor/FileEditor.tsx
@@ -0,0 +1,17 @@
+import { useEffect, useRef } from 'react';
+import { BaseEditor } from '../../editor/editor';
+
+export const FileEditor = () => {
+ const containerRef = useRef(null);
+ useEffect(() => {
+ const editor = new BaseEditor();
+ if (containerRef.current) {
+ editor.createEditor(containerRef.current);
+ }
+ return () => {
+ editor.destroyEditor();
+ };
+ }, []);
+
+ return ;
+};
diff --git a/packages/codemirror/src/style.css b/packages/codemirror/src/style.css
new file mode 100644
index 0000000..a7e8068
--- /dev/null
+++ b/packages/codemirror/src/style.css
@@ -0,0 +1,3 @@
+.cm-editor {
+ height: 100%;
+}
\ No newline at end of file
diff --git a/packages/codemirror/tsconfig.json b/packages/codemirror/tsconfig.json
new file mode 100644
index 0000000..b63871f
--- /dev/null
+++ b/packages/codemirror/tsconfig.json
@@ -0,0 +1,39 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": [
+ "ES2020",
+ "DOM",
+ "DOM.Iterable"
+ ],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "baseUrl": "./",
+ "typeRoots": [
+ "node_modules/@types",
+ "node_modules/@kevisual/types",
+ ],
+ "paths": {
+ "@kevisual/codemirror/*": [
+ "src/*"
+ ]
+ },
+ /* Linting */
+ "strict": true,
+ "noImplicitAny": false,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": [
+ "src",
+ ]
+}
\ No newline at end of file
diff --git a/packages/codemirror/vite.config.mjs b/packages/codemirror/vite.config.mjs
new file mode 100644
index 0000000..851041b
--- /dev/null
+++ b/packages/codemirror/vite.config.mjs
@@ -0,0 +1,83 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import path from 'path';
+import tailwindcss from '@tailwindcss/vite';
+import basicSsl from '@vitejs/plugin-basic-ssl';
+
+const plugins = [basicSsl()];
+// const plugins = [];
+plugins.push(tailwindcss());
+let proxy = {};
+if (true) {
+ proxy = {
+ '/api': {
+ target: 'https://kevisual.silkyai.cn',
+ changeOrigin: true,
+ ws: true,
+ cookieDomainRewrite: 'localhost',
+ rewrite: (path) => path.replace(/^\/api/, '/api'),
+ },
+ '/api/router': {
+ target: 'wss://kevisual.silkyai.cn',
+ changeOrigin: true,
+ ws: true,
+ rewriteWsOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, '/api'),
+ },
+ '/user/login': {
+ target: 'https://kevisual.silkyai.cn',
+ changeOrigin: true,
+ cookieDomainRewrite: 'localhost',
+ rewrite: (path) => path.replace(/^\/user/, '/user'),
+ },
+ };
+}
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react(), ...plugins],
+
+ css: {
+ postcss: {},
+ },
+ resolve: {
+ alias: {
+ '@kevisual/codemirror': path.resolve(__dirname, './src'),
+ },
+ },
+ define: {
+ DEV_SERVER: JSON.stringify(process.env.NODE_ENV === 'development'),
+ BASE_NAME: JSON.stringify('/root/codemirror/'),
+ },
+ base: './',
+ // base: isDev ? '/' : '/root/codemirror/',
+ build: {
+ rollupOptions: {
+ // external: ['react', 'react-dom'],
+ },
+ },
+ server: {
+ port: 6022,
+ host: '0.0.0.0',
+ proxy: {
+ '/system/lib': {
+ target: 'https://kevisual.xiongxiao.me',
+ changeOrigin: true,
+ },
+ '/api': {
+ target: 'http://localhost:4005',
+ changeOrigin: true,
+ ws: true,
+ cookieDomainRewrite: 'localhost',
+ rewrite: (path) => path.replace(/^\/api/, '/api'),
+ },
+ '/api/router': {
+ target: 'ws://localhost:4005',
+ changeOrigin: true,
+ ws: true,
+ rewriteWsOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, '/api'),
+ },
+ ...proxy,
+ },
+ },
+});
diff --git a/packages/components/package.json b/packages/components/package.json
index 5787025..e97eef8 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -17,7 +17,8 @@
"@emotion/styled": "^11.14.0",
"@mui/material": "^6.4.7",
"react": "19.0.0",
- "react-dom": "19.0.0"
+ "react-dom": "19.0.0",
+ "react-hook-form": "^7.54.2"
},
"exports": {
".": "./src/index.tsx",
diff --git a/packages/components/src/input/index.tsx b/packages/components/src/input/index.tsx
new file mode 100644
index 0000000..2d1fc81
--- /dev/null
+++ b/packages/components/src/input/index.tsx
@@ -0,0 +1,47 @@
+import { FormControlLabel, TextField } from '@mui/material';
+import { useForm, Controller } from 'react-hook-form';
+export const InputControl = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => {
+ return (
+ onChange?.(e.target.value)}
+ sx={{
+ width: '100%',
+ marginBottom: '16px',
+ }}
+ />
+ );
+};
+type FormProps = {
+ onSubmit?: (data: any) => void;
+ children?: React.ReactNode;
+};
+export const FormDemo = (props: FormProps) => {
+ const { control, handleSubmit } = useForm();
+ const { onSubmit = () => {}, children } = props;
+
+ return (
+
+ );
+};
diff --git a/packages/components/src/modal/Confirm.tsx b/packages/components/src/modal/Confirm.tsx
new file mode 100644
index 0000000..099bef9
--- /dev/null
+++ b/packages/components/src/modal/Confirm.tsx
@@ -0,0 +1,91 @@
+import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button } from '@mui/material';
+import { useRef, useState } from 'react';
+
+export const Confirm = ({
+ open,
+ onClose,
+ title,
+ content,
+ onConfirm,
+ confirmText = '确认',
+ cancelText = '取消',
+}: {
+ open: boolean;
+ onClose: () => void;
+ title: string;
+ content: string;
+ onConfirm?: () => void;
+ confirmText?: string;
+ cancelText?: string;
+}) => {
+ return (
+
+ );
+};
+
+type Fn = () => void;
+export const useConfirm = () => {
+ const [open, setOpen] = useState(false);
+ const [title, setTitle] = useState('');
+ const [content, setContent] = useState('');
+ const fns = useRef<{
+ onConfirm: Fn;
+ onCancel: Fn;
+ confirmText: string;
+ cancelText: string;
+ }>({
+ onConfirm: () => {},
+ onCancel: () => {},
+ confirmText: '确认',
+ cancelText: '取消',
+ });
+ return {
+ contextHolder: (
+ {
+ setOpen(false);
+ fns.current.onCancel();
+ }}
+ title={title}
+ content={content}
+ onConfirm={fns.current.onConfirm}
+ />
+ ),
+ confirm: (
+ title: string,
+ content: string,
+ opts?: {
+ onConfirm: () => void;
+ confirmText?: string;
+ cancelText?: string;
+ onCancel?: () => void;
+ },
+ ) => {
+ setOpen(true);
+ setTitle(title);
+ setContent(content);
+ fns.current.onConfirm = opts?.onConfirm || (() => {});
+ fns.current.onCancel = opts?.onCancel || (() => {});
+ fns.current.confirmText = opts?.confirmText || '确认';
+ fns.current.cancelText = opts?.cancelText || '取消';
+ },
+ };
+};
diff --git a/packages/components/src/theme/wind-theme.css b/packages/components/src/theme/wind-theme.css
index f190af2..c15e3cd 100644
--- a/packages/components/src/theme/wind-theme.css
+++ b/packages/components/src/theme/wind-theme.css
@@ -5,4 +5,67 @@
--color-secondary: #ffa000;
--color-success: #28a745;
--scrollbar-color: #ffc107; /* 滚动条颜色 */
-}
\ No newline at end of file
+}
+
+html,
+body {
+ width: 100%;
+ height: 100%;
+ font-size: 16px;
+ font-family: 'Montserrat', sans-serif;
+}
+/* font-family */
+@utility font-family-mon {
+ font-family: 'Montserrat', sans-serif;
+}
+@utility font-family-rob {
+ font-family: 'Roboto', sans-serif;
+}
+@utility font-family-int {
+ font-family: 'Inter', sans-serif;
+}
+@utility font-family-orb {
+ font-family: 'Orbitron', sans-serif;
+}
+@utility font-family-din {
+ font-family: 'DIN', sans-serif;
+}
+
+@utility flex-row-center {
+ @apply flex flex-row items-center justify-center;
+}
+@utility flex-col-center {
+ @apply flex flex-col items-center justify-center;
+}
+
+@utility scrollbar {
+ overflow: auto;
+ /* 整个滚动条 */
+ &::-webkit-scrollbar {
+ width: 3px;
+ height: 3px;
+ }
+
+ /* 滚动条有滑块的轨道部分 */
+ &::-webkit-scrollbar-track-piece {
+ background-color: transparent;
+ border-radius: 1px;
+ }
+
+ /* 滚动条滑块(竖向:vertical 横向:horizontal) */
+ &::-webkit-scrollbar-thumb {
+ cursor: pointer;
+ background-color: #c1c1c1;
+ border-radius: 5px;
+ }
+
+ /* 滚动条滑块hover */
+ &::-webkit-scrollbar-thumb:hover {
+ background-color: #999999;
+ }
+
+ /* 同时有垂直和水平滚动条时交汇的部分 */
+ &::-webkit-scrollbar-corner {
+ display: block; /* 修复交汇时出现的白块 */
+ }
+}
diff --git a/packages/resources/index.html b/packages/resources/index.html
index f0a0768..2df0f35 100644
--- a/packages/resources/index.html
+++ b/packages/resources/index.html
@@ -3,7 +3,7 @@
AI Apps
-
+