refactor: simplify AI panel workflow - direct file modification and reload from disk
This commit is contained in:
@@ -19,15 +19,9 @@ function toggleMode() {
|
|||||||
function buildMessage(userInput: string): string {
|
function buildMessage(userInput: string): string {
|
||||||
if (mode.value !== 'json') return `需求: ${userInput}`
|
if (mode.value !== 'json') return `需求: ${userInput}`
|
||||||
|
|
||||||
let ctx = `当前编辑文件: ${store.sourcePath}\n`
|
let ctx = `修改文件 ${store.sourcePath}`
|
||||||
if (store.selectedScene) {
|
if (store.selectedScene) ctx += `,针对场景节点 ${store.selectedScene.id}`
|
||||||
ctx += `选中场景: ${store.selectedScene.id}\n`
|
ctx += `。需求: ${userInput}`
|
||||||
ctx += `当前场景 JSON:\n${JSON.stringify(store.selectedScene, null, 2)}\n\n`
|
|
||||||
} else if (Object.keys(store.gameData.scenes).length > 0) {
|
|
||||||
const { scenes, ...rest } = store.gameData
|
|
||||||
ctx += `全局配置:\n${JSON.stringify(rest, null, 2)}\n\n`
|
|
||||||
}
|
|
||||||
ctx += `需求: ${userInput}`
|
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,14 +45,8 @@ async function send() {
|
|||||||
if (newSid) store.setAISessionId(newSid)
|
if (newSid) store.setAISessionId(newSid)
|
||||||
|
|
||||||
if (mode.value === 'json') {
|
if (mode.value === 'json') {
|
||||||
const clean = result.replace(/^```json\n?|\n?```$/g, '').trim()
|
messages.value.push({ role: 'assistant', content: result || '已完成' })
|
||||||
try {
|
await store.reloadFromDisk()
|
||||||
JSON.parse(clean)
|
|
||||||
store.setAIResult(clean)
|
|
||||||
messages.value.push({ role: 'assistant', content: '已生成 JSON,请查看编辑器面板' })
|
|
||||||
} catch {
|
|
||||||
messages.value.push({ role: 'assistant', content: result })
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
messages.value.push({ role: 'assistant', content: result || '已完成' })
|
messages.value.push({ role: 'assistant', content: result || '已完成' })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,29 +17,6 @@ const store = useEditorStore()
|
|||||||
const jsonText = ref('')
|
const jsonText = ref('')
|
||||||
const errorMsg = ref('')
|
const errorMsg = ref('')
|
||||||
const saved = ref(false)
|
const saved = ref(false)
|
||||||
const showAcceptReject = ref(false)
|
|
||||||
const preAIValue = ref('')
|
|
||||||
|
|
||||||
watch(() => store.aiResult, (result) => {
|
|
||||||
if (!result) return
|
|
||||||
preAIValue.value = jsonText.value
|
|
||||||
jsonText.value = result
|
|
||||||
showAcceptReject.value = true
|
|
||||||
errorMsg.value = ''
|
|
||||||
})
|
|
||||||
|
|
||||||
function acceptAI() {
|
|
||||||
showAcceptReject.value = false
|
|
||||||
jsonText.value = jsonText.value
|
|
||||||
store.setAIResult('')
|
|
||||||
onBlur()
|
|
||||||
}
|
|
||||||
|
|
||||||
function rejectAI() {
|
|
||||||
jsonText.value = preAIValue.value
|
|
||||||
showAcceptReject.value = false
|
|
||||||
store.setAIResult('')
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => [props.scene, store.gameData] as const, () => {
|
watch(() => [props.scene, store.gameData] as const, () => {
|
||||||
errorMsg.value = ''
|
errorMsg.value = ''
|
||||||
@@ -79,11 +56,7 @@ function onBlur() {
|
|||||||
<div class="editor-header">
|
<div class="editor-header">
|
||||||
<h3>{{ scene ? scene.id : '全局配置' }}</h3>
|
<h3>{{ scene ? scene.id : '全局配置' }}</h3>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<span v-if="showAcceptReject" class="ai-actions">
|
<span v-if="saved" class="saved-hint">已保存</span>
|
||||||
<button class="ai-accept-btn" @click.stop="acceptAI">接受</button>
|
|
||||||
<button class="ai-reject-btn" @click.stop="rejectAI">撤销</button>
|
|
||||||
</span>
|
|
||||||
<span v-else-if="saved" class="saved-hint">已保存</span>
|
|
||||||
<span v-if="errorMsg" class="error-hint">JSON 错误: {{ errorMsg }}</span>
|
<span v-if="errorMsg" class="error-hint">JSON 错误: {{ errorMsg }}</span>
|
||||||
<button class="icon-btn" @click="store.showAIPanel = true" title="AI 助手">🤖</button>
|
<button class="icon-btn" @click="store.showAIPanel = true" title="AI 助手">🤖</button>
|
||||||
<button v-if="scene" class="icon-btn danger" @click="emit('deleteScene', scene.id)" title="删除场景">🗑</button>
|
<button v-if="scene" class="icon-btn danger" @click="emit('deleteScene', scene.id)" title="删除场景">🗑</button>
|
||||||
@@ -175,33 +148,6 @@ function onBlur() {
|
|||||||
.icon-btn:hover { color: #ddd; border-color: rgba(255,255,255,0.25); }
|
.icon-btn:hover { color: #ddd; border-color: rgba(255,255,255,0.25); }
|
||||||
.icon-btn.danger:hover { color: #e74c3c; border-color: #e74c3c; }
|
.icon-btn.danger:hover { color: #e74c3c; border-color: #e74c3c; }
|
||||||
|
|
||||||
.ai-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ai-accept-btn {
|
|
||||||
padding: 3px 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
color: #fff;
|
|
||||||
background: #4caf50;
|
|
||||||
border: none;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.ai-accept-btn:hover { background: #388e3c; }
|
|
||||||
|
|
||||||
.ai-reject-btn {
|
|
||||||
padding: 3px 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
color: #fff;
|
|
||||||
background: #e74c3c;
|
|
||||||
border: none;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.ai-reject-btn:hover { background: #c62828; }
|
|
||||||
|
|
||||||
.json-area {
|
.json-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
const sourcePath = ref('/scenes/demo.json')
|
const sourcePath = ref('/scenes/demo.json')
|
||||||
const deepseekKey = ref(localStorage.getItem('deepseek_key') || '')
|
const deepseekKey = ref(localStorage.getItem('deepseek_key') || '')
|
||||||
const showAIPanel = ref(false)
|
const showAIPanel = ref(false)
|
||||||
const aiResult = ref('')
|
|
||||||
const aiSessionId = ref('')
|
const aiSessionId = ref('')
|
||||||
|
|
||||||
const selectedScene = computed(() => {
|
const selectedScene = computed(() => {
|
||||||
@@ -142,8 +141,6 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
|
|
||||||
function clearAISession() { aiSessionId.value = ''; localStorage.removeItem('editor_ai_session') }
|
function clearAISession() { aiSessionId.value = ''; localStorage.removeItem('editor_ai_session') }
|
||||||
|
|
||||||
function setAIResult(r: string) { aiResult.value = r }
|
|
||||||
|
|
||||||
async function autoSave() {
|
async function autoSave() {
|
||||||
try {
|
try {
|
||||||
await fetch('/api/save', {
|
await fetch('/api/save', {
|
||||||
@@ -154,11 +151,21 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
} catch { /* dev server not running */ }
|
} catch { /* dev server not running */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function reloadFromDisk() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(sourcePath.value)
|
||||||
|
const data = await resp.json()
|
||||||
|
gameData.value = data
|
||||||
|
selectedNodeId.value = null
|
||||||
|
clearAISession()
|
||||||
|
} catch { /* failed to reload */ }
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
gameData, selectedNodeId, selectedScene, startSceneId, dirty, sourcePath,
|
gameData, selectedNodeId, selectedScene, startSceneId, dirty, sourcePath,
|
||||||
deepseekKey, showAIPanel, aiResult, aiSessionId,
|
deepseekKey, showAIPanel, aiSessionId,
|
||||||
markDirty, loadJSON, exportJSON, addScene, deleteScene,
|
markDirty, loadJSON, exportJSON, addScene, deleteScene,
|
||||||
updateScene, addChoice, updateChoice, deleteChoice, generateId,
|
updateScene, addChoice, updateChoice, deleteChoice, generateId,
|
||||||
setSourcePath, setDeepseekKey, setAISessionId, clearAISession, setAIResult, autoSave,
|
setSourcePath, setDeepseekKey, setAISessionId, clearAISession, autoSave, reloadFromDisk,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ function apiSavePlugin() {
|
|||||||
|
|
||||||
const modePrefix = mode === 'code'
|
const modePrefix = mode === 'code'
|
||||||
? `当前项目根目录: ${__dirname}\n代码模式:直接修改 src/ 下的源码文件并保存。需求:`
|
? `当前项目根目录: ${__dirname}\n代码模式:直接修改 src/ 下的源码文件并保存。需求:`
|
||||||
: `当前项目根目录: ${__dirname}\nJSON模式:只返回修改后的 JSON 文本,不要写任何文件。需求:`
|
: `当前项目根目录: ${__dirname}\nJSON模式:直接修改 JSON 配置文件并保存,用自然语言回复修改了什么。需求:`
|
||||||
const fullMessage = modePrefix + userMessage
|
const fullMessage = modePrefix + userMessage
|
||||||
|
|
||||||
const args = ['run', '--model', 'deepseek/deepseek-v4-pro', '--format', 'json']
|
const args = ['run', '--model', 'deepseek/deepseek-v4-pro', '--format', 'json']
|
||||||
@@ -131,16 +131,8 @@ function apiSavePlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mode === 'json') {
|
if (mode === 'json') {
|
||||||
const codeBlock = aiText.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/)
|
|
||||||
let jsonStr = codeBlock ? codeBlock[1] : aiText
|
|
||||||
try {
|
|
||||||
JSON.parse(jsonStr)
|
|
||||||
} catch {
|
|
||||||
const bareMatch = aiText.match(/(\{[\s\S]*\}|\[[\s\S]*\])/)
|
|
||||||
jsonStr = bareMatch ? bareMatch[0] : aiText
|
|
||||||
}
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' })
|
res.writeHead(200, { 'Content-Type': 'application/json' })
|
||||||
res.end(JSON.stringify({ result: jsonStr, sessionId: resolvedSessionId || '' }))
|
res.end(JSON.stringify({ result: aiText || 'done', sessionId: resolvedSessionId || '' }))
|
||||||
} else {
|
} else {
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' })
|
res.writeHead(200, { 'Content-Type': 'application/json' })
|
||||||
res.end(JSON.stringify({ result: aiText || 'done', sessionId: resolvedSessionId || '' }))
|
res.end(JSON.stringify({ result: aiText || 'done', sessionId: resolvedSessionId || '' }))
|
||||||
|
|||||||
Reference in New Issue
Block a user