From 0c1110daa59bf77ddcff2704641280f417158c10 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期四, 12 三月 2026 14:48:26 +0800
Subject: [PATCH] #

---
 src/main/java/com/zy/ai/service/impl/AiPromptComposerServiceImpl.java |   42 +++
 src/main/java/com/zy/ai/service/impl/AiPromptTemplateServiceImpl.java |  286 ++++++++++++++++++---
 src/main/java/com/zy/ai/config/AiPromptTemplateInitializer.java       |    4 
 src/main/java/com/zy/ai/utils/AiPromptUtils.java                      |  119 +++++++++
 src/main/java/com/zy/ai/mapper/AiPromptBlockMapper.java               |   11 
 src/main/java/com/zy/ai/enums/AiPromptBlockType.java                  |   49 +++
 src/main/webapp/views/ai/prompt_config.html                           |  134 ++++++++-
 src/main/resources/sql/20260312_create_sys_ai_prompt_block.sql        |   13 +
 src/main/java/com/zy/ai/controller/AiPromptTemplateController.java    |   10 
 src/main/java/com/zy/ai/entity/AiPromptTemplate.java                  |   16 +
 src/main/java/com/zy/ai/entity/AiPromptBlock.java                     |   42 +++
 src/main/java/com/zy/ai/service/AiPromptComposerService.java          |    8 
 src/main/java/com/zy/ai/service/AiPromptTemplateService.java          |    4 
 13 files changed, 665 insertions(+), 73 deletions(-)

diff --git a/src/main/java/com/zy/ai/config/AiPromptTemplateInitializer.java b/src/main/java/com/zy/ai/config/AiPromptTemplateInitializer.java
index 7e84560..fe88e98 100644
--- a/src/main/java/com/zy/ai/config/AiPromptTemplateInitializer.java
+++ b/src/main/java/com/zy/ai/config/AiPromptTemplateInitializer.java
@@ -25,8 +25,8 @@
     @PostConstruct
     public void init() {
         try (Connection connection = dataSource.getConnection()) {
-            if (!hasTable(connection, "sys_ai_prompt_template")) {
-                log.warn("Skip AI prompt initialization because table sys_ai_prompt_template does not exist");
+            if (!hasTable(connection, "sys_ai_prompt_template") || !hasTable(connection, "sys_ai_prompt_block")) {
+                log.warn("Skip AI prompt initialization because prompt tables do not exist");
                 return;
             }
             int changed = aiPromptTemplateService.initDefaultsIfMissing();
diff --git a/src/main/java/com/zy/ai/controller/AiPromptTemplateController.java b/src/main/java/com/zy/ai/controller/AiPromptTemplateController.java
index 35566e9..295f802 100644
--- a/src/main/java/com/zy/ai/controller/AiPromptTemplateController.java
+++ b/src/main/java/com/zy/ai/controller/AiPromptTemplateController.java
@@ -45,7 +45,7 @@
             wrapper.eq("status", status);
         }
         wrapper.orderByAsc("scene_code").orderByDesc("version").orderByDesc("id");
-        List<AiPromptTemplate> list = aiPromptTemplateService.list(wrapper);
+        List<AiPromptTemplate> list = aiPromptTemplateService.enrichTemplates(aiPromptTemplateService.list(wrapper));
         return R.ok(list);
     }
 
@@ -53,7 +53,7 @@
     @ManagerAuth
     public R active(@RequestParam("sceneCode") String sceneCode) {
         try {
-            return R.ok(aiPromptTemplateService.resolvePublished(sceneCode));
+            return R.ok(aiPromptTemplateService.enrichTemplate(aiPromptTemplateService.resolvePublished(sceneCode)));
         } catch (IllegalArgumentException | IllegalStateException e) {
             return R.error(e.getMessage());
         }
@@ -63,7 +63,7 @@
     @ManagerAuth
     public R save(@RequestBody AiPromptTemplate template) {
         try {
-            return R.ok(aiPromptTemplateService.savePrompt(template, getUserId()));
+            return R.ok(aiPromptTemplateService.enrichTemplate(aiPromptTemplateService.savePrompt(template, getUserId())));
         } catch (IllegalArgumentException e) {
             return R.error(e.getMessage());
         }
@@ -73,7 +73,7 @@
     @ManagerAuth
     public R publish(@RequestParam("id") Long id) {
         try {
-            return R.ok(aiPromptTemplateService.publishPrompt(id, getUserId()));
+            return R.ok(aiPromptTemplateService.enrichTemplate(aiPromptTemplateService.publishPrompt(id, getUserId())));
         } catch (IllegalArgumentException e) {
             return R.error(e.getMessage());
         }
@@ -83,7 +83,7 @@
     @ManagerAuth
     public R cancelPublish(@RequestParam("id") Long id) {
         try {
-            return R.ok(aiPromptTemplateService.cancelPublish(id, getUserId()));
+            return R.ok(aiPromptTemplateService.enrichTemplate(aiPromptTemplateService.cancelPublish(id, getUserId())));
         } catch (IllegalArgumentException e) {
             return R.error(e.getMessage());
         }
diff --git a/src/main/java/com/zy/ai/entity/AiPromptBlock.java b/src/main/java/com/zy/ai/entity/AiPromptBlock.java
new file mode 100644
index 0000000..779589c
--- /dev/null
+++ b/src/main/java/com/zy/ai/entity/AiPromptBlock.java
@@ -0,0 +1,42 @@
+package com.zy.ai.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("sys_ai_prompt_block")
+public class AiPromptBlock implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("template_id")
+    private Long templateId;
+
+    @TableField("block_type")
+    private String blockType;
+
+    private String content;
+
+    @TableField("sort_no")
+    private Integer sortNo;
+
+    /**
+     * 1 鍚敤 0 绂佺敤
+     */
+    private Short status;
+
+    @TableField("create_time")
+    private Date createTime;
+
+    @TableField("update_time")
+    private Date updateTime;
+}
diff --git a/src/main/java/com/zy/ai/entity/AiPromptTemplate.java b/src/main/java/com/zy/ai/entity/AiPromptTemplate.java
index a915108..f4e65e3 100644
--- a/src/main/java/com/zy/ai/entity/AiPromptTemplate.java
+++ b/src/main/java/com/zy/ai/entity/AiPromptTemplate.java
@@ -8,6 +8,7 @@
 
 import java.io.Serializable;
 import java.util.Date;
+import java.util.List;
 
 @Data
 @TableName("sys_ai_prompt_template")
@@ -53,4 +54,19 @@
     private Date updateTime;
 
     private String memo;
+
+    @TableField(exist = false)
+    private String basePolicy;
+
+    @TableField(exist = false)
+    private String toolPolicy;
+
+    @TableField(exist = false)
+    private String outputContract;
+
+    @TableField(exist = false)
+    private String scenePlaybook;
+
+    @TableField(exist = false)
+    private List<AiPromptBlock> blocks;
 }
diff --git a/src/main/java/com/zy/ai/enums/AiPromptBlockType.java b/src/main/java/com/zy/ai/enums/AiPromptBlockType.java
new file mode 100644
index 0000000..691a9a8
--- /dev/null
+++ b/src/main/java/com/zy/ai/enums/AiPromptBlockType.java
@@ -0,0 +1,49 @@
+package com.zy.ai.enums;
+
+public enum AiPromptBlockType {
+
+    BASE_POLICY("base_policy", "Base Policy", "鍩虹绛栫暐", 10),
+    TOOL_POLICY("tool_policy", "Tool Policy", "宸ュ叿绛栫暐", 20),
+    OUTPUT_CONTRACT("output_contract", "Output Contract", "杈撳嚭绾﹀畾", 30),
+    SCENE_PLAYBOOK("scene_playbook", "Scene Playbook", "鍦烘櫙绛栫暐", 40);
+
+    private final String code;
+    private final String title;
+    private final String label;
+    private final int sort;
+
+    AiPromptBlockType(String code, String title, String label, int sort) {
+        this.code = code;
+        this.title = title;
+        this.label = label;
+        this.sort = sort;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public int getSort() {
+        return sort;
+    }
+
+    public static AiPromptBlockType ofCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (AiPromptBlockType item : values()) {
+            if (item.code.equals(code)) {
+                return item;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/com/zy/ai/mapper/AiPromptBlockMapper.java b/src/main/java/com/zy/ai/mapper/AiPromptBlockMapper.java
new file mode 100644
index 0000000..d827798
--- /dev/null
+++ b/src/main/java/com/zy/ai/mapper/AiPromptBlockMapper.java
@@ -0,0 +1,11 @@
+package com.zy.ai.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zy.ai.entity.AiPromptBlock;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiPromptBlockMapper extends BaseMapper<AiPromptBlock> {
+}
diff --git a/src/main/java/com/zy/ai/service/AiPromptComposerService.java b/src/main/java/com/zy/ai/service/AiPromptComposerService.java
new file mode 100644
index 0000000..f5203e8
--- /dev/null
+++ b/src/main/java/com/zy/ai/service/AiPromptComposerService.java
@@ -0,0 +1,8 @@
+package com.zy.ai.service;
+
+import com.zy.ai.entity.AiPromptTemplate;
+
+public interface AiPromptComposerService {
+
+    String compose(AiPromptTemplate template);
+}
diff --git a/src/main/java/com/zy/ai/service/AiPromptTemplateService.java b/src/main/java/com/zy/ai/service/AiPromptTemplateService.java
index f2a24f7..3329a8a 100644
--- a/src/main/java/com/zy/ai/service/AiPromptTemplateService.java
+++ b/src/main/java/com/zy/ai/service/AiPromptTemplateService.java
@@ -16,6 +16,10 @@
 
     AiPromptTemplate cancelPublish(Long id, Long operatorUserId);
 
+    AiPromptTemplate enrichTemplate(AiPromptTemplate template);
+
+    List<AiPromptTemplate> enrichTemplates(List<AiPromptTemplate> templates);
+
     boolean deletePrompt(Long id);
 
     int initDefaultsIfMissing();
diff --git a/src/main/java/com/zy/ai/service/impl/AiPromptComposerServiceImpl.java b/src/main/java/com/zy/ai/service/impl/AiPromptComposerServiceImpl.java
new file mode 100644
index 0000000..160d018
--- /dev/null
+++ b/src/main/java/com/zy/ai/service/impl/AiPromptComposerServiceImpl.java
@@ -0,0 +1,42 @@
+package com.zy.ai.service.impl;
+
+import com.zy.ai.entity.AiPromptTemplate;
+import com.zy.ai.enums.AiPromptBlockType;
+import com.zy.ai.service.AiPromptComposerService;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service("aiPromptComposerService")
+public class AiPromptComposerServiceImpl implements AiPromptComposerService {
+
+    @Override
+    public String compose(AiPromptTemplate template) {
+        if (template == null) {
+            return "";
+        }
+        List<String> sections = new ArrayList<>();
+        appendSection(sections, AiPromptBlockType.BASE_POLICY, template.getBasePolicy());
+        appendSection(sections, AiPromptBlockType.TOOL_POLICY, template.getToolPolicy());
+        appendSection(sections, AiPromptBlockType.OUTPUT_CONTRACT, template.getOutputContract());
+        appendSection(sections, AiPromptBlockType.SCENE_PLAYBOOK, template.getScenePlaybook());
+        return String.join("\n\n", sections).trim();
+    }
+
+    private void appendSection(List<String> sections, AiPromptBlockType type, String content) {
+        String value = trim(content);
+        if (value == null) {
+            return;
+        }
+        sections.add("銆�" + type.getLabel() + "銆慭n" + value);
+    }
+
+    private String trim(String value) {
+        if (value == null) {
+            return null;
+        }
+        String trimmed = value.trim();
+        return trimmed.isEmpty() ? null : trimmed;
+    }
+}
diff --git a/src/main/java/com/zy/ai/service/impl/AiPromptTemplateServiceImpl.java b/src/main/java/com/zy/ai/service/impl/AiPromptTemplateServiceImpl.java
index 004d3fb..10780e6 100644
--- a/src/main/java/com/zy/ai/service/impl/AiPromptTemplateServiceImpl.java
+++ b/src/main/java/com/zy/ai/service/impl/AiPromptTemplateServiceImpl.java
@@ -3,9 +3,13 @@
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zy.ai.entity.AiPromptBlock;
 import com.zy.ai.entity.AiPromptTemplate;
+import com.zy.ai.enums.AiPromptBlockType;
 import com.zy.ai.enums.AiPromptScene;
+import com.zy.ai.mapper.AiPromptBlockMapper;
 import com.zy.ai.mapper.AiPromptTemplateMapper;
+import com.zy.ai.service.AiPromptComposerService;
 import com.zy.ai.service.AiPromptTemplateService;
 import com.zy.ai.utils.AiPromptUtils;
 import lombok.RequiredArgsConstructor;
@@ -14,8 +18,10 @@
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.ArrayList;
-import java.util.Date;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -24,7 +30,20 @@
 @RequiredArgsConstructor
 public class AiPromptTemplateServiceImpl extends ServiceImpl<AiPromptTemplateMapper, AiPromptTemplate> implements AiPromptTemplateService {
 
+    private static final Comparator<AiPromptBlock> BLOCK_SORT = (a, b) -> {
+        int sa = a != null && a.getSortNo() != null ? a.getSortNo() : Integer.MAX_VALUE;
+        int sb = b != null && b.getSortNo() != null ? b.getSortNo() : Integer.MAX_VALUE;
+        if (sa != sb) {
+            return sa - sb;
+        }
+        long ia = a != null && a.getId() != null ? a.getId() : Long.MAX_VALUE;
+        long ib = b != null && b.getId() != null ? b.getId() : Long.MAX_VALUE;
+        return Long.compare(ia, ib);
+    };
+
     private final AiPromptUtils aiPromptUtils;
+    private final AiPromptBlockMapper aiPromptBlockMapper;
+    private final AiPromptComposerService aiPromptComposerService;
 
     @Override
     public AiPromptTemplate resolvePublished(String sceneCode) {
@@ -33,18 +52,15 @@
         if (prompt == null) {
             synchronized (("ai_prompt_scene_init_" + scene.getCode()).intern()) {
                 prompt = findPublished(scene.getCode());
-                if (prompt == null) {
-                    if (findLatest(scene.getCode()) == null) {
-                        prompt = ensurePublishedScene(scene);
-                    }
+                if (prompt == null && findLatest(scene.getCode()) == null) {
+                    prompt = ensurePublishedScene(scene);
                 }
             }
         }
-
         if (prompt == null) {
             throw new IllegalStateException("褰撳墠鍦烘櫙娌℃湁宸插彂甯� Prompt锛宻ceneCode=" + scene.getCode());
         }
-        return prompt;
+        return enrichTemplate(prompt);
     }
 
     @Override
@@ -54,9 +70,9 @@
             throw new IllegalArgumentException("Prompt 涓嶈兘涓虹┖");
         }
         AiPromptScene scene = requireScene(template.getSceneCode());
-        String content = template.getContent();
-        if (content == null || content.trim().isEmpty()) {
-            throw new IllegalArgumentException("Prompt 鍐呭涓嶈兘涓虹┖");
+        String compiled = buildCompiledPrompt(template);
+        if (compiled.isEmpty()) {
+            throw new IllegalArgumentException("Prompt 鍒嗘鍐呭涓嶈兘涓虹┖");
         }
 
         if (template.getId() == null) {
@@ -65,12 +81,13 @@
             entity.setName(defaultName(scene, version, template.getName()));
             entity.setSceneCode(scene.getCode());
             entity.setVersion(version);
-            entity.setContent(content);
+            entity.setContent(compiled);
             entity.setStatus(normalizeStatus(template.getStatus()));
             entity.setPublished((short) 0);
             entity.setCreatedBy(operatorUserId);
             entity.setMemo(trim(template.getMemo()));
             this.save(entity);
+            upsertBlocks(entity.getId(), extractBlockContentMap(template));
             return entity;
         }
 
@@ -82,14 +99,15 @@
             throw new IllegalArgumentException("涓嶅厑璁镐慨鏀� Prompt 鎵�灞炲満鏅�");
         }
         if (Short.valueOf((short) 1).equals(db.getPublished())) {
-            throw new IllegalArgumentException("宸插彂甯� Prompt 涓嶅厑璁哥洿鎺ヤ慨鏀癸紝璇锋柊寤虹増鏈悗鍐嶅彂甯�");
+            throw new IllegalArgumentException("宸插彂甯� Prompt 涓嶅厑璁哥洿鎺ヤ慨鏀癸紝璇峰厛鍙栨秷鍙戝竷鍚庡啀淇濆瓨");
         }
 
         db.setName(defaultName(scene, db.getVersion() == null ? 1 : db.getVersion(), template.getName()));
-        db.setContent(content);
+        db.setContent(compiled);
         db.setStatus(normalizeStatus(template.getStatus()));
         db.setMemo(trim(template.getMemo()));
         this.updateById(db);
+        upsertBlocks(db.getId(), extractBlockContentMap(template));
         return db;
     }
 
@@ -103,7 +121,9 @@
         if (db == null) {
             throw new IllegalArgumentException("Prompt 涓嶅瓨鍦�");
         }
-        if (db.getContent() == null || db.getContent().trim().isEmpty()) {
+        db = enrichTemplate(db);
+        String compiled = buildCompiledPrompt(db);
+        if (compiled.isEmpty()) {
             throw new IllegalArgumentException("Prompt 鍐呭涓嶈兘涓虹┖");
         }
 
@@ -114,10 +134,8 @@
         db.setPublished((short) 1);
         db.setStatus((short) 1);
         db.setPublishedBy(operatorUserId);
-        db.setPublishedTime(new Date());
-        if (db.getVersion() == null || db.getVersion() <= 0) {
-            db.setVersion(nextVersion(db.getSceneCode()));
-        }
+        db.setPublishedTime(new java.util.Date());
+        db.setContent(compiled);
         if (db.getName() == null || db.getName().trim().isEmpty()) {
             AiPromptScene scene = requireScene(db.getSceneCode());
             db.setName(defaultName(scene, db.getVersion(), null));
@@ -147,6 +165,65 @@
     }
 
     @Override
+    public AiPromptTemplate enrichTemplate(AiPromptTemplate template) {
+        if (template == null) {
+            return null;
+        }
+        if (template.getId() == null) {
+            template.setContent(buildCompiledPrompt(template));
+            return template;
+        }
+
+        List<AiPromptBlock> blocks = loadBlocks(template.getId());
+        if (blocks.isEmpty()) {
+            migrateLegacyTemplateBlocks(template);
+            blocks = loadBlocks(template.getId());
+        }
+        applyBlocks(template, blocks);
+        return template;
+    }
+
+    @Override
+    public List<AiPromptTemplate> enrichTemplates(List<AiPromptTemplate> templates) {
+        if (templates == null || templates.isEmpty()) {
+            return templates == null ? Collections.emptyList() : templates;
+        }
+
+        List<Long> templateIds = new ArrayList<>();
+        for (AiPromptTemplate template : templates) {
+            if (template != null && template.getId() != null) {
+                templateIds.add(template.getId());
+            }
+        }
+        Map<Long, List<AiPromptBlock>> blockMap = groupBlocks(loadBlocks(templateIds));
+        boolean migrated = false;
+        for (AiPromptTemplate template : templates) {
+            if (template == null || template.getId() == null) {
+                continue;
+            }
+            List<AiPromptBlock> blocks = blockMap.get(template.getId());
+            if (blocks == null || blocks.isEmpty()) {
+                migrateLegacyTemplateBlocks(template);
+                migrated = true;
+            }
+        }
+        if (migrated) {
+            blockMap = groupBlocks(loadBlocks(templateIds));
+        }
+        for (AiPromptTemplate template : templates) {
+            if (template == null) {
+                continue;
+            }
+            if (template.getId() == null) {
+                template.setContent(buildCompiledPrompt(template));
+                continue;
+            }
+            applyBlocks(template, blockMap.get(template.getId()));
+        }
+        return templates;
+    }
+
+    @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean deletePrompt(Long id) {
         if (id == null) {
@@ -157,8 +234,9 @@
             return false;
         }
         if (Short.valueOf((short) 1).equals(db.getPublished())) {
-            throw new IllegalArgumentException("宸插彂甯� Prompt 涓嶅厑璁稿垹闄わ紝璇峰厛鍙戝竷鍏朵粬鐗堟湰");
+            throw new IllegalArgumentException("宸插彂甯� Prompt 涓嶅厑璁稿垹闄わ紝璇峰厛鍙栨秷鍙戝竷");
         }
+        aiPromptBlockMapper.delete(new QueryWrapper<AiPromptBlock>().eq("template_id", id));
         return this.removeById(id);
     }
 
@@ -170,6 +248,12 @@
             AiPromptTemplate latest = findLatest(scene.getCode());
             if (latest == null) {
                 ensurePublishedScene(scene);
+                changed++;
+                continue;
+            }
+            List<AiPromptBlock> blocks = loadBlocks(latest.getId());
+            if (blocks.isEmpty()) {
+                migrateLegacyTemplateBlocks(latest);
                 changed++;
             }
         }
@@ -189,33 +273,145 @@
     }
 
     private AiPromptTemplate ensurePublishedScene(AiPromptScene scene) {
-        AiPromptTemplate latest = findLatest(scene.getCode());
-        if (latest == null) {
-            AiPromptTemplate seed = new AiPromptTemplate();
-            seed.setName(defaultName(scene, 1, null));
-            seed.setSceneCode(scene.getCode());
-            seed.setVersion(1);
-            seed.setContent(aiPromptUtils.getDefaultPrompt(scene.getCode()));
-            seed.setStatus((short) 1);
-            seed.setPublished((short) 1);
-            seed.setPublishedTime(new Date());
-            seed.setMemo("绯荤粺鍒濆鍖栭粯璁� Prompt");
-            this.save(seed);
-            log.info("Initialized default AI prompt, sceneCode={}, version={}", scene.getCode(), seed.getVersion());
-            return seed;
+        LinkedHashMap<AiPromptBlockType, String> blocks = aiPromptUtils.getDefaultPromptBlocks(scene);
+        AiPromptTemplate seed = new AiPromptTemplate();
+        seed.setName(defaultName(scene, 1, null));
+        seed.setSceneCode(scene.getCode());
+        seed.setVersion(1);
+        seed.setStatus((short) 1);
+        seed.setPublished((short) 1);
+        seed.setPublishedTime(new java.util.Date());
+        seed.setMemo("绯荤粺鍒濆鍖栭粯璁� Prompt");
+        applyBlockFields(seed, blocks);
+        seed.setContent(buildCompiledPrompt(seed));
+        this.save(seed);
+        upsertBlocks(seed.getId(), blocks);
+        log.info("Initialized default AI prompt blocks, sceneCode={}, version={}", scene.getCode(), seed.getVersion());
+        return seed;
+    }
+
+    private void migrateLegacyTemplateBlocks(AiPromptTemplate template) {
+        if (template == null || template.getId() == null) {
+            return;
+        }
+        AiPromptScene scene = requireScene(template.getSceneCode());
+        LinkedHashMap<AiPromptBlockType, String> blocks = aiPromptUtils.resolveStoredOrDefaultBlocks(scene, template.getContent());
+        upsertBlocks(template.getId(), blocks);
+        applyBlockFields(template, blocks);
+        template.setContent(buildCompiledPrompt(template));
+        this.updateById(template);
+    }
+
+    private void applyBlocks(AiPromptTemplate template, List<AiPromptBlock> blocks) {
+        List<AiPromptBlock> ordered = blocks == null ? new ArrayList<>() : new ArrayList<>(blocks);
+        ordered.sort(BLOCK_SORT);
+        template.setBlocks(ordered);
+
+        LinkedHashMap<AiPromptBlockType, String> blockContent = new LinkedHashMap<>();
+        for (AiPromptBlockType type : AiPromptBlockType.values()) {
+            blockContent.put(type, "");
+        }
+        for (AiPromptBlock block : ordered) {
+            AiPromptBlockType type = AiPromptBlockType.ofCode(block.getBlockType());
+            if (type == null) {
+                continue;
+            }
+            blockContent.put(type, block.getContent());
+        }
+        applyBlockFields(template, blockContent);
+        template.setContent(buildCompiledPrompt(template));
+    }
+
+    private void applyBlockFields(AiPromptTemplate template, LinkedHashMap<AiPromptBlockType, String> blockContent) {
+        template.setBasePolicy(valueOf(blockContent, AiPromptBlockType.BASE_POLICY));
+        template.setToolPolicy(valueOf(blockContent, AiPromptBlockType.TOOL_POLICY));
+        template.setOutputContract(valueOf(blockContent, AiPromptBlockType.OUTPUT_CONTRACT));
+        template.setScenePlaybook(valueOf(blockContent, AiPromptBlockType.SCENE_PLAYBOOK));
+    }
+
+    private String valueOf(LinkedHashMap<AiPromptBlockType, String> blockContent, AiPromptBlockType type) {
+        if (blockContent == null) {
+            return "";
+        }
+        String value = blockContent.get(type);
+        return value == null ? "" : value;
+    }
+
+    private String buildCompiledPrompt(AiPromptTemplate template) {
+        String compiled = aiPromptComposerService.compose(template);
+        return compiled == null ? "" : compiled.trim();
+    }
+
+    private LinkedHashMap<AiPromptBlockType, String> extractBlockContentMap(AiPromptTemplate template) {
+        LinkedHashMap<AiPromptBlockType, String> blocks = new LinkedHashMap<>();
+        blocks.put(AiPromptBlockType.BASE_POLICY, defaultString(template.getBasePolicy()));
+        blocks.put(AiPromptBlockType.TOOL_POLICY, defaultString(template.getToolPolicy()));
+        blocks.put(AiPromptBlockType.OUTPUT_CONTRACT, defaultString(template.getOutputContract()));
+        blocks.put(AiPromptBlockType.SCENE_PLAYBOOK, defaultString(template.getScenePlaybook()));
+        return blocks;
+    }
+
+    private void upsertBlocks(Long templateId, LinkedHashMap<AiPromptBlockType, String> blockContent) {
+        List<AiPromptBlock> existingBlocks = loadBlocks(templateId);
+        HashMap<String, AiPromptBlock> existingMap = new HashMap<>();
+        for (AiPromptBlock block : existingBlocks) {
+            existingMap.put(block.getBlockType(), block);
         }
 
-        UpdateWrapper<AiPromptTemplate> clearWrapper = new UpdateWrapper<>();
-        clearWrapper.eq("scene_code", scene.getCode()).set("published", 0);
-        this.update(clearWrapper);
-
-        latest.setStatus((short) 1);
-        latest.setPublished((short) 1);
-        if (latest.getPublishedTime() == null) {
-            latest.setPublishedTime(new Date());
+        for (AiPromptBlockType type : AiPromptBlockType.values()) {
+            AiPromptBlock block = existingMap.get(type.getCode());
+            if (block == null) {
+                block = new AiPromptBlock();
+                block.setTemplateId(templateId);
+                block.setBlockType(type.getCode());
+                block.setSortNo(type.getSort());
+                block.setStatus((short) 1);
+                block.setContent(defaultString(blockContent.get(type)));
+                aiPromptBlockMapper.insert(block);
+                continue;
+            }
+            block.setSortNo(type.getSort());
+            block.setStatus((short) 1);
+            block.setContent(defaultString(blockContent.get(type)));
+            aiPromptBlockMapper.updateById(block);
         }
-        this.updateById(latest);
-        return latest;
+    }
+
+    private List<AiPromptBlock> loadBlocks(Long templateId) {
+        if (templateId == null) {
+            return Collections.emptyList();
+        }
+        return aiPromptBlockMapper.selectList(new QueryWrapper<AiPromptBlock>()
+                .eq("template_id", templateId)
+                .orderByAsc("sort_no")
+                .orderByAsc("id"));
+    }
+
+    private List<AiPromptBlock> loadBlocks(List<Long> templateIds) {
+        if (templateIds == null || templateIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+        return aiPromptBlockMapper.selectList(new QueryWrapper<AiPromptBlock>()
+                .in("template_id", templateIds)
+                .orderByAsc("sort_no")
+                .orderByAsc("id"));
+    }
+
+    private Map<Long, List<AiPromptBlock>> groupBlocks(List<AiPromptBlock> blocks) {
+        HashMap<Long, List<AiPromptBlock>> result = new HashMap<>();
+        if (blocks == null) {
+            return result;
+        }
+        for (AiPromptBlock block : blocks) {
+            if (block == null || block.getTemplateId() == null) {
+                continue;
+            }
+            result.computeIfAbsent(block.getTemplateId(), k -> new ArrayList<>()).add(block);
+        }
+        for (List<AiPromptBlock> list : result.values()) {
+            list.sort(BLOCK_SORT);
+        }
+        return result;
     }
 
     private AiPromptTemplate findPublished(String sceneCode) {
@@ -281,4 +477,8 @@
         String trimmed = value.trim();
         return trimmed.isEmpty() ? null : trimmed;
     }
+
+    private String defaultString(String value) {
+        return value == null ? "" : value;
+    }
 }
diff --git a/src/main/java/com/zy/ai/utils/AiPromptUtils.java b/src/main/java/com/zy/ai/utils/AiPromptUtils.java
index 3fed8c0..e0a0e88 100644
--- a/src/main/java/com/zy/ai/utils/AiPromptUtils.java
+++ b/src/main/java/com/zy/ai/utils/AiPromptUtils.java
@@ -1,7 +1,10 @@
 package com.zy.ai.utils;
 
+import com.zy.ai.enums.AiPromptBlockType;
 import com.zy.ai.enums.AiPromptScene;
 import org.springframework.stereotype.Component;
+
+import java.util.LinkedHashMap;
 
 @Component
 public class AiPromptUtils {
@@ -24,6 +27,122 @@
         throw new IllegalArgumentException("涓嶆敮鎸佺殑 Prompt 鍦烘櫙: " + scene.getCode());
     }
 
+    public LinkedHashMap<AiPromptBlockType, String> getDefaultPromptBlocks(String sceneCode) {
+        AiPromptScene scene = AiPromptScene.ofCode(sceneCode);
+        if (scene == null) {
+            throw new IllegalArgumentException("涓嶆敮鎸佺殑 Prompt 鍦烘櫙: " + sceneCode);
+        }
+        return getDefaultPromptBlocks(scene);
+    }
+
+    public LinkedHashMap<AiPromptBlockType, String> getDefaultPromptBlocks(AiPromptScene scene) {
+        LinkedHashMap<AiPromptBlockType, String> blocks = new LinkedHashMap<>();
+        if (scene == AiPromptScene.DIAGNOSE_STREAM) {
+            blocks.put(AiPromptBlockType.BASE_POLICY,
+                    "浣犳槸涓�鍚嶈祫娣� WCS锛堜粨鍌ㄦ帶鍒剁郴缁燂級涓庤嚜鍔ㄥ寲绔嬪簱涓撳锛岀啛鎮夛細鍫嗗灈鏈恒�佽緭閫佺嚎銆佹彁鍗囨満銆佺┛姊溅绛夎澶囩殑浠诲姟鍒嗛厤鍜岃繍琛岄�昏緫锛屼篃鐔熸倝甯歌鐨勭郴缁熷崱姝汇�佷换鍔′笉鎵ц銆佽澶囩┖闂蹭絾鏃犱换鍔$瓑闂妯″紡銆�");
+            blocks.put(AiPromptBlockType.TOOL_POLICY,
+                    "浣犲彲浠ユ寜闇�璋冪敤绯荤粺鎻愪緵鐨勫伐鍏蜂互鑾峰彇瀹炴椂鏁版嵁涓庝笂涓嬫枃锛堝伐鍏疯繑鍥� JSON锛夛細\n" +
+                            "- 浠诲姟锛歵ask_query\n" +
+                            "- 璁惧瀹炴椂鐘舵�侊細device_get_crn_status / device_get_station_status / device_get_rgv_status\n" +
+                            "- 鏃ュ織锛歭og_query\n" +
+                            "- 璁惧閰嶇疆锛歝onfig_get_device_config\n" +
+                            "- 绯荤粺閰嶇疆锛歝onfig_get_system_config\n\n" +
+                            "浣跨敤绛栫暐锛歕n" +
+                            "1锛夐伩鍏嶈噯娴嬨�傚淇℃伅涓嶈冻锛屽厛璋冪敤鐩稿簲宸ュ叿鏀堕泦蹇呰鏁版嵁锛涘彲澶氳疆璋冪敤銆俓n" +
+                            "2锛夊宸ュ叿杩斿洖鐨� JSON 鍏堣繘琛岀粨鏋勫寲褰掔撼锛屾彁鐐煎叧閿瓧娈碉紝鍐嶅仛鎺ㄧ悊銆俓n" +
+                            "3锛変紭鍏堥『搴忥細浠诲姟鈫掕澶囩姸鎬佲啋鏃ュ織鈫掗厤缃紱鎸夐渶璋冩暣銆俓n\n" +
+                            "濡傞渶瑕侀澶栨暟鎹紝璇峰厛璋冪敤鍚堥�傜殑宸ュ叿鍐嶇户缁洖绛斻��");
+            blocks.put(AiPromptBlockType.OUTPUT_CONTRACT,
+                    "璇锋寜浠ヤ笅缁撴瀯杈撳嚭璇婃柇缁撴灉锛堜娇鐢ㄧ畝浣撲腑鏂囷級锛歕n" +
+                            "1. 闂姒傝堪锛�1-3 鍙ヨ瘽锛屾鎷綋鍓嶇郴缁熺姸鎬侊級\n" +
+                            "2. 鍙枒璁惧鍒楄〃锛堝垪鍑� 1-N 涓澶囩紪鍙凤紝骞惰鏄庢瘡涓澶囦负浠�涔堝彲鐤戯紝渚嬪锛氶厤缃鐢�/闀挎椂闂寸┖闂�/鐘舵�佸紓甯�/浠诲姟鍒嗛厤涓嶅埌瀹冪瓑锛塡n" +
+                            "3. 鍙兘鍘熷洜锛堜粠浠诲姟鍒嗛厤銆佽澶囩姸鎬併�侀厤缃敊璇�佹帴鍙�/閫氫俊寮傚父绛夎搴︼紝鍒楀嚭 3-7 鏉★級\n" +
+                            "4. 寤鸿鎺掓煡姝ラ锛堟楠� 1銆�2銆�3...锛屾瘡姝ヨ灏介噺鍏蜂綋銆佸彲鎿嶄綔锛屼緥濡傦細鍦ㄦ煇椤甸潰鏌ョ湅鏌愬瓧娈点�佹鏌ユ煇涓紑鍏炽�佸姣旀煇涓姸鎬佷綅绛夛級\n" +
+                            "5. 椋庨櫓璇勪及锛堣鏄庡綋鍓嶉棶棰樺涓氬姟褰卞搷绋嬪害锛氶珮/涓�/浣庯紝浠ュ強鏄惁闇�瑕佺珛鍗充汉宸ュ共棰勶級");
+            blocks.put(AiPromptBlockType.SCENE_PLAYBOOK,
+                    "浣犵殑鐩爣鏄細甯姪鐜板満杩愮淮浜哄憳鍒嗘瀽锛屼负浠�涔堢郴缁熷綋鍓嶄笉鎵ц浠诲姟锛屾垨鑰呬换鍔℃墽琛屾晥鐜囧紓甯革紝鎸囧嚭鍙兘鏄摢浜涜澶囧鑷寸殑闂銆�");
+            return blocks;
+        }
+        if (scene == AiPromptScene.SENSOR_CHAT) {
+            blocks.put(AiPromptBlockType.BASE_POLICY,
+                    "浣犳槸涓�鍚嶈祫娣� WCS锛堜粨鍌ㄦ帶鍒剁郴缁燂級涓庤嚜鍔ㄥ寲绔嬪簱涓撳锛孿n" +
+                            "绮鹃�氬爢鍨涙満銆佽緭閫佺嚎銆佹彁鍗囨満銆佺┛姊溅銆丷GV銆佸伐浣嶇瓑璁惧鐨刓n" +
+                            "浠诲姟鍒嗛厤銆佽繍琛岀姸鎬佹祦杞笌寮傚父澶勭悊銆俓n\n" +
+                            "浣犵殑鑱岃矗鏄細**鍩轰簬瀹炴椂鏁版嵁杩涜宸ョ▼绾ц瘖鏂紝鑰屼笉鏄嚟缁忛獙鐚滄祴銆�**");
+            blocks.put(AiPromptBlockType.TOOL_POLICY,
+                    "==================== 宸ヤ綔瑙勫垯锛堥潪甯搁噸瑕侊級 ====================\n\n" +
+                            "1. **绂佹鍦ㄦ湭鑾峰彇瀹炴椂鏁版嵁鐨勬儏鍐典笅鐩存帴涓嬬粨璁恒��**\n" +
+                            "   - 鑻ラ棶棰樻秹鍙娾�滃綋鍓嶇姸鎬� / 鏄惁鍗℃ / 鏄惁鏈変换鍔� / 鏄惁寮傚父鈥濓紝\n" +
+                            "     浣犲繀椤诲厛璋冪敤宸ュ叿鑾峰彇鏁版嵁锛屽啀杩涜鍒嗘瀽銆俓n\n" +
+                            "2. **浼樺厛浣跨敤鏈�灏戜笖鏈�鐩稿叧鐨勫伐鍏疯皟鐢ㄣ��**\n" +
+                            "   - 鏁翠綋璇婃柇鏃讹紝鍏堟煡浠诲姟涓庡叧閿澶囩姸鎬併�俓n" +
+                            "   - 闇�瑕佽ˉ璇佹嵁鏃讹紝鍐嶆煡鏃ュ織鎴栭厤缃�俓n\n" +
+                            "3. **褰撲俊鎭笉瓒充互鍒ゆ柇鏃讹紝涓嶅緱鐚滄祴鍘熷洜銆�**\n" +
+                            "   - 蹇呴』鏄庣‘鎸囧嚭鈥滅己灏戝摢浜涙暟鎹�濓紝骞惰皟鐢ㄥ搴斿伐鍏疯幏鍙栥�俓n\n" +
+                            "4. **宸ュ叿杩斿洖鐨勬暟鎹槸浜嬪疄渚濇嵁锛屽繀椤诲紩鐢ㄥ叾鍏抽敭淇℃伅杩涜鎺ㄧ悊銆�**\n\n" +
+                            "==================== 鍙敤宸ュ叿锛堣繑鍥� JSON锛� ====================\n\n" +
+                            "銆愪换鍔$浉鍏炽�慭n" +
+                            "- task_query锛氭寜浠诲姟鍙枫�佺姸鎬併�佽澶囥�佹潯鐮併�佸簱浣嶇瓑鏉′欢鏌ヨ浠诲姟\n" +
+                            "\n銆愯澶囧疄鏃剁姸鎬併�慭n" +
+                            "- device_get_crn_status锛氬爢鍨涙満瀹炴椂鐘舵�乗n" +
+                            "- device_get_station_status锛氬伐浣嶅疄鏃剁姸鎬乗n" +
+                            "- device_get_rgv_status锛歊GV / 绌挎杞﹀疄鏃剁姸鎬乗n" +
+                            "\n銆愭棩蹇椼�慭n" +
+                            "- log_query锛氭煡璇㈢郴缁�/璁惧鏃ュ織\n" +
+                            "\n銆愰厤缃�慭n" +
+                            "- config_get_device_config锛氳澶囬厤缃甛n" +
+                            "- config_get_system_config锛氱郴缁熺骇閰嶇疆");
+            blocks.put(AiPromptBlockType.OUTPUT_CONTRACT,
+                    "==================== 杈撳嚭瑕佹眰 ====================\n\n" +
+                            "- 浣跨敤**绠�娲併�佹槑纭殑涓枃**\n" +
+                            "- 閬垮厤娉涙硾鑰岃皥銆侀伩鍏嶁�滃彲鑳�/涔熻鈥濆紡绌烘硾鎻忚堪\n" +
+                            "- 鑻ラ渶瑕佽繘涓�姝ユ暟鎹紝璇�**鍏堣皟鐢ㄥ伐鍏凤紝鍐嶇户缁垎鏋�**");
+            blocks.put(AiPromptBlockType.SCENE_PLAYBOOK,
+                    "==================== 鎺ㄨ崘璇婃柇娴佺▼ ====================\n\n" +
+                            "褰撴帴鍒拌瘖鏂姹傛椂锛岃閬靛惊浠ヤ笅姝ラ锛歕n\n" +
+                            "Step 1 鏄庣‘璇婃柇鐩爣\n" +
+                            "- 褰撳墠瑕佸垽鏂殑鏄細璁惧鏄惁寮傚父锛熶换鍔℃槸鍚﹀崱姝伙紵璋冨害鏄惁闃诲锛焅n\n" +
+                            "Step 2 璋冪敤蹇呰宸ュ叿鑾峰彇浜嬪疄鏁版嵁\n" +
+                            "- 璁惧鐘舵�� 鈫� 鏄惁鍦ㄧ嚎 / 鏄惁绌洪棽 / 褰撳墠浠诲姟\n" +
+                            "- 浠诲姟鐘舵�� 鈫� 鏄惁瀛樺湪寰呮墽琛�/鎸傝捣浠诲姟\n" +
+                            "- 鏃ュ織 鈫� 鏄惁瀛樺湪鍏抽敭寮傚父銆佺瓑寰呯‘璁ゃ�佸懡浠ゆ湭鍝嶅簲绛変俊鎭痋n\n" +
+                            "Step 3 鍩轰簬鏁版嵁杩涜閫昏緫鍒嗘瀽\n" +
+                            "- 浣跨敤 WCS 涓撲笟鐭ヨ瘑杩涜鍥犳灉鍒ゆ柇锛堣�岄潪鐚滄祴锛塡n\n" +
+                            "Step 4 杈撳嚭缁撴瀯鍖栫粨璁篭n" +
+                            "- 銆愮幇璞℃�荤粨銆慭n" +
+                            "- 銆愬叧閿瘉鎹紙鏉ヨ嚜宸ュ叿杩斿洖锛夈�慭n" +
+                            "- 銆愬彲鑳藉師鍥狅紙鎸変紭鍏堢骇锛夈�慭n" +
+                            "- 銆愬彲鎵ц鐨勬帓鏌� / 澶勭悊寤鸿銆�");
+            return blocks;
+        }
+        throw new IllegalArgumentException("涓嶆敮鎸佺殑 Prompt 鍦烘櫙: " + scene.getCode());
+    }
+
+    public LinkedHashMap<AiPromptBlockType, String> resolveStoredOrDefaultBlocks(AiPromptScene scene, String legacyContent) {
+        String content = trim(legacyContent);
+        if (content == null) {
+            return getDefaultPromptBlocks(scene);
+        }
+        if ((scene == AiPromptScene.DIAGNOSE_STREAM && content.equals(getAiDiagnosePromptMcp()))
+                || (scene == AiPromptScene.SENSOR_CHAT && content.equals(getWcsSensorPromptMcp()))) {
+            return getDefaultPromptBlocks(scene);
+        }
+        LinkedHashMap<AiPromptBlockType, String> blocks = new LinkedHashMap<>();
+        blocks.put(AiPromptBlockType.BASE_POLICY, "");
+        blocks.put(AiPromptBlockType.TOOL_POLICY, "");
+        blocks.put(AiPromptBlockType.OUTPUT_CONTRACT, "");
+        blocks.put(AiPromptBlockType.SCENE_PLAYBOOK, content);
+        return blocks;
+    }
+
+    private String trim(String value) {
+        if (value == null) {
+            return null;
+        }
+        String trimmed = value.trim();
+        return trimmed.isEmpty() ? null : trimmed;
+    }
+
     //AI璇婃柇绯荤粺Prompt
     public String getAiDiagnosePromptMcp() {
         String prompt = "浣犳槸涓�鍚嶈祫娣� WCS锛堜粨鍌ㄦ帶鍒剁郴缁燂級涓庤嚜鍔ㄥ寲绔嬪簱涓撳锛岀啛鎮夛細鍫嗗灈鏈恒�佽緭閫佺嚎銆佹彁鍗囨満銆佺┛姊溅绛夎澶囩殑浠诲姟鍒嗛厤鍜岃繍琛岄�昏緫锛屼篃鐔熸倝甯歌鐨勭郴缁熷崱姝汇�佷换鍔′笉鎵ц銆佽澶囩┖闂蹭絾鏃犱换鍔$瓑闂妯″紡銆俓n\n" +
diff --git a/src/main/resources/sql/20260312_create_sys_ai_prompt_block.sql b/src/main/resources/sql/20260312_create_sys_ai_prompt_block.sql
new file mode 100644
index 0000000..89f6883
--- /dev/null
+++ b/src/main/resources/sql/20260312_create_sys_ai_prompt_block.sql
@@ -0,0 +1,13 @@
+CREATE TABLE IF NOT EXISTS `sys_ai_prompt_block` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+  `template_id` BIGINT NOT NULL COMMENT '妯℃澘ID',
+  `block_type` VARCHAR(64) NOT NULL COMMENT '鍒嗘绫诲瀷',
+  `content` LONGTEXT NOT NULL COMMENT '鍒嗘鍐呭',
+  `sort_no` INT NOT NULL DEFAULT 0 COMMENT '鎺掑簭鍙�',
+  `status` TINYINT NOT NULL DEFAULT 1 COMMENT '鐘舵��:1鍚敤0绂佺敤',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_sys_ai_prompt_block_template_type` (`template_id`, `block_type`),
+  KEY `idx_sys_ai_prompt_block_template_sort` (`template_id`, `sort_no`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI Prompt 妯℃澘鍒嗘琛�';
diff --git a/src/main/webapp/views/ai/prompt_config.html b/src/main/webapp/views/ai/prompt_config.html
index fc91051..6125dd8 100644
--- a/src/main/webapp/views/ai/prompt_config.html
+++ b/src/main/webapp/views/ai/prompt_config.html
@@ -265,14 +265,33 @@
       font-size: 12px;
       color: #6f859d;
     }
-    .editor-textarea .el-textarea__inner {
-      min-height: 430px !important;
+    .block-textarea .el-textarea__inner {
+      min-height: 190px !important;
       font-family: Menlo, Monaco, Consolas, "Liberation Mono", monospace;
       font-size: 12px;
       line-height: 1.7;
       color: #243447;
       background: #fbfdff;
       border-color: #dbe7f3;
+    }
+    .preview-textarea .el-textarea__inner {
+      min-height: 260px !important;
+      font-family: Menlo, Monaco, Consolas, "Liberation Mono", monospace;
+      font-size: 12px;
+      line-height: 1.75;
+      color: #203246;
+      background: linear-gradient(180deg, #fcfdff 0%, #f7fbff 100%);
+      border-color: #d5e3f1;
+    }
+    .dynamic-context-card {
+      margin-top: 10px;
+      padding: 10px 12px;
+      border-radius: 12px;
+      border: 1px solid #e2ebf5;
+      background: linear-gradient(180deg, #fdfefe 0%, #f5fbfa 100%);
+      color: #5f7288;
+      font-size: 12px;
+      line-height: 1.75;
     }
     .editor-hint {
       margin-top: 10px;
@@ -512,14 +531,56 @@
             <div class="field-label">澶囨敞</div>
             <el-input v-model="editor.memo" size="mini" placeholder="璁板綍杩欐浼樺寲鐩殑鎴栨祴璇曠粨璁�"></el-input>
           </div>
-          <div class="editor-full">
-            <div class="field-label">Prompt鍐呭</div>
+          <div>
+            <div class="field-label">Base Policy</div>
             <el-input
-              class="editor-textarea"
+              class="block-textarea"
               type="textarea"
-              v-model="editor.content"
-              placeholder="鍦ㄨ繖閲岀紪杈� Prompt 鍐呭"
-              :autosize="{ minRows: 20, maxRows: 28 }"></el-input>
+              v-model="editor.basePolicy"
+              placeholder="瑙掕壊銆佸簳绾胯鍒欍�佺ǔ瀹氱害鏉�"
+              :autosize="{ minRows: 8, maxRows: 14 }"></el-input>
+          </div>
+          <div>
+            <div class="field-label">Tool Policy</div>
+            <el-input
+              class="block-textarea"
+              type="textarea"
+              v-model="editor.toolPolicy"
+              placeholder="浠�涔堟椂鍊欏繀椤绘煡 MCP銆佹�庝箞浣跨敤宸ュ叿鍜屽紩鐢ㄨ瘉鎹�"
+              :autosize="{ minRows: 8, maxRows: 14 }"></el-input>
+          </div>
+          <div class="editor-full">
+            <div class="field-label">Output Contract</div>
+            <el-input
+              class="block-textarea"
+              type="textarea"
+              v-model="editor.outputContract"
+              placeholder="杈撳嚭鏍煎紡銆佺粨鏋勩�佺姝簨椤广�佸伐绋嬪寲瑕佹眰"
+              :autosize="{ minRows: 7, maxRows: 12 }"></el-input>
+          </div>
+          <div class="editor-full">
+            <div class="field-label">Scene Playbook</div>
+            <el-input
+              class="block-textarea"
+              type="textarea"
+              v-model="editor.scenePlaybook"
+              placeholder="鍦烘櫙绛栫暐锛屾瘮濡備换鍔′笉鎵ц銆佽澶囧紓甯搞�佷汉宸ラ棶绛旂瓑"
+              :autosize="{ minRows: 10, maxRows: 16 }"></el-input>
+          </div>
+          <div class="editor-full">
+            <div class="field-label">缁勮棰勮</div>
+            <el-input
+              class="preview-textarea"
+              type="textarea"
+              :value="assembledPromptPreview"
+              readonly
+              :autosize="{ minRows: 12, maxRows: 18 }"></el-input>
+          </div>
+          <div class="editor-full">
+            <div class="dynamic-context-card">
+              <strong>Dynamic Context</strong><br />
+              杩欎竴灞備笉鍦ㄨ繖閲屾寔涔呭寲缁存姢銆傝繍琛屾椂浠嶇敱璇锋眰瀹炴椂娉ㄥ叆锛屾瘮濡傦細鐢ㄦ埛闂銆佸憡璀︽弿杩般�侀噸鐐硅澶囥�佹棩蹇楄寖鍥淬�乪xtraContext锛屼互鍙� Agent 鍚庣画閫氳繃 MCP 鎷夊埌鐨勫疄鏃朵簨瀹炴暟鎹��
+            </div>
           </div>
         </div>
 
@@ -540,7 +601,7 @@
             褰撳墠姝e湪缂栬緫鑽夌 v{{ editor.version || '-' }}銆備繚瀛樺彧浼氭洿鏂拌繖浠借崏绋匡紝鍙戝竷鍚庡畠浼氭浛鎹㈠綋鍓嶅満鏅殑绾夸笂鐗堟湰銆�
           </div>
           <div v-else>
-            褰撳墠鏄柊鑽夌銆備綘涓�鏂板缓瀹冨氨浼氬嚭鐜板湪宸︿晶鐗堟湰鍒楄〃閲岋紱鍙互鍏堜繚瀛橈紝鍐嶅崟鐙彂甯冦��
+            褰撳墠鏄柊鑽夌銆備綘鐜板湪缂栬緫鐨勬槸 4 涓寔涔呭寲灞傦細Base Policy銆乀ool Policy銆丱utput Contract銆丼cene Playbook锛汥ynamic Context 缁х画鐢辫繍琛屾椂娉ㄥ叆銆�
           </div>
         </div>
       </div>
@@ -570,6 +631,10 @@
           sceneCode: '',
           version: null,
           content: '',
+          basePolicy: '',
+          toolPolicy: '',
+          outputContract: '',
+          scenePlaybook: '',
           status: 1,
           published: 0,
           memo: '',
@@ -622,19 +687,22 @@
           return '鍏堜粠宸︿晶閫夋嫨鍦烘櫙锛屾垨鑰呯洿鎺ユ柊寤鸿崏绋裤��';
         }
         if (this.editor.published === 1) {
-          return '宸插彂甯冪増鏈彧浣滀负鏌ョ湅鍜屽鍒舵潵婧愶紝涓嶇洿鎺ヨ鐩栦慨鏀广��';
+          return '褰撳墠鏄凡鍙戝竷鐗堟湰锛�4 灞傚唴瀹瑰彧鑳芥煡鐪嬶紱鑻ヨ淇敼锛屽厛鍙栨秷鍙戝竷銆�';
         }
         if (this.editor.id) {
-          return '姝e湪缂栬緫宸叉湁鑽夌锛屼繚瀛樺悗涓嶄細褰卞搷绾夸笂鐗堟湰锛屽彧鏈夊彂甯冩墠浼氬垏鎹€��';
+          return '姝e湪缂栬緫宸叉湁鑽夌銆備慨鏀圭殑鏄� Prompt 鐨� 4 涓寔涔呭寲灞傦紝淇濆瓨涓嶄細褰卞搷绾夸笂鐗堟湰銆�';
         }
-        return '褰撳墠鏄柊鑽夌锛屽彲浠ヨ嚜鐢辩紪杈戝苟淇濆瓨銆�';
+        return '褰撳墠鏄柊鑽夌銆備綘鍙互鍒嗗眰缂栬緫 Prompt锛屽啀閫氳繃鍙充晶棰勮妫�鏌ユ渶缁堟嫾鎺ユ晥鏋溿��';
+      },
+      assembledPromptPreview: function() {
+        return this.composePromptPreview(this.editor);
       },
       contentCharCount: function() {
-        return this.editor.content ? this.editor.content.length : 0;
+        return this.assembledPromptPreview ? this.assembledPromptPreview.length : 0;
       },
       contentLineCount: function() {
-        if (!this.editor.content) return 0;
-        return this.editor.content.split(/\r?\n/).length;
+        if (!this.assembledPromptPreview) return 0;
+        return this.assembledPromptPreview.split(/\r?\n/).length;
       }
     },
     methods: {
@@ -645,6 +713,10 @@
           sceneCode: '',
           version: null,
           content: '',
+          basePolicy: '',
+          toolPolicy: '',
+          outputContract: '',
+          scenePlaybook: '',
           status: 1,
           published: 0,
           memo: '',
@@ -686,6 +758,10 @@
           sceneCode: item && item.sceneCode ? item.sceneCode : '',
           version: item && item.version != null ? item.version : null,
           content: item && item.content ? item.content : '',
+          basePolicy: item && item.basePolicy ? item.basePolicy : '',
+          toolPolicy: item && item.toolPolicy ? item.toolPolicy : '',
+          outputContract: item && item.outputContract ? item.outputContract : '',
+          scenePlaybook: item && item.scenePlaybook ? item.scenePlaybook : '',
           status: item && item.status != null ? item.status : 1,
           published: item && item.published != null ? item.published : 0,
           memo: item && item.memo ? item.memo : '',
@@ -722,6 +798,19 @@
       },
       buildLocalDraftKey: function() {
         return 'draft_' + Date.now() + '_' + Math.floor(Math.random() * 100000);
+      },
+      composePromptPreview: function(editor) {
+        var sections = [];
+        var append = function(label, content) {
+          var value = content == null ? '' : String(content).trim();
+          if (!value) return;
+          sections.push('銆�' + label + '銆慭n' + value);
+        };
+        append('鍩虹绛栫暐', editor && editor.basePolicy);
+        append('宸ュ叿绛栫暐', editor && editor.toolPolicy);
+        append('杈撳嚭绾﹀畾', editor && editor.outputContract);
+        append('鍦烘櫙绛栫暐', editor && editor.scenePlaybook);
+        return sections.join('\n\n');
       },
       hasUnsavedEditorForScene: function(sceneCode) {
         return !!(this.editor
@@ -885,15 +974,14 @@
         this.cloneFromTemplate(this.currentPublishedTemplate);
       },
       buildSavePayload: function() {
-        var payloadId = this.editor.id;
-        if (this.editor.published === 1) {
-          payloadId = null;
-        }
         return {
-          id: payloadId,
+          id: this.editor.id,
           name: this.editor.name,
           sceneCode: this.editor.sceneCode,
-          content: this.editor.content,
+          basePolicy: this.editor.basePolicy,
+          toolPolicy: this.editor.toolPolicy,
+          outputContract: this.editor.outputContract,
+          scenePlaybook: this.editor.scenePlaybook,
           status: this.editor.status,
           memo: this.editor.memo
         };
@@ -911,8 +999,8 @@
           self.$message.warning('璇烽�夋嫨鍦烘櫙');
           return;
         }
-        if (!self.editor.content || !self.editor.content.trim()) {
-          self.$message.warning('Prompt鍐呭涓嶈兘涓虹┖');
+        if (!self.assembledPromptPreview || !self.assembledPromptPreview.trim()) {
+          self.$message.warning('鑷冲皯濉啓涓�涓� Prompt 鍒嗘');
           return;
         }
         self.saving = true;

--
Gitblit v1.9.1