This commit is contained in:
神仙都没用 2024-06-27 18:45:40 +08:00
parent 129d401d64
commit 2eea120fe8
4 changed files with 2030 additions and 690 deletions

1772
build/cool/eps.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
export const proxy = { export const proxy = {
"/dev/": { "/dev/": {
// target: "http://127.0.0.1:8001", target: "http://127.0.0.1:9009",
// target: "http://192.168.0.112:9009", // target: "http://192.168.0.112:9009",
target: "https://dev-admin.cool-js.cloud", // target: "https://dev-admin.cool-js.cloud",
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/dev/, "") rewrite: (path: string) => path.replace(/^\/dev/, "")
}, },

View File

@ -1,5 +1,4 @@
<template> <template>
<el-scrollbar :ref="setRefs('scrollbar')">
<div class="ai-code"> <div class="ai-code">
<div class="bg"> <div class="bg">
<div class="a"></div> <div class="a"></div>
@ -65,7 +64,6 @@
</div> </div>
<div class="content"> <div class="content">
<template v-if="step.value == 'form'">
<div class="row"> <div class="row">
<div class="label"> <div class="label">
实体名称 实体名称
@ -80,11 +78,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
<el-input <el-input v-model="form.entity" maxlength="20" placeholder="请输入">
v-model="form.entity"
maxlength="20"
placeholder="请输入"
>
<template #prefix> <template #prefix>
<el-icon> <el-icon>
<arrow-right-bold /> <arrow-right-bold />
@ -107,11 +101,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
<el-input <el-input v-model="form.module" maxlength="20" placeholder="请输入">
v-model="form.module"
maxlength="20"
placeholder="请输入"
>
<template #prefix> <template #prefix>
<el-popover <el-popover
:ref="setRefs('modulePopover')" :ref="setRefs('modulePopover')"
@ -162,11 +152,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
<el-input <el-input v-model="form.column" maxlength="200" placeholder="请输入">
v-model="form.column"
maxlength="200"
placeholder="请输入"
>
<template #prefix> <template #prefix>
<el-icon class="icon"> <el-icon class="icon">
<arrow-right-bold /> <arrow-right-bold />
@ -189,11 +175,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
<el-input <el-input v-model="form.other" maxlength="200" placeholder="请输入">
v-model="form.other"
maxlength="200"
placeholder="请输入"
>
<template #prefix> <template #prefix>
<el-icon> <el-icon>
<arrow-right-bold /> <arrow-right-bold />
@ -201,7 +183,6 @@
</template> </template>
</el-input> </el-input>
</div> </div>
</template>
</div> </div>
</div> </div>
@ -231,13 +212,6 @@
<span></span> <span></span>
<span></span> <span></span>
</div> </div>
<div class="print">
<el-icon class="is-loading">
<refresh />
</el-icon>
<span>生成 {{ codeData?.label }}代码中</span>
</div>
</div> </div>
<div class="content"> <div class="content">
@ -249,29 +223,57 @@
:class="{ :class="{
active: code.active == item.value active: code.active == item.value
}" }"
@click="
() => {
code.active = item.value;
}
"
> >
{{ item.label }} {{ item.label }}
</div> </div>
<div class="op"> <div class="op" v-if="!isEmpty(code.tabs) && !code.loading">
<el-icon> <el-tooltip content="创建文件">
<el-icon @click="createFile">
<download /> <download />
</el-icon> </el-icon>
</el-tooltip>
</div> </div>
</div> </div>
<div class="code"> <div class="code">
<cl-editor-monaco <cl-editor-monaco
:ref="setRefs('editor')"
v-model="codeData.content"
height="100%" height="100%"
:border="false" :border="false"
:options="{ :options="{
theme: 'ai-code--dark' theme: 'ai-code--dark'
}" }"
v-model="codeData.content" :key="codeData.value"
language="typescript" :language="codeData.value == 'vue' ? 'html' : 'typescript'"
v-if="codeData" v-if="codeData"
/> />
</div> </div>
<div class="console">
<el-scrollbar :ref="setRefs('console.scrollbar')">
<div class="item" v-for="(item, index) in code.logs" :key="index">
<span class="date"> {{ item.date }} </span>
<span class="text">
{{ item.text }}
</span>
<el-icon
class="is-loading"
v-if="code.loading ? index == code.logs.length - 1 : false"
>
<loading />
</el-icon>
</div>
</el-scrollbar>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -280,14 +282,12 @@
<!-- 创建菜单 --> <!-- 创建菜单 -->
<cl-form ref="Form" /> <cl-form ref="Form" />
</div> </div>
</el-scrollbar>
</template> </template>
<script lang="tsx" setup name="helper-ai-code"> <script lang="tsx" setup name="helper-ai-code">
import { onMounted, reactive, watch, nextTick, computed } from "vue"; import { onMounted, reactive, computed } from "vue";
import { useCool, storage, module } from "/@/cool"; import { useCool, module } from "/@/cool";
import { import {
Refresh,
Download, Download,
Back, Back,
ArrowRightBold, ArrowRightBold,
@ -295,18 +295,17 @@ import {
CirclePlusFilled, CirclePlusFilled,
QuestionFilled QuestionFilled
} from "@element-plus/icons-vue"; } from "@element-plus/icons-vue";
import { ElLoading, ElMessage, ElMessageBox } from "element-plus"; import { ElLoading, ElMessage } from "element-plus";
import { debounce, isEmpty, last } from "lodash-es"; import { assign, isEmpty } from "lodash-es";
import { useClipboard } from "@vueuse/core";
import { useMenu, useAi } from "../hooks"; import { useMenu, useAi } from "../hooks";
import { isDev } from "/@/config"; import { isDev } from "/@/config";
import { useForm } from "@cool-vue/crud"; import { useForm } from "@cool-vue/crud";
import type { CodeType } from "../types";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import { sleep } from "/@/cool/utils"; import { sleep } from "/@/cool/utils";
import dayjs from "dayjs";
import { nextTick } from "vue";
const { service, refs, setRefs, router } = useCool(); const { service, refs, setRefs, router } = useCool();
const { copy } = useClipboard();
const menu = useMenu(); const menu = useMenu();
const ai = useAi(); const ai = useAi();
const Form = useForm(); const Form = useForm();
@ -333,7 +332,7 @@ const form = reactive({
// //
const step = reactive({ const step = reactive({
loading: false, loading: false,
value: "form", value: "start",
list: ["start", "enter", "form", "coding"], list: ["start", "enter", "form", "coding"],
async next() { async next() {
@ -384,6 +383,9 @@ const code = reactive({
tabs: [] as { label: string; value: string; content: string }[], tabs: [] as { label: string; value: string; content: string }[],
active: "", active: "",
//
logs: [] as any[],
// //
loading: false, loading: false,
@ -401,6 +403,23 @@ const code = reactive({
// //
clear() { clear() {
code.tabs = []; code.tabs = [];
code.logs = [];
},
//
tips(val?: string) {
code.logs.push({
date: dayjs().format("HH:mm:ss"),
text: val
});
//
nextTick(() => {
refs["console.scrollbar"]?.wrapRef?.scrollTo({
top: Math.random() + 10000,
behavior: "smooth"
});
});
}, },
// //
@ -427,65 +446,190 @@ const code = reactive({
await sleep(300); await sleep(300);
code.tips("Entity 代码生成中");
// entity // entity
const entity = await code.setContent("Entity 实体", "node-entity"); const entity = await code.setContent("Entity 实体", "node-entity");
code.tips("Entity 生成成功,开始解析");
// entity // entity
const entityData = await ai.invokeFlow("comm-parse-entity", { const entityData = await ai.invokeFlow("comm-parse-entity", {
entity entity
}); });
code.tips(`Entity 解析成功,${JSON.stringify(entityData)}`);
code.tips("Service 代码生成中");
// service // service
const service = await code.setContent("Service 服务层", "node-service", { const service = await code.setContent("Service 服务层", "node-service", {
...entityData ...entityData
}); });
code.tips("Service 生成成功,开始解析");
// service // service
const serviceData = await ai.invokeFlow("comm-parse-service", { const serviceData = await ai.invokeFlow("comm-parse-service", {
service service
}); });
code.tips(`Service 解析成功,${JSON.stringify(serviceData)}`);
code.tips("Controller 代码生成中");
// controller // controller
await code.setContent("Controller 控制器", "node-controller", { await code.setContent("Controller 控制器", "node-controller", {
...serviceData, ...serviceData,
...entityData ...entityData
}); });
code.tips("Controller 生成成功");
await code.createVue();
code.tips("编码完成");
code.loading = false; code.loading = false;
}, },
// vue
async createVue() {
const data = {
...form,
router: "",
prefix: "",
path: "",
module: "",
fileName: "",
className: "",
other: "",
columns: [],
api: [
{
path: "/add",
summary: "新增"
},
{
path: "/info",
summary: "单个信息"
},
{
path: "/update",
summary: "修改"
},
{
path: "/delete",
summary: "删除"
},
{
path: "/page",
summary: "分页查询"
},
{
path: "/list",
summary: "列表查询"
}
]
};
const item = code.add("Vue 页面", "vue");
code.tips("Vue 代码生成中");
//
await service.base.sys.menu
.parse({
module: form.module,
entity: code.getContent("node-entity")
})
.then((res) => {
assign(data, res);
data.router = res.path.replace("/admin", "");
data.prefix = res.path;
});
code.tips("AI 字段分析中");
// ai
await ai.matchType({ columns: data.columns, name: form.entity });
//
item.content = menu.createVue(data);
code.tips("Vue 生成成功");
await sleep(300);
//
refs.editor.formatCode();
},
// tab
add(label: string, flow: string) {
const item = reactive({
label,
value: flow,
content: "",
_content: ""
});
code.active = flow;
code.tabs.push(item);
return item;
},
//
getContent(value: string) {
return code.tabs.find((e) => e.value == value)?.content;
},
// //
async setContent(label: string, flow: string, data?: any) { async setContent(label: string, flow: string, data?: any) {
return new Promise((resolve) => { return new Promise((resolve) => {
const item = { const item = code.add(label, flow);
label,
value: flow,
content: ""
};
code.active = flow; //
code.tabs.push(item); let isEnd = false;
//
let content = ""; let content = "";
ai.invokeFlow(flow, { ...form, ...data }, (res) => { ai.invokeFlow(flow, { ...form, ...data }, (res) => {
if (res.isEnd) { isEnd = res.isEnd;
resolve(item.content);
} else { if (!res.isEnd) {
content += res.content; content += res.content;
if (content.indexOf("```typescript\n") == 0) { if (content.indexOf("```typescript\n") == 0) {
item.content = content.replace(/^```typescript\n/g, "").replace(/```$/, ""); item._content = content
.replace(/^```typescript\n/g, "")
.replace(/```$/, "");
} }
} }
}); });
});
},
// const timer = setInterval(() => {
copy(k: CodeType) { const v = item._content[item.content.length] || "";
copy(codes[k]);
ElMessage.success("复制成功"); if (isEnd) {
if (!v) {
clearInterval(timer);
resolve(item.content);
return false;
}
}
item.content += v;
//
if (flow == code.active) {
refs.editor.revealLine(99999);
}
}, 10);
});
} }
}); });
@ -579,107 +723,6 @@ const desc = reactive({
} }
}); });
//
const scroller = {
timer: null as any,
scrollTo({ el, top }: { el?: string; top?: number }) {
const scrollbar = refs.scrollbar.wrapRef;
if (el) {
top = scrollbar.querySelector(el).offsetTop;
}
scrollbar.scrollTo({
top,
behavior: "smooth"
});
},
start() {
this.stop();
this.timer = setInterval(() => {
if (codes.entity) {
this.scrollTo({
top: Math.random() + 10000
});
}
}, 100);
},
stop() {
if (this.timer) {
clearInterval(this.timer);
}
}
};
//
const temp = reactive({
disabled: false,
coding: "" as "" | CodeType,
data: {
router: "",
prefix: "",
path: "",
module: "",
fileName: "",
className: "",
other: "",
columns: [],
api: []
},
api: [
{
path: "/add",
summary: "新增"
},
{
path: "/info",
summary: "单个信息"
},
{
path: "/update",
summary: "修改"
},
{
path: "/delete",
summary: "删除"
},
{
path: "/page",
summary: "分页查询"
},
{
path: "/list",
summary: "列表查询"
}
]
});
//
const codes = reactive<{ [key: string]: string }>({
entity: "",
controller: "",
vue: ""
});
//
function stop() {
temp.disabled = false;
temp.coding = "";
scroller.stop();
}
//
function reset() {
stop();
codes.entity = "";
codes.controller = "";
codes.vue = "";
}
// //
function createFile() { function createFile() {
if (!isDev) { if (!isDev) {
@ -787,60 +830,6 @@ function createFile() {
}); });
} }
// vue
const createVue = debounce((auto?: boolean) => {
async function next() {
if (codes.entity) {
// ai
await ai.matchType({ columns: temp.data.columns, name: form.entity });
//
codes.vue = menu.createVue(temp.data);
stop();
setTimeout(() => {
scroller.scrollTo({ el: `.codes .is-vue` });
refs.codeVue.formatCode();
}, 300);
}
}
if (auto) {
next();
} else {
ElMessageBox.confirm("此操作将会重新生成vue代码是否继续", "提示", {
type: "warning"
})
.then(async () => {
temp.coding = "vue";
codes.vue = "";
await parseEntity();
next();
})
.catch(() => null);
}
}, 300);
//
async function parseEntity() {
await service.base.sys.menu
.parse({
entity: codes.entity,
module: form.module
})
.then((res) => {
temp.data = {
...form,
...res,
router: res.path.replace("/admin", ""),
prefix: res.path,
api: temp.api
};
});
}
// //
function toDoc() { function toDoc() {
window.open("https://cool-js.com/"); window.open("https://cool-js.com/");
@ -857,6 +846,7 @@ $color: #41d1ff;
.ai-code { .ai-code {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
align-items: center; align-items: center;
position: relative; position: relative;
height: 100vh; height: 100vh;
@ -948,7 +938,7 @@ $color: #41d1ff;
position: relative; position: relative;
height: 100%; height: 100%;
width: 1040px; width: 1040px;
max-width: calc(100% - 40px); max-width: 100%;
.editor { .editor {
background-color: #080e14; background-color: #080e14;
@ -1074,6 +1064,7 @@ $color: #41d1ff;
height: 50px; height: 50px;
text-align: center; text-align: center;
margin: 0 auto; margin: 0 auto;
flex-shrink: 0;
.el-button { .el-button {
height: 40px; height: 40px;
@ -1099,7 +1090,6 @@ $color: #41d1ff;
margin: 0 auto; margin: 0 auto;
position: relative; position: relative;
animation: enter 0.3s forwards; animation: enter 0.3s forwards;
overflow: hidden;
width: 10px; width: 10px;
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
@ -1244,15 +1234,16 @@ $color: #41d1ff;
} }
.coding { .coding {
position: absolute; position: fixed;
bottom: 20px; bottom: 0;
left: 0; left: 0;
transition: all 0.3s ease; height: 100vh;
height: 0;
width: 100%; width: 100%;
animation: coding 0.3s forwards; animation: coding 0.3s forwards;
border: 5px solid rgba(255, 255, 255, 0.1); border-top: 5px solid rgba(255, 255, 255, 0.1);
box-sizing: border-box; box-sizing: border-box;
opacity: 0;
transform: translateY(10vh);
.editor { .editor {
height: 100%; height: 100%;
@ -1271,28 +1262,15 @@ $color: #41d1ff;
} }
} }
} }
.print {
display: flex;
align-items: center;
margin-left: auto;
color: #fff;
font-size: 12px;
.el-icon {
margin-right: 5px;
font-size: 15px;
}
}
} }
.content { .content {
height: calc(100% - 36px); height: calc(100% - 36px);
background-color: #080e14;
.tabs { .tabs {
display: flex; display: flex;
height: 40px; height: 40px;
background-color: #080e14;
.item { .item {
display: flex; display: flex;
@ -1335,24 +1313,44 @@ $color: #41d1ff;
} }
.code { .code {
height: calc(100% - 40px); height: calc(100% - 190px);
}
.console {
height: 150px;
padding: 5px 0;
box-sizing: border-box;
.item {
font-size: 12px;
padding: 5px 10px;
color: #fff;
.date {
margin-right: 5px;
color: #ccc;
}
.el-icon {
margin: 0 5px;
font-size: 14px;
position: relative;
top: 3px;
}
}
} }
} }
} }
@keyframes coding { @keyframes coding {
from { from {
height: 0; opacity: 0;
transform: translateY(10vh);
} }
to { to {
height: 75vh; opacity: 1;
} transform: translateY(0);
}
&.is-coding {
.head {
transform: translateY(-180px);
} }
} }
} }

View File

@ -101,6 +101,11 @@ function appendContent(text: string = "") {
} }
} }
//
function revealLine(val: number) {
editor?.revealLine(val);
}
// //
async function formatCode() { async function formatCode() {
await editor?.getAction("editor.action.formatDocument")?.run(); await editor?.getAction("editor.action.formatDocument")?.run();
@ -190,7 +195,8 @@ defineExpose({
editor, editor,
setContent, setContent,
appendContent, appendContent,
formatCode formatCode,
revealLine
}); });
</script> </script>