feat: 添加Markdown渲染支持并增强UI功能
- 引入markdown-it库实现节点内容和摘要的Markdown渲染 - 添加多种边类型选择功能(直线/曲线/折线等) - 实现全屏模式切换功能 - 优化底部工具栏和节点输入框的样式 - 更新i18n多语言支持新增功能文本
This commit is contained in:
92
package-lock.json
generated
92
package-lock.json
generated
@@ -16,10 +16,12 @@
|
||||
"axios": "^1.6.7",
|
||||
"html-to-image": "^1.11.13",
|
||||
"lucide-vue-next": "^0.322.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"vue": "^3.4.15",
|
||||
"vue-i18n": "^11.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.35",
|
||||
@@ -956,6 +958,31 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/markdown-it": {
|
||||
"version": "14.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.1.2.tgz",
|
||||
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/linkify-it": "^5",
|
||||
"@types/mdurl": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
||||
@@ -1327,6 +1354,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -2161,6 +2194,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-vue-next": {
|
||||
"version": "0.322.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.322.0.tgz",
|
||||
@@ -2179,6 +2221,35 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -2188,6 +2259,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
@@ -2528,6 +2605,15 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -2861,6 +2947,12 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
"axios": "^1.6.7",
|
||||
"html-to-image": "^1.11.13",
|
||||
"lucide-vue-next": "^0.322.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"vue": "^3.4.15",
|
||||
"vue-i18n": "^11.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.35",
|
||||
|
||||
41
src/App.vue
41
src/App.vue
@@ -11,8 +11,9 @@ import { useI18n } from 'vue-i18n'
|
||||
// 画布:VueFlow 与可选插件
|
||||
import { VueFlow } from '@vue-flow/core'
|
||||
import { Background, BackgroundVariant } from '@vue-flow/background'
|
||||
import { Controls } from '@vue-flow/controls'
|
||||
import { Controls, ControlButton } from '@vue-flow/controls'
|
||||
import { MiniMap } from '@vue-flow/minimap'
|
||||
import { Maximize, Minimize } from 'lucide-vue-next'
|
||||
|
||||
// VueFlow 内置样式(必须引入,否则组件样式缺失)
|
||||
import '@vue-flow/core/dist/style.css'
|
||||
@@ -31,9 +32,39 @@ import WindowNode from './components/WindowNode.vue'
|
||||
|
||||
// 业务层:统一的状态与动作入口
|
||||
import { useThinkFlow } from './composables/useThinkFlow'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
/**
|
||||
* 全屏控制逻辑
|
||||
*/
|
||||
const isFullscreen = ref(false)
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen().catch((err) => {
|
||||
console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`)
|
||||
})
|
||||
} else {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleFullscreenChange = () => {
|
||||
isFullscreen.value = !!document.fullscreenElement
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
||||
})
|
||||
|
||||
/**
|
||||
* 从业务层拿到全局状态与动作。
|
||||
* 说明:
|
||||
@@ -102,7 +133,7 @@ const fitToView = () => {
|
||||
|
||||
<div class="flex-grow relative">
|
||||
<VueFlow
|
||||
:default-edge-options="{ type: 'smoothstep' }"
|
||||
:default-edge-options="{ type: config.edgeType }"
|
||||
:fit-view-on-init="true"
|
||||
class="bg-white"
|
||||
:class="{ 'space-pressed': isSpacePressed }"
|
||||
@@ -115,7 +146,11 @@ const fitToView = () => {
|
||||
:gap="24"
|
||||
:size="config.backgroundVariant === BackgroundVariant.Dots ? 1 : 0.5"
|
||||
/>
|
||||
<Controls v-if="config.showControls" />
|
||||
<Controls v-if="config.showControls" :show-fullscreen="false" :show-fit-view="false">
|
||||
<ControlButton @click="toggleFullscreen" :title="isFullscreen ? t('nav.exitFullscreen') : t('nav.fullscreen')">
|
||||
<component :is="isFullscreen ? Minimize : Maximize" class="w-4 h-4 text-slate-500" />
|
||||
</ControlButton>
|
||||
</Controls>
|
||||
<MiniMap v-if="config.showMiniMap" pannable zoomable />
|
||||
|
||||
<template #node-window="{ id, data, selected }">
|
||||
|
||||
@@ -39,14 +39,14 @@ const emit = defineEmits<{
|
||||
<input
|
||||
:value="props.modelValue"
|
||||
:placeholder="props.t('nav.placeholder')"
|
||||
class="flex-grow bg-transparent border-none outline-none text-xs md:text-sm font-bold text-slate-700 placeholder:text-slate-300 min-w-0"
|
||||
class="flex-grow bg-transparent border-none outline-none text-xs md:text-sm font-bold text-slate-700 placeholder:text-slate-400 min-w-0"
|
||||
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
||||
@keyup.enter="emit('expand')"
|
||||
/>
|
||||
<button
|
||||
@click="emit('expand')"
|
||||
:disabled="props.isLoading || !props.modelValue.trim()"
|
||||
class="flex items-center gap-1.5 md:gap-2 px-3 md:px-4 py-2 md:py-2.5 bg-slate-900 hover:bg-slate-800 text-white rounded-lg md:rounded-xl transition-all active:scale-95 disabled:opacity-20 disabled:grayscale disabled:cursor-not-allowed group/btn flex-shrink-0"
|
||||
class="flex items-center gap-1.5 md:gap-2 px-3 md:px-4 py-2 md:py-2.5 bg-slate-900 hover:bg-slate-800 text-white rounded-lg md:rounded-xl transition-all active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed group/btn flex-shrink-0"
|
||||
>
|
||||
<span class="text-[9px] md:text-[10px] font-black tracking-widest uppercase">{{ props.t('nav.execute') }}</span>
|
||||
<Zap v-if="!props.isLoading" class="w-3.5 h-3.5 md:w-4 h-4 text-orange-400 group-hover/btn:scale-110 transition-transform" />
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
// 图标:标题/关闭/加载
|
||||
import { RefreshCw, Sparkles, X } from 'lucide-vue-next'
|
||||
|
||||
// Markdown 渲染
|
||||
import MarkdownIt from 'markdown-it'
|
||||
|
||||
/**
|
||||
* props:
|
||||
* - show:弹窗显示开关
|
||||
@@ -23,6 +26,12 @@ const props = defineProps<{
|
||||
summaryContent: string
|
||||
}>()
|
||||
|
||||
const md = new MarkdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true
|
||||
})
|
||||
|
||||
/**
|
||||
* close:关闭弹窗
|
||||
*/
|
||||
@@ -59,10 +68,8 @@ const emit = defineEmits<{
|
||||
<RefreshCw class="w-8 h-8 text-orange-500 animate-spin" />
|
||||
<p class="text-sm font-bold text-slate-400 animate-pulse">{{ props.t('common.summarizing') }}</p>
|
||||
</div>
|
||||
<div v-else class="prose prose-slate max-w-none">
|
||||
<div class="whitespace-pre-wrap text-slate-600 leading-relaxed text-sm md:text-base font-medium">
|
||||
{{ props.summaryContent }}
|
||||
</div>
|
||||
<div v-else class="markdown-body max-w-none">
|
||||
<div class="text-slate-600 leading-relaxed text-sm md:text-base font-medium" v-html="md.render(props.summaryContent)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -108,6 +108,14 @@ const callAndClose = (fn: () => void) => {
|
||||
<span class="text-[9px] md:text-[10px] font-bold text-slate-500 uppercase">{{ props.t('nav.edge') }}</span>
|
||||
</div>
|
||||
|
||||
<select v-model="props.config.edgeType" class="toolbar-select flex-shrink-0">
|
||||
<option value="default">{{ props.t('nav.edgeTypes.default') }}</option>
|
||||
<option value="straight">{{ props.t('nav.edgeTypes.straight') }}</option>
|
||||
<option value="step">{{ props.t('nav.edgeTypes.step') }}</option>
|
||||
<option value="smoothstep">{{ props.t('nav.edgeTypes.smoothstep') }}</option>
|
||||
<option value="simplebezier">{{ props.t('nav.edgeTypes.simplebezier') }}</option>
|
||||
</select>
|
||||
|
||||
<select v-model="props.config.backgroundVariant" class="toolbar-select flex-shrink-0">
|
||||
<option :value="BackgroundVariant.Lines">{{ props.t('nav.lines') }}</option>
|
||||
<option :value="BackgroundVariant.Dots">{{ props.t('nav.dots') }}</option>
|
||||
@@ -204,6 +212,14 @@ const callAndClose = (fn: () => void) => {
|
||||
<span class="text-[10px] font-bold text-slate-500 uppercase">{{ props.t('nav.edge') }}</span>
|
||||
</div>
|
||||
|
||||
<select v-model="props.config.edgeType" class="toolbar-select">
|
||||
<option value="default">{{ props.t('nav.edgeTypes.default') }}</option>
|
||||
<option value="straight">{{ props.t('nav.edgeTypes.straight') }}</option>
|
||||
<option value="step">{{ props.t('nav.edgeTypes.step') }}</option>
|
||||
<option value="smoothstep">{{ props.t('nav.edgeTypes.smoothstep') }}</option>
|
||||
<option value="simplebezier">{{ props.t('nav.edgeTypes.simplebezier') }}</option>
|
||||
</select>
|
||||
|
||||
<select v-model="props.config.backgroundVariant" class="toolbar-select">
|
||||
<option :value="BackgroundVariant.Lines">{{ props.t('nav.lines') }}</option>
|
||||
<option :value="BackgroundVariant.Dots">{{ props.t('nav.dots') }}</option>
|
||||
|
||||
@@ -25,6 +25,9 @@ import {
|
||||
X
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
// Markdown 渲染
|
||||
import MarkdownIt from 'markdown-it'
|
||||
|
||||
/**
|
||||
* props:
|
||||
* - id/data/selected:VueFlow 提供的节点数据
|
||||
@@ -47,6 +50,12 @@ const props = defineProps<{
|
||||
expandIdea: (param?: any, customInput?: string) => void
|
||||
}>()
|
||||
|
||||
const md = new MarkdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true
|
||||
})
|
||||
|
||||
/**
|
||||
* preview:请求 App 打开图片预览弹窗
|
||||
*/
|
||||
@@ -201,9 +210,11 @@ const getNodePosition = (id: string) => props.flowNodes.find(n => n.id === id)?.
|
||||
</div>
|
||||
<span class="text-[9px] font-black text-slate-300 uppercase tracking-widest animate-pulse">{{ props.t('common.loading') }}</span>
|
||||
</div>
|
||||
<div v-else class="text-[11px] text-slate-600 leading-relaxed font-medium whitespace-pre-wrap max-h-[350px] overflow-y-auto custom-scrollbar pr-2 selection:bg-orange-100 nowheel">
|
||||
{{ props.data.detailedContent }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="markdown-body text-[11px] text-slate-600 leading-relaxed font-medium max-h-[350px] overflow-y-auto custom-scrollbar pr-2 selection:bg-orange-100 nowheel"
|
||||
v-html="md.render(props.data.detailedContent)"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="relative group/input">
|
||||
@@ -219,14 +230,14 @@ const getNodePosition = (id: string) => props.flowNodes.find(n => n.id === id)?.
|
||||
@blur="isFocused = false"
|
||||
@keyup.enter="props.expandIdea({ id: props.id, data: props.data, position: getNodePosition(props.id) }, props.data.followUp)"
|
||||
:placeholder="props.t('node.followUp')"
|
||||
class="bg-transparent border-none outline-none text-[10px] font-bold text-slate-700 flex-grow placeholder:text-slate-300"
|
||||
class="bg-transparent border-none outline-none text-[10px] font-bold text-slate-700 flex-grow placeholder:text-slate-400"
|
||||
:disabled="props.data.isExpanding"
|
||||
/>
|
||||
<button
|
||||
@click.stop="props.expandIdea({ id: props.id, data: props.data, position: getNodePosition(props.id) }, props.data.followUp)"
|
||||
:disabled="!props.data.followUp?.trim() || props.data.isExpanding"
|
||||
class="transition-all transform active:scale-90"
|
||||
:style="{ color: props.data.followUp?.trim() ? props.config.edgeColor : '#cbd5e1' }"
|
||||
:style="{ color: props.data.followUp?.trim() ? props.config.edgeColor : '#94a3b8' }"
|
||||
>
|
||||
<RefreshCw v-if="props.data.isExpanding" class="w-3.5 h-3.5 animate-spin" />
|
||||
<ArrowRight v-else class="w-3.5 h-3.5" />
|
||||
|
||||
@@ -232,7 +232,7 @@ export function useThinkFlow({ t, locale }: { t: Translate; locale: Ref<string>
|
||||
|
||||
const config = reactive({
|
||||
edgeColor: '#fed7aa',
|
||||
edgeStyle: 'smoothstep',
|
||||
edgeType: 'default',
|
||||
backgroundVariant: BackgroundVariant.Lines,
|
||||
showControls: true,
|
||||
showMiniMap: true
|
||||
@@ -245,12 +245,12 @@ export function useThinkFlow({ t, locale }: { t: Translate; locale: Ref<string>
|
||||
* - 通过 lastAppliedStatus 避免无效重复 setEdges
|
||||
*/
|
||||
watch(
|
||||
[() => activeNodeId.value, () => config.edgeColor, () => flowEdges.value.length, () => flowNodes.value.some(n => n.data.isExpanding)],
|
||||
([, newColor, , anyExpanding]) => {
|
||||
[() => activeNodeId.value, () => config.edgeColor, () => config.edgeType, () => flowEdges.value.length, () => flowNodes.value.some(n => n.data.isExpanding)],
|
||||
([, newColor, newType, , anyExpanding]) => {
|
||||
const { edgeIds } = activePath.value
|
||||
const edgeIdsStr = Array.from(edgeIds).sort().join(',')
|
||||
|
||||
const currentStatus = `${edgeIdsStr}-${newColor}-${anyExpanding}`
|
||||
const currentStatus = `${edgeIdsStr}-${newColor}-${newType}-${anyExpanding}`
|
||||
if (lastAppliedStatus.value === currentStatus) return
|
||||
lastAppliedStatus.value = currentStatus
|
||||
|
||||
@@ -261,6 +261,7 @@ export function useThinkFlow({ t, locale }: { t: Translate; locale: Ref<string>
|
||||
|
||||
return {
|
||||
...edge,
|
||||
type: newType,
|
||||
animated: isHighlighted || isExpanding,
|
||||
style: {
|
||||
...edge.style,
|
||||
@@ -574,6 +575,7 @@ export function useThinkFlow({ t, locale }: { t: Translate; locale: Ref<string>
|
||||
source: parentId,
|
||||
target: childId,
|
||||
animated: true,
|
||||
type: config.edgeType,
|
||||
style: { stroke: config.edgeColor, strokeWidth: 2 },
|
||||
markerEnd: MarkerType.ArrowClosed
|
||||
})
|
||||
|
||||
@@ -37,11 +37,20 @@
|
||||
"layout": "LAYOUT",
|
||||
"center": "CENTER",
|
||||
"edge": "EDGE",
|
||||
"edgeTypes": {
|
||||
"default": "Bezier",
|
||||
"straight": "Straight",
|
||||
"step": "Step",
|
||||
"smoothstep": "Smooth",
|
||||
"simplebezier": "Simple"
|
||||
},
|
||||
"lines": "LINES",
|
||||
"dots": "DOTS",
|
||||
"map": "MAP",
|
||||
"summary": "SUMMARY",
|
||||
"export": "EXPORT"
|
||||
"export": "EXPORT",
|
||||
"fullscreen": "Fullscreen",
|
||||
"exitFullscreen": "Exit Fullscreen"
|
||||
},
|
||||
"settings": {
|
||||
"title": "API Settings",
|
||||
|
||||
@@ -37,11 +37,20 @@
|
||||
"layout": "布局",
|
||||
"center": "起点",
|
||||
"edge": "连线",
|
||||
"edgeTypes": {
|
||||
"default": "曲线",
|
||||
"straight": "直线",
|
||||
"step": "折线",
|
||||
"smoothstep": "圆角",
|
||||
"simplebezier": "平滑"
|
||||
},
|
||||
"lines": "网格",
|
||||
"dots": "点阵",
|
||||
"map": "地图",
|
||||
"summary": "总结",
|
||||
"export": "导出"
|
||||
"export": "导出",
|
||||
"fullscreen": "全屏",
|
||||
"exitFullscreen": "退出全屏"
|
||||
},
|
||||
"settings": {
|
||||
"title": "API 设置",
|
||||
|
||||
@@ -29,3 +29,72 @@ body {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Markdown 渲染基础样式 */
|
||||
.markdown-body {
|
||||
@apply text-slate-700;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
@apply text-lg font-black mt-4 mb-2 text-slate-900;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
@apply text-base font-black mt-3 mb-2 text-slate-900;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
@apply text-sm font-black mt-2 mb-1 text-slate-800;
|
||||
}
|
||||
|
||||
.markdown-body p {
|
||||
@apply mb-2 last:mb-0;
|
||||
}
|
||||
|
||||
.markdown-body ul, .markdown-body ol {
|
||||
@apply pl-4 mb-2 list-outside;
|
||||
}
|
||||
|
||||
.markdown-body ul {
|
||||
@apply list-disc;
|
||||
}
|
||||
|
||||
.markdown-body ol {
|
||||
@apply list-decimal;
|
||||
}
|
||||
|
||||
.markdown-body li {
|
||||
@apply mb-1;
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
@apply px-1 py-0.5 bg-slate-100 rounded text-[0.9em] font-mono text-orange-600;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
@apply p-3 bg-slate-900 text-slate-100 rounded-lg overflow-x-auto mb-3 font-mono text-[0.85em];
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
@apply pl-3 border-l-4 border-slate-200 text-slate-500 italic mb-2;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
@apply text-orange-500 hover:underline;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
@apply w-full border-collapse mb-3;
|
||||
}
|
||||
|
||||
.markdown-body th, .markdown-body td {
|
||||
@apply border border-slate-200 p-2 text-left;
|
||||
}
|
||||
|
||||
.markdown-body th {
|
||||
@apply bg-slate-50 font-bold;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
@apply my-4 border-t border-slate-100;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user