优化代码
This commit is contained in:
307
src/App.vue
307
src/App.vue
@@ -119,6 +119,7 @@ const hoveredNodeId = ref<string | null>(null)
|
||||
const focusedNodeId = ref<string | null>(null)
|
||||
const draggingNodeId = ref<string | null>(null)
|
||||
const previewImageUrl = ref<string | null>(null)
|
||||
const showResetConfirm = ref(false)
|
||||
|
||||
// 画布控制状态
|
||||
const panOnDrag = ref(true)
|
||||
@@ -248,6 +249,19 @@ watch(
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
/**
|
||||
* 统一错误处理
|
||||
*/
|
||||
const getErrorMessage = (error: any) => {
|
||||
if (error.name === 'TypeError' && error.message === 'Failed to fetch') {
|
||||
return t('common.error.cors')
|
||||
}
|
||||
if (error.status === 429) return t('common.error.rateLimit')
|
||||
if (error.status === 400) return t('common.error.badRequest')
|
||||
if (error.status >= 500) return t('common.error.serverError')
|
||||
return error.message || t('common.error.unknown')
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚焦到根节点
|
||||
*/
|
||||
@@ -327,7 +341,10 @@ const generateNodeImage = async (nodeId: string, prompt: string) => {
|
||||
const node = flowNodes.value.find(n => n.id === nodeId)
|
||||
if (!node || node.data.isImageLoading) return
|
||||
|
||||
updateNode(nodeId, { data: { ...node.data, isImageLoading: true } })
|
||||
// 激活节点
|
||||
updateNode(nodeId, { selected: true, zIndex: 1000 })
|
||||
|
||||
updateNode(nodeId, { data: { ...node.data, isImageLoading: true, error: null } })
|
||||
|
||||
const useConfig = apiConfig.mode === 'default' ? DEFAULT_CONFIG.image : apiConfig.image
|
||||
// 自定义模式下完全使用用户输入,不进行项目 Key 兜底
|
||||
@@ -346,14 +363,70 @@ const generateNodeImage = async (nodeId: string, prompt: string) => {
|
||||
})
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error('Image request failed')
|
||||
if (!response.ok) {
|
||||
const error: any = new Error('Image request failed')
|
||||
error.status = response.status
|
||||
throw error
|
||||
}
|
||||
const data = await response.json()
|
||||
const imageUrl = data.data[0].url
|
||||
|
||||
updateNode(nodeId, { data: { ...node.data, imageUrl, isImageLoading: false } })
|
||||
} catch (error) {
|
||||
updateNode(nodeId, { data: { ...node.data, imageUrl, isImageLoading: false, error: null } })
|
||||
} catch (error: any) {
|
||||
console.error('Image Generation Error:', error)
|
||||
updateNode(nodeId, { data: { ...node.data, isImageLoading: false } })
|
||||
updateNode(nodeId, { data: { ...node.data, isImageLoading: false, error: getErrorMessage(error) } })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度解析节点内容
|
||||
*/
|
||||
const deepDive = async (nodeId: string, topic: string) => {
|
||||
const node = flowNodes.value.find(n => n.id === nodeId)
|
||||
if (!node) return
|
||||
|
||||
// 激活节点并置顶
|
||||
updateNode(nodeId, { selected: true, zIndex: 1000 })
|
||||
|
||||
// 如果已经有内容且当前是收起状态,则直接展开
|
||||
if (node.data.detailedContent && !node.data.isDetailExpanded) {
|
||||
updateNode(nodeId, { data: { ...node.data, isDetailExpanded: true } })
|
||||
return
|
||||
}
|
||||
|
||||
// 如果已经在加载,则不重复请求
|
||||
if (node.data.isDeepDiving) return
|
||||
|
||||
updateNode(nodeId, { data: { ...node.data, isDeepDiving: true, isDetailExpanded: true, error: null } })
|
||||
|
||||
const useConfig = apiConfig.mode === 'default' ? DEFAULT_CONFIG.chat : apiConfig.chat
|
||||
const finalApiKey = apiConfig.mode === 'default' ? useConfig.apiKey || API_KEY : useConfig.apiKey
|
||||
|
||||
try {
|
||||
const response = await fetch(useConfig.baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${finalApiKey}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: useConfig.model,
|
||||
messages: [{ role: 'user', content: t('prompts.deepDivePrompt', { topic }) }]
|
||||
})
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error: any = new Error('Deep dive request failed')
|
||||
error.status = response.status
|
||||
throw error
|
||||
}
|
||||
const data = await response.json()
|
||||
const content = data.choices[0].message.content
|
||||
|
||||
updateNode(nodeId, { data: { ...node.data, detailedContent: content, isDeepDiving: false, error: null } })
|
||||
} catch (error: any) {
|
||||
console.error('Deep Dive Error:', error)
|
||||
updateNode(nodeId, { data: { ...node.data, isDeepDiving: false, error: getErrorMessage(error) } })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +481,9 @@ const expandIdea = async (param?: any, customInput?: string) => {
|
||||
description: t('node.coreIdea'),
|
||||
type: 'root',
|
||||
isExpanding: true,
|
||||
followUp: ''
|
||||
isTitleExpanded: false,
|
||||
followUp: '',
|
||||
error: null
|
||||
},
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left
|
||||
@@ -417,7 +492,16 @@ const expandIdea = async (param?: any, customInput?: string) => {
|
||||
ideaInput.value = ''
|
||||
} else {
|
||||
const node = flowNodes.value.find(n => n.id === parentNode.id)
|
||||
if (node) node.data.isExpanding = true
|
||||
if (node) {
|
||||
updateNode(parentNode.id, {
|
||||
data: {
|
||||
...node.data,
|
||||
isExpanding: true,
|
||||
isDetailExpanded: false, // 开始发散时隐藏详情
|
||||
error: null
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const systemPrompt = t('prompts.system')
|
||||
@@ -452,7 +536,11 @@ const expandIdea = async (param?: any, customInput?: string) => {
|
||||
})
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error('AI request failed')
|
||||
if (!response.ok) {
|
||||
const error: any = new Error('AI request failed')
|
||||
error.status = response.status
|
||||
throw error
|
||||
}
|
||||
const data = await response.json()
|
||||
const result = JSON.parse(data.choices[0].message.content)
|
||||
|
||||
@@ -462,8 +550,29 @@ const expandIdea = async (param?: any, customInput?: string) => {
|
||||
const startY = parentNodeObj ? parentNodeObj.position.y : 300
|
||||
|
||||
processSubNodes(result.nodes, currentParentId, startX, startY)
|
||||
} catch (error) {
|
||||
|
||||
// 首次输入后,优化缩放比例:展示根节点和大约3个二级节点
|
||||
if (!parentNode) {
|
||||
setTimeout(() => {
|
||||
const childEdges = flowEdges.value.filter(e => e.source === currentParentId)
|
||||
const childIds = childEdges.map(e => e.target)
|
||||
|
||||
// 选取前3个二级节点作为缩放参考,这样可以保证缩放比例适中(约看到3个二级的大小)
|
||||
const nodesToFit = [currentParentId, ...childIds.slice(0, 3)]
|
||||
|
||||
fitView({
|
||||
nodes: nodesToFit,
|
||||
padding: 0.25,
|
||||
duration: 1000
|
||||
})
|
||||
}, 100)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Expansion Error:', error)
|
||||
const node = flowNodes.value.find(n => n.id === currentParentId)
|
||||
if (node) {
|
||||
updateNode(currentParentId, { data: { ...node.data, error: getErrorMessage(error) } })
|
||||
}
|
||||
} finally {
|
||||
const node = flowNodes.value.find(n => n.id === currentParentId)
|
||||
if (node) {
|
||||
@@ -489,7 +598,9 @@ const processSubNodes = (subNodes: any[], parentId: string, baseX: number, baseY
|
||||
type: 'child',
|
||||
followUp: '',
|
||||
isExpanding: false,
|
||||
isImageLoading: false
|
||||
isImageLoading: false,
|
||||
isTitleExpanded: false,
|
||||
error: null
|
||||
},
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left
|
||||
@@ -506,10 +617,19 @@ const processSubNodes = (subNodes: any[], parentId: string, baseX: number, baseY
|
||||
})
|
||||
}
|
||||
|
||||
const startNewSession = () => {
|
||||
const executeReset = () => {
|
||||
ideaInput.value = ''
|
||||
setNodes([])
|
||||
setEdges([])
|
||||
showResetConfirm.value = false
|
||||
}
|
||||
|
||||
const startNewSession = () => {
|
||||
if (flowNodes.value.length > 0) {
|
||||
showResetConfirm.value = true
|
||||
return
|
||||
}
|
||||
executeReset()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -527,12 +647,6 @@ const startNewSession = () => {
|
||||
|
||||
<!-- 桌面端工具按钮组 -->
|
||||
<div class="hidden md:flex items-center gap-2">
|
||||
<!-- 重置画布 -->
|
||||
<button @click="startNewSession" class="toolbar-btn text-red-500 hover:bg-red-50 border-red-100 flex-shrink-0" :title="t('nav.reset')">
|
||||
<Trash2 class="w-3.5 h-3.5 md:w-4 h-4" />
|
||||
<span>{{ t('nav.reset') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="h-4 w-[1px] bg-slate-100 mx-1 flex-shrink-0"></div>
|
||||
|
||||
<!-- 布局控制 -->
|
||||
@@ -553,6 +667,14 @@ const startNewSession = () => {
|
||||
|
||||
<div class="h-4 w-[1px] bg-slate-100 mx-1 flex-shrink-0"></div>
|
||||
|
||||
<!-- 重置画布 -->
|
||||
<button @click="startNewSession" class="toolbar-btn text-red-500 hover:bg-red-50 border-red-100 flex-shrink-0" :title="t('nav.reset')">
|
||||
<Trash2 class="w-3.5 h-3.5 md:w-4 h-4" />
|
||||
<span>{{ t('nav.reset') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="h-4 w-[1px] bg-slate-100 mx-1 flex-shrink-0"></div>
|
||||
|
||||
<!-- 连线颜色 -->
|
||||
<div class="flex items-center gap-2 px-2 md:px-3 py-1.5 bg-slate-50 rounded-lg border border-slate-100 flex-shrink-0">
|
||||
<Palette class="w-3 h-3 md:w-3.5 h-3.5 text-slate-400" />
|
||||
@@ -636,12 +758,6 @@ const startNewSession = () => {
|
||||
leave-to-class="transform -translate-y-4 opacity-0"
|
||||
>
|
||||
<div v-if="isToolsExpanded" class="md:hidden absolute top-[57px] left-0 right-0 bg-white/95 backdrop-blur-md border-b border-slate-200 shadow-xl z-40 py-4 px-4 flex flex-wrap gap-3 justify-center">
|
||||
<!-- 重置画布 -->
|
||||
<button @click="startNewSession(); isToolsExpanded = false" class="toolbar-btn text-red-500 hover:bg-red-50 border-red-100" :title="t('nav.reset')">
|
||||
<Trash2 class="w-4 h-4" />
|
||||
<span>{{ t('nav.reset') }}</span>
|
||||
</button>
|
||||
|
||||
<!-- 布局控制 -->
|
||||
<button @click="fitView({ padding: 0.2, duration: 800 }); isToolsExpanded = false" class="toolbar-btn text-blue-500 hover:bg-blue-50 border-blue-100" :title="t('nav.fit')">
|
||||
<Focus class="w-4 h-4" />
|
||||
@@ -658,6 +774,12 @@ const startNewSession = () => {
|
||||
<span>{{ t('nav.center') }}</span>
|
||||
</button>
|
||||
|
||||
<!-- 重置画布 -->
|
||||
<button @click="startNewSession(); isToolsExpanded = false" class="toolbar-btn text-red-500 hover:bg-red-50 border-red-100" :title="t('nav.reset')">
|
||||
<Trash2 class="w-4 h-4" />
|
||||
<span>{{ t('nav.reset') }}</span>
|
||||
</button>
|
||||
|
||||
<!-- 连线颜色 -->
|
||||
<div class="flex items-center gap-2 px-3 py-1.5 bg-slate-50 rounded-lg border border-slate-100">
|
||||
<Palette class="w-4 h-4 text-slate-400" />
|
||||
@@ -706,7 +828,12 @@ const startNewSession = () => {
|
||||
:pan-on-drag="panOnDrag"
|
||||
:selection-key-code="'Shift'"
|
||||
>
|
||||
<Background :variant="config.backgroundVariant" pattern-color="#f2f2f2" :gap="24" :size="0.5" />
|
||||
<Background
|
||||
:variant="config.backgroundVariant"
|
||||
:pattern-color="config.backgroundVariant === BackgroundVariant.Dots ? '#cbd5e1' : '#f1f5f9'"
|
||||
:gap="24"
|
||||
:size="config.backgroundVariant === BackgroundVariant.Dots ? 1 : 0.5"
|
||||
/>
|
||||
<Controls v-if="config.showControls" />
|
||||
<MiniMap v-if="config.showMiniMap" pannable zoomable />
|
||||
|
||||
@@ -716,7 +843,8 @@ const startNewSession = () => {
|
||||
class="window-node group transition-all duration-500"
|
||||
:class="{
|
||||
'opacity-40 grayscale-[0.4] blur-[0.5px] scale-[0.98] pointer-events-none': activeNodeId && !activePath.nodeIds.has(id),
|
||||
'opacity-100 grayscale-0 blur-0 scale-105 z-50 ring-2 ring-offset-4': activePath.nodeIds.has(id)
|
||||
'opacity-100 grayscale-0 blur-0 scale-105 z-50 ring-2 ring-offset-4': activePath.nodeIds.has(id),
|
||||
'!w-[450px]': data.isDetailExpanded
|
||||
}"
|
||||
:style="{
|
||||
borderColor: activePath.nodeIds.has(id) ? config.edgeColor : config.edgeColor + '40',
|
||||
@@ -784,14 +912,38 @@ const startNewSession = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="font-bold" :style="{ color: config.edgeColor }">></span>
|
||||
<h3 class="font-black text-slate-900 tracking-tight truncate">{{ data.label }}</h3>
|
||||
<div class="flex items-start gap-2 mb-2">
|
||||
<span class="font-bold shrink-0 mt-0.5" :style="{ color: config.edgeColor }">></span>
|
||||
<h3
|
||||
class="font-black text-slate-900 tracking-tight cursor-pointer hover:text-orange-600 transition-colors"
|
||||
:class="data.isTitleExpanded ? 'whitespace-normal' : 'truncate'"
|
||||
@click.stop="updateNode(id, { data: { ...data, isTitleExpanded: !data.isTitleExpanded } })"
|
||||
>
|
||||
{{ data.label }}
|
||||
</h3>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-500 leading-relaxed font-medium line-clamp-3">
|
||||
{{ data.description }}
|
||||
</p>
|
||||
|
||||
<!-- 错误反馈显示 -->
|
||||
<div v-if="data.error" class="mt-3 p-2.5 bg-red-50 border border-red-100 rounded-lg animate-in fade-in slide-in-from-top-1 duration-300">
|
||||
<div class="flex items-start gap-2">
|
||||
<Shield class="w-3.5 h-3.5 text-red-500 shrink-0 mt-0.5" />
|
||||
<div class="flex-grow space-y-1">
|
||||
<p class="text-[10px] font-black text-red-600 leading-tight">{{ t('common.error.title') }}</p>
|
||||
<p class="text-[9px] text-red-500 leading-relaxed">{{ data.error }}</p>
|
||||
</div>
|
||||
<button
|
||||
@click.stop="data.imageUrl === null && data.isImageLoading === false ? generateNodeImage(id, data.label) : expandIdea({ id, data, position: flowNodes.find(n => n.id === id)?.position })"
|
||||
class="p-1 hover:bg-red-100 rounded transition-colors"
|
||||
:title="t('common.error.retry')"
|
||||
>
|
||||
<RefreshCw class="w-3 h-3 text-red-600" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Node Actions -->
|
||||
<div class="pt-3 mt-3 border-t border-slate-50">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
@@ -803,6 +955,14 @@ const startNewSession = () => {
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
@click.stop="deepDive(id, data.label)"
|
||||
class="action-btn text-orange-500 hover:bg-orange-50"
|
||||
:title="t('node.deepDive')"
|
||||
>
|
||||
<BookOpen class="w-2.5 h-2.5" />
|
||||
<span>{{ t('node.deepDive') }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="!data.imageUrl && !data.isImageLoading"
|
||||
@click.stop="generateNodeImage(id, data.label)"
|
||||
@@ -814,6 +974,26 @@ const startNewSession = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detailed Content Display -->
|
||||
<div v-if="data.isDetailExpanded" class="mb-4 pt-4 border-t border-slate-100 animate-in fade-in slide-in-from-top-2 duration-300">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-[9px] font-black text-slate-400 uppercase tracking-widest">{{ t('node.deepDive') }}</span>
|
||||
<button @click.stop="updateNode(id, { data: { ...data, isDetailExpanded: false } })" class="text-slate-300 hover:text-slate-500">
|
||||
<X class="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="data.isDeepDiving" class="flex flex-col items-center py-6">
|
||||
<div class="relative mb-3">
|
||||
<RefreshCw class="w-6 h-6 text-orange-400 animate-spin" />
|
||||
<div class="absolute inset-0 blur-lg bg-orange-200 opacity-50 animate-pulse"></div>
|
||||
</div>
|
||||
<span class="text-[9px] font-black text-slate-300 uppercase tracking-widest animate-pulse">{{ 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">
|
||||
{{ data.detailedContent }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Follow-up Input -->
|
||||
<div class="relative group/input">
|
||||
<div
|
||||
@@ -1000,14 +1180,55 @@ const startNewSession = () => {
|
||||
</div>
|
||||
|
||||
<!-- 全局图片预览弹窗 -->
|
||||
<div v-if="previewImageUrl" class="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 backdrop-blur-sm p-10" @click="previewImageUrl = null">
|
||||
<div class="relative max-w-full max-h-full rounded-lg overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-300" @click.stop>
|
||||
<button @click="previewImageUrl = null" class="absolute top-4 right-4 p-2 bg-black/50 hover:bg-black/70 text-white rounded-full transition-colors z-10">
|
||||
<X class="w-5 h-5" />
|
||||
</button>
|
||||
<img :src="previewImageUrl" class="max-w-screen max-h-screen object-contain" />
|
||||
<Transition name="fade">
|
||||
<div v-if="previewImageUrl" class="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 backdrop-blur-sm p-10" @click="previewImageUrl = null">
|
||||
<div class="relative max-w-full max-h-full rounded-lg overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-300" @click.stop>
|
||||
<button @click="previewImageUrl = null" class="absolute top-4 right-4 p-2 bg-black/50 hover:bg-black/70 text-white rounded-full transition-colors z-10">
|
||||
<X class="w-5 h-5" />
|
||||
</button>
|
||||
<img :src="previewImageUrl" class="max-w-screen max-h-screen object-contain" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- 自定义重置确认弹窗 -->
|
||||
<Transition name="fade">
|
||||
<div v-if="showResetConfirm" class="fixed inset-0 z-[100] flex items-center justify-center p-4">
|
||||
<div class="absolute inset-0 bg-slate-900/40 backdrop-blur-sm" @click="showResetConfirm = false"></div>
|
||||
<div class="relative bg-white rounded-2xl shadow-2xl border border-slate-100 p-6 w-full max-w-sm overflow-hidden group animate-in zoom-in duration-300">
|
||||
<!-- 背景装饰 -->
|
||||
<div class="absolute -top-12 -right-12 w-24 h-24 bg-orange-50 rounded-full blur-2xl group-hover:bg-orange-100 transition-colors"></div>
|
||||
|
||||
<div class="relative flex flex-col items-center text-center space-y-4">
|
||||
<div class="w-16 h-16 bg-orange-50 rounded-2xl flex items-center justify-center text-orange-500 mb-2 ring-4 ring-orange-50/50">
|
||||
<RefreshCw class="w-8 h-8 animate-spin-slow" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-lg font-bold text-slate-800 tracking-tight">{{ t('nav.reset') }}</h3>
|
||||
<p class="text-sm text-slate-500 leading-relaxed px-4">
|
||||
{{ t('common.confirmReset') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 w-full pt-2">
|
||||
<button
|
||||
@click="showResetConfirm = false"
|
||||
class="flex-1 px-4 py-2.5 rounded-xl border border-slate-200 text-slate-600 font-medium hover:bg-slate-50 transition-colors active:scale-95"
|
||||
>
|
||||
{{ t('common.cancel') || 'Cancel' }}
|
||||
</button>
|
||||
<button
|
||||
@click="executeReset"
|
||||
class="flex-1 px-4 py-2.5 rounded-xl bg-orange-500 text-white font-medium hover:bg-orange-600 shadow-lg shadow-orange-500/30 transition-all active:scale-95"
|
||||
>
|
||||
{{ t('common.confirm') || 'Confirm' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<!-- 底部全局操作栏 -->
|
||||
@@ -1097,11 +1318,27 @@ body {
|
||||
@apply border-current bg-opacity-10;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
@apply bg-slate-200 rounded-full hover:bg-slate-300 transition-colors;
|
||||
}
|
||||
|
||||
/* VueFlow Overrides */
|
||||
.vue-flow__node-window {
|
||||
@apply p-0 border-none bg-transparent !important;
|
||||
}
|
||||
|
||||
.vue-flow__node.selected {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
.vue-flow__controls {
|
||||
@apply !bg-white !border-slate-200 !shadow-xl !rounded-lg !left-4 md:!left-6 !bottom-28 md:!bottom-6 !transition-all;
|
||||
}
|
||||
|
||||
@@ -3,14 +3,25 @@
|
||||
"settings": "API Settings",
|
||||
"default": "DEFAULT",
|
||||
"custom": "CUSTOM",
|
||||
"save": "Save",
|
||||
"save": "Save & Close",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"loading": "Loading...",
|
||||
"generating": "Generating...",
|
||||
"expanding": "Expanding Idea...",
|
||||
"active": "Active",
|
||||
"signin": "SIGN IN",
|
||||
"tools": "Tools"
|
||||
"tools": "Tools",
|
||||
"confirmReset": "Are you sure you want to reset the canvas? All current ideas will be lost.",
|
||||
"error": {
|
||||
"title": "Request Failed",
|
||||
"cors": "Request blocked, possibly due to CORS policy or network connection issues.",
|
||||
"rateLimit": "Too many requests (429), please try again later.",
|
||||
"badRequest": "Bad request (400), please check your configuration.",
|
||||
"serverError": "Internal server error (500), please try again later.",
|
||||
"unknown": "An unknown error occurred, please check your network or API config.",
|
||||
"retry": "Retry"
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"title": "ThinkFlow AI",
|
||||
@@ -46,6 +57,7 @@
|
||||
"coreIdea": "Core Idea",
|
||||
"followUp": "Ask a follow-up...",
|
||||
"imgAction": "IMG",
|
||||
"deepDive": "Detail",
|
||||
"view": "View",
|
||||
"regenerate": "Regenerate",
|
||||
"mainTitle": "Root Node",
|
||||
@@ -53,11 +65,12 @@
|
||||
},
|
||||
"prompts": {
|
||||
"system": "You are a mind-mapping assistant that helps users expand their ideas layer by layer into a thinking tree.\n\nWorkflow:\n1. User provides an initial idea (or selects an existing node).\n2. Understand user intent based on the [Thinking Context Path] (trace from root to current node).\n3. Generate 3-5 deeper or related sub-ideas.\n4. Each sub-idea includes a short name and a minimal description.\n\nResponse format must be strict JSON:\n{'{'}\n \"nodes\": [\n {'{'} \"text\": \"Sub-idea 1 Name\", \"description\": \"One-sentence description\" {'}'},\n {'{'} \"text\": \"Sub-idea 2 Name\", \"description\": \"One-sentence description\" {'}'}\n ]\n{'}'}\n\nNote: Return ONLY JSON, no explanation.",
|
||||
"image": "Generate an illustration style image representing the theme: {prompt}. Requirements: Simple composition, bright colors, suitable as a visual aid for a mind map. NO Chinese characters or text in the image.",
|
||||
"image": "A beautiful book art illustration, theme: {prompt}. Style: Exquisite picture book aesthetic, warm tones, soft lighting, elegant and poetic composition that tells a story. Details: High-quality brushwork, minimalist yet decorative, suitable as a visual centerpiece for a mind map. STRICT REQUIREMENT: No text, letters, or characters in the image.",
|
||||
"continue": "Please continue exploring",
|
||||
"coreIdeaPrefix": "Core Idea",
|
||||
"contextPath": "Thinking Context Path",
|
||||
"selectedNode": "Current Selected Node",
|
||||
"newRequirement": "User Follow-up/New Requirement"
|
||||
"newRequirement": "User Follow-up/New Requirement",
|
||||
"deepDivePrompt": "Please provide a deep and detailed analysis of 【{topic}】. Requirements:\n1. Clear structure, including background, core principles, key elements, and practical applications.\n2. Professional yet easy-to-understand language.\n3. Total length around 300-500 words.\n4. Output the body text directly, do not use JSON format, and do not include any opening or closing remarks."
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,33 @@
|
||||
{
|
||||
"common": {
|
||||
"settings": "API 设置",
|
||||
"default": "默认模式",
|
||||
"custom": "自定义模式",
|
||||
"save": "保存",
|
||||
"default": "默认接口",
|
||||
"custom": "自定义接口",
|
||||
"save": "保存并关闭",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定",
|
||||
"loading": "加载中...",
|
||||
"generating": "生成中...",
|
||||
"expanding": "正在展开想法...",
|
||||
"expanding": "发散中...",
|
||||
"active": "激活",
|
||||
"signin": "登录",
|
||||
"tools": "工具箱"
|
||||
"tools": "工具箱",
|
||||
"confirmReset": "确定要重置画布吗?当前的所有想法都将丢失。",
|
||||
"error": {
|
||||
"title": "请求失败",
|
||||
"cors": "网络请求受阻,可能是跨域(CORS)策略或网络连接问题。",
|
||||
"rateLimit": "请求过于频繁(429),请稍后再试。",
|
||||
"badRequest": "请求参数错误(400),请检查配置。",
|
||||
"serverError": "服务器内部错误(500),请稍后再试。",
|
||||
"unknown": "发生未知错误,请检查网络或 API 配置。",
|
||||
"retry": "重试"
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"title": "ThinkFlow AI",
|
||||
"subtitle": "思维导图 & AI 助手",
|
||||
"placeholder": "在这里输入您的想法...",
|
||||
"execute": "执行发散",
|
||||
"execute": "发散思维",
|
||||
"reset": "重置",
|
||||
"fit": "适配",
|
||||
"layout": "布局",
|
||||
@@ -46,6 +57,7 @@
|
||||
"coreIdea": "核心想法",
|
||||
"followUp": "输入后续问题...",
|
||||
"imgAction": "生图",
|
||||
"deepDive": "详情",
|
||||
"view": "查看",
|
||||
"regenerate": "重新生成",
|
||||
"mainTitle": "主节点",
|
||||
@@ -53,11 +65,12 @@
|
||||
},
|
||||
"prompts": {
|
||||
"system": "你是一个思维发散助手,帮助用户将想法逐层展开,构建思维树。\n\n工作流程:\n1. 用户给出一个初始想法(或选择一个已有节点继续追问)。\n2. 你需要根据【思考上下文路径】(即从根节点到当前节点的思考链路)来理解用户的意图。\n3. 生成 3-5 个更深层或相关维度的子想法。\n4. 每个子想法包含简短名称和极简描述。\n\n返回格式必须为严格 JSON:\n{'{'}\n \"nodes\": [\n {'{'} \"text\": \"子想法1名称\", \"description\": \"一句话描述\" {'}'},\n {'{'} \"text\": \"子想法2名称\", \"description\": \"一句话描述\" {'}'}\n ]\n{'}'}\n\n注意:只返回 JSON,不附加解释。",
|
||||
"image": "生成一张插画风格的图片,表现主题:{prompt}。要求:构图简洁,色彩明快,适合作为思维导图的视觉辅助,图片中严禁出现任何中文字符。",
|
||||
"image": "这是一张精美的艺术插画,主题是:{prompt}。风格要求:具有精致的绘本感,构图优美且富有诗意,像是在讲述一个故事。细节要求:高质量的笔触,极简主义与装饰艺术结合,适合作为思维导图的视觉核心。严禁:图片中不得出现任何文字、字母或字符。",
|
||||
"continue": "请继续深入发散",
|
||||
"coreIdeaPrefix": "核心想法",
|
||||
"contextPath": "思考上下文路径",
|
||||
"selectedNode": "当前选择节点",
|
||||
"newRequirement": "用户追问/新要求"
|
||||
"newRequirement": "用户追问/新要求",
|
||||
"deepDivePrompt": "请针对【{topic}】提供一个深度且详细的解析。要求:\n1. 结构清晰,包含背景、核心原理、关键要素和实际应用。\n2. 语言专业且易懂。\n3. 总字数控制在 300-500 字左右。\n4. 直接输出正文内容,不要包含 JSON 格式,也不要包含任何开场白或结束语。"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user