update
Some checks failed
Deploy / build-and-deploy (push) Has been cancelled

This commit is contained in:
2025-10-22 00:18:27 +08:00
parent 64eeffd041
commit 9164fff6c2
9 changed files with 345 additions and 394 deletions

View File

@@ -32,7 +32,6 @@ export const status = ref(Status.NotStarted)
export const loading = ref(false)
export const turtleRunId = ref(0)
export const size = ref(0)
export const debug = ref(false)
watch(size, (value: number) => {
cache.fontsize.value = value

View File

@@ -0,0 +1,172 @@
<script lang="ts" setup>
import { marked } from "marked"
interface Props {
analysis: string
loading: boolean
}
defineProps<Props>()
</script>
<template>
<n-spin :show="loading">
<div class="analysisPanel" v-html="marked.parse(analysis)"></div>
</n-spin>
</template>
<style scoped>
.analysisPanel {
width: 400px;
min-height: 60px;
max-height: calc(100vh - 200px);
overflow: auto;
padding: 16px;
border-radius: 8px;
line-height: 1.6;
color: #374151;
}
/* 简洁 Markdown 样式 */
.analysisPanel :deep(h1),
.analysisPanel :deep(h2),
.analysisPanel :deep(h3),
.analysisPanel :deep(h4),
.analysisPanel :deep(h5),
.analysisPanel :deep(h6) {
margin: 16px 0 8px 0;
font-weight: 600;
color: #1f2937;
}
.analysisPanel :deep(h1) {
font-size: 1.5em;
border-bottom: 2px solid #e5e7eb;
padding-bottom: 8px;
}
.analysisPanel :deep(h2) {
font-size: 1.3em;
color: #374151;
}
.analysisPanel :deep(h3) {
font-size: 1.1em;
color: #4b5563;
}
.analysisPanel :deep(p) {
margin: 12px 0;
color: #374151;
font-size: 14px;
}
.analysisPanel :deep(ul),
.analysisPanel :deep(ol) {
margin: 12px 0;
padding-left: 20px;
}
.analysisPanel :deep(li) {
margin: 4px 0;
color: #374151;
}
.analysisPanel :deep(strong) {
font-weight: 600;
color: #1f2937;
}
.analysisPanel :deep(em) {
font-style: italic;
color: #6b7280;
}
/* 简洁代码块样式 */
.analysisPanel :deep(pre) {
background: #f8f9fa;
color: #24292f;
padding: 16px;
border-radius: 6px;
margin: 12px 0;
overflow-x: auto;
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
font-size: 13px;
line-height: 1.5;
border: 1px solid #d0d7de;
}
.analysisPanel :deep(code) {
background: #f1f3f4;
color: #d63384;
padding: 2px 6px;
border-radius: 3px;
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
font-size: 0.9em;
}
.analysisPanel :deep(pre code) {
background: transparent;
color: inherit;
padding: 0;
border-radius: 0;
}
/* 简洁引用块样式 */
.analysisPanel :deep(blockquote) {
border-left: 4px solid #d0d7de;
background: #f6f8fa;
margin: 16px 0;
padding: 12px 16px;
border-radius: 0 6px 6px 0;
color: #656d76;
font-style: italic;
}
/* 简洁表格样式 */
.analysisPanel :deep(table) {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
background: #ffffff;
border-radius: 6px;
overflow: hidden;
border: 1px solid #d0d7de;
}
.analysisPanel :deep(th),
.analysisPanel :deep(td) {
padding: 12px;
text-align: left;
border-bottom: 1px solid #d0d7de;
}
.analysisPanel :deep(th) {
background: #f6f8fa;
font-weight: 600;
color: #24292f;
}
.analysisPanel :deep(td) {
color: #24292f;
}
/* 简洁链接样式 */
.analysisPanel :deep(a) {
color: #0969da;
text-decoration: none;
}
.analysisPanel :deep(a:hover) {
color: #0969da;
text-decoration: underline;
}
/* 简洁分割线 */
.analysisPanel :deep(hr) {
border: none;
height: 1px;
background: #d0d7de;
margin: 16px 0;
}
</style>

View File

@@ -0,0 +1,44 @@
<script lang="ts" setup>
import copyTextToClipboard from "copy-text-to-clipboard"
import { useMessage } from "naive-ui"
import CodeEditor from "../components/CodeEditor.vue"
import { code, input, reset, size } from "../composables/code"
import { debug } from "../api"
const message = useMessage()
function copy() {
copyTextToClipboard(code.value)
message.success("已经复制好了")
}
async function handleDebug() {
const inputs = input.value ? input.value.split("\n") : []
const res = await debug(code.value, inputs)
console.log(res.data)
}
</script>
<template>
<CodeEditor
label="代码区"
icon="streamline-emojis:lemon"
:font-size="size"
v-model="code.value"
:language="code.language"
>
<template #actions>
<n-button
quaternary
type="error"
:disabled="!code.value"
v-if="code.language === 'python'"
@click="handleDebug"
>
调试
</n-button>
<n-button quaternary type="primary" @click="copy">复制</n-button>
<n-button quaternary @click="reset">清空</n-button>
</template>
</CodeEditor>
</template>

View File

@@ -1,182 +1,33 @@
<script lang="ts" setup>
import copyTextToClipboard from "copy-text-to-clipboard"
import { useMessage } from "naive-ui"
import { computed, watch, useTemplateRef } from "vue"
import { marked } from "marked"
import { debug as debugApi } from "../api"
// @ts-ignore
import * as Sk from "skulpt"
import CodeEditor from "../components/CodeEditor.vue"
import {
analysis,
loading,
getAIAnalysis,
showAnalysis,
} from "../composables/analysis"
import {
clearInput,
code,
debug,
input,
output,
reset,
size,
status,
turtleRunId,
} from "../composables/code"
import { Status } from "../types"
const showInputClearBtn = computed(() => !!input.value)
const message = useMessage()
function copy() {
copyTextToClipboard(code.value)
message.success("已经复制好了")
}
function handleDebug() {
debug.value = true
}
async function handleDebugNew() {
const inputs = input.value ? input.value.split("\n") : []
const res = await debugApi(code.value, inputs)
console.log(res.data)
}
const turtleCanvas = useTemplateRef("turtle")
function builtinRead(x: any) {
if (
Sk.builtinFiles === undefined ||
Sk.builtinFiles["files"][x] === undefined
)
throw "文件没有找到:'" + x + "'"
return Sk.builtinFiles["files"][x]
}
function runSkulptTurtle() {
const canvas = turtleCanvas.value
if (!canvas) return
canvas.innerHTML = ""
Sk.configure({
output: console.log,
read: builtinRead,
inputfun: function () {
return input.value
},
__future__: Sk.python3,
})
Sk.TurtleGraphics = {
target: canvas,
width: canvas.clientWidth,
height: canvas.clientHeight,
}
Sk.misceval
.asyncToPromise(function () {
return Sk.importMainWithBody("<stdin>", false, code.value, true)
})
.catch((err: any) => {
output.value += String(err)
})
}
watch(turtleRunId, () => runSkulptTurtle())
import { code } from "../composables/code"
import CodeSection from "./CodeSection.vue"
import InputSection from "./InputSection.vue"
import OutputSection from "./OutputSection.vue"
import TurtleSection from "./TurtleSection.vue"
</script>
<template>
<n-layout-content class="container">
<n-split direction="horizontal" :min="1 / 3" :max="4 / 5">
<template #1>
<CodeEditor
label="代码区"
icon="streamline-emojis:lemon"
:font-size="size"
v-model="code.value"
:language="code.language"
>
<template #actions>
<n-button
quaternary
type="error"
:disabled="!code.value"
v-if="code.language === 'python'"
@click="handleDebug"
>
调试
</n-button>
<n-button
quaternary
type="error"
:disabled="!code.value"
v-if="false && code.language === 'python'"
@click="handleDebugNew"
>
调试
</n-button>
<n-button quaternary type="primary" @click="copy">复制</n-button>
<n-button quaternary @click="reset">清空</n-button>
</template>
</CodeEditor>
<CodeSection />
</template>
<template #2>
<n-split
v-if="code.language !== 'turtle'"
direction="vertical"
:default-size="1 / 3"
:min="1 / 5"
:max="3 / 5"
>
<template #1>
<CodeEditor
icon="streamline-emojis:four-leaf-clover"
label="输入框"
:font-size="size"
v-model="input"
>
<template #actions>
<n-button
quaternary
type="primary"
@click="clearInput"
v-if="showInputClearBtn"
>
清空
</n-button>
</template>
</CodeEditor>
<InputSection />
</template>
<template #2>
<CodeEditor
v-if="code.language !== 'turtle'"
icon="streamline-emojis:hibiscus"
label="输出框"
v-model="output"
readonly
:font-size="size"
>
<template #actions>
<n-tag v-if="status === Status.Accepted" type="success">
运行成功
</n-tag>
<n-tag v-if="showAnalysis" type="warning">运行失败</n-tag>
<n-popover v-if="showAnalysis" trigger="click" placement="left">
<template #trigger>
<n-button quaternary type="error" @click="getAIAnalysis">
推测原因
</n-button>
</template>
<n-spin :show="loading">
<div
class="analysisPanel"
v-html="marked.parse(analysis)"
></div>
</n-spin>
</n-popover>
</template>
</CodeEditor>
<div v-else ref="turtle" class="canvas"></div>
<OutputSection />
</template>
</n-split>
<TurtleSection v-else />
</template>
</n-split>
</n-layout-content>
@@ -186,163 +37,4 @@ watch(turtleRunId, () => runSkulptTurtle())
.container {
height: calc(100vh - 60px);
}
.canvas {
width: 100%;
height: 100%;
}
.analysisPanel {
width: 400px;
min-height: 60px;
max-height: calc(100vh - 200px);
overflow: auto;
padding: 16px;
border-radius: 8px;
line-height: 1.6;
color: #374151;
}
/* 简洁 Markdown 样式 */
.analysisPanel :deep(h1),
.analysisPanel :deep(h2),
.analysisPanel :deep(h3),
.analysisPanel :deep(h4),
.analysisPanel :deep(h5),
.analysisPanel :deep(h6) {
margin: 16px 0 8px 0;
font-weight: 600;
color: #1f2937;
}
.analysisPanel :deep(h1) {
font-size: 1.5em;
border-bottom: 2px solid #e5e7eb;
padding-bottom: 8px;
}
.analysisPanel :deep(h2) {
font-size: 1.3em;
color: #374151;
}
.analysisPanel :deep(h3) {
font-size: 1.1em;
color: #4b5563;
}
.analysisPanel :deep(p) {
margin: 12px 0;
color: #374151;
font-size: 14px;
}
.analysisPanel :deep(ul),
.analysisPanel :deep(ol) {
margin: 12px 0;
padding-left: 20px;
}
.analysisPanel :deep(li) {
margin: 4px 0;
color: #374151;
}
.analysisPanel :deep(strong) {
font-weight: 600;
color: #1f2937;
}
.analysisPanel :deep(em) {
font-style: italic;
color: #6b7280;
}
/* 简洁代码块样式 */
.analysisPanel :deep(pre) {
background: #f8f9fa;
color: #24292f;
padding: 16px;
border-radius: 6px;
margin: 12px 0;
overflow-x: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
line-height: 1.5;
border: 1px solid #d0d7de;
}
.analysisPanel :deep(code) {
background: #f1f3f4;
color: #d63384;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.9em;
}
.analysisPanel :deep(pre code) {
background: transparent;
color: inherit;
padding: 0;
border-radius: 0;
}
/* 简洁引用块样式 */
.analysisPanel :deep(blockquote) {
border-left: 4px solid #d0d7de;
background: #f6f8fa;
margin: 16px 0;
padding: 12px 16px;
border-radius: 0 6px 6px 0;
color: #656d76;
font-style: italic;
}
/* 简洁表格样式 */
.analysisPanel :deep(table) {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
background: #ffffff;
border-radius: 6px;
overflow: hidden;
border: 1px solid #d0d7de;
}
.analysisPanel :deep(th),
.analysisPanel :deep(td) {
padding: 12px;
text-align: left;
border-bottom: 1px solid #d0d7de;
}
.analysisPanel :deep(th) {
background: #f6f8fa;
font-weight: 600;
color: #24292f;
}
.analysisPanel :deep(td) {
color: #24292f;
}
/* 简洁链接样式 */
.analysisPanel :deep(a) {
color: #0969da;
text-decoration: none;
}
.analysisPanel :deep(a:hover) {
color: #0969da;
text-decoration: underline;
}
/* 简洁分割线 */
.analysisPanel :deep(hr) {
border: none;
height: 1px;
background: #d0d7de;
margin: 16px 0;
}
</style>

View File

@@ -1,64 +0,0 @@
<template>
<div class="loading" v-if="loading">正在加载中...第一次打开会有点慢</div>
<div v-if="!loading">
<p class="tip">提醒</p>
<p>1. 点击下一步开始调试也可以拖动进度条</p>
<p>
2. 点击
<n-button text type="primary" @click="close">修改代码</n-button>
完成修改后可再次调试
</p>
</div>
<iframe
width="100%"
height="360"
frameborder="0"
:src="src"
ref="main"
></iframe>
</template>
<script lang="ts" setup>
import qs from "query-string"
import { onMounted, ref, useTemplateRef } from "vue"
import { code, debug } from "../composables/code"
import { useDark } from "@vueuse/core"
const src = ref("")
const loading = ref(true)
const main = useTemplateRef("main")
const isDark = useDark()
onMounted(() => {
const url = import.meta.env.PUBLIC_PYVIZ_URL
const base = url + "/iframe-embed.html"
const part1 = qs.stringify({
code: code.value,
codeDivWidth: 300,
})
const part2 =
"&cumulative=false&curInstr=0&heapPrimitives=nevernest&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=true&"
const part3 = qs.stringify({
dark: isDark.value,
})
const query = part1 + part2 + part3
src.value = base + "#" + query
main.value!.addEventListener("load", () => {
loading.value = false
})
})
function close() {
debug.value = false
}
</script>
<style scoped>
.loading {
font-size: 16px;
}
.tip {
margin-top: 0;
}
</style>

View File

@@ -0,0 +1,27 @@
<script lang="ts" setup>
import { computed } from "vue"
import CodeEditor from "../components/CodeEditor.vue"
import { clearInput, input, size } from "../composables/code"
const showInputClearBtn = computed(() => !!input.value)
</script>
<template>
<CodeEditor
icon="streamline-emojis:four-leaf-clover"
label="输入框"
:font-size="size"
v-model="input"
>
<template #actions>
<n-button
quaternary
type="primary"
@click="clearInput"
v-if="showInputClearBtn"
>
清空
</n-button>
</template>
</CodeEditor>
</template>

View File

@@ -0,0 +1,35 @@
<script lang="ts" setup>
import CodeEditor from "../components/CodeEditor.vue"
import { output, size, status } from "../composables/code"
import { Status } from "../types"
import {
analysis,
loading,
getAIAnalysis,
showAnalysis,
} from "../composables/analysis"
import AnalysisPanel from "./AnalysisPanel.vue"
</script>
<template>
<CodeEditor
icon="streamline-emojis:hibiscus"
label="输出框"
v-model="output"
readonly
:font-size="size"
>
<template #actions>
<n-tag v-if="status === Status.Accepted" type="success"> 运行成功 </n-tag>
<n-tag v-if="showAnalysis" type="warning">运行失败</n-tag>
<n-popover v-if="showAnalysis" trigger="click" placement="left">
<template #trigger>
<n-button quaternary type="error" @click="getAIAnalysis">
推测原因
</n-button>
</template>
<AnalysisPanel :analysis="analysis" :loading="loading" />
</n-popover>
</template>
</CodeEditor>
</template>

View File

@@ -0,0 +1,56 @@
<script lang="ts" setup>
import { useTemplateRef, watch } from "vue"
// @ts-ignore
import * as Sk from "skulpt"
import { code, input, output, turtleRunId } from "../composables/code"
const turtleCanvas = useTemplateRef("turtle")
function builtinRead(x: any) {
if (
Sk.builtinFiles === undefined ||
Sk.builtinFiles["files"][x] === undefined
)
throw "文件没有找到:'" + x + "'"
return Sk.builtinFiles["files"][x]
}
function runSkulptTurtle() {
const canvas = turtleCanvas.value
if (!canvas) return
canvas.innerHTML = ""
Sk.configure({
output: console.log,
read: builtinRead,
inputfun: function () {
return input.value
},
__future__: Sk.python3,
})
Sk.TurtleGraphics = {
target: canvas,
width: canvas.clientWidth,
height: canvas.clientHeight,
}
Sk.misceval
.asyncToPromise(function () {
return Sk.importMainWithBody("<stdin>", false, code.value, true)
})
.catch((err: any) => {
output.value += String(err)
})
}
watch(turtleRunId, () => runSkulptTurtle())
</script>
<template>
<div ref="turtle" class="canvas"></div>
</template>
<style scoped>
.canvas {
width: 100%;
height: 100%;
}
</style>

View File

@@ -19,22 +19,12 @@
>
<Query />
</n-modal>
<n-modal
v-model:show="debug"
preset="card"
style="width: 700px"
:mask-closable="false"
title="可视化调试(测试版)"
>
<Debug />
</n-modal>
</template>
<script lang="ts" setup>
import { useMagicKeys, whenever } from "@vueuse/core"
import { ref } from "vue"
import { debug, run } from "../composables/code"
import { run } from "../composables/code"
import Content from "./Content.vue"
import Debug from "./Debug.vue"
import File from "./File.vue"
import Header from "./Header.vue"
import Query from "./Query.vue"