#
Junjie
3 天以前 0c1110daa59bf77ddcff2704641280f417158c10
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 />
              这一层不在这里持久化维护。运行时仍由请求实时注入,比如:用户问题、告警描述、重点设备、日志范围、extraContext,以及 Agent 后续通过 MCP 拉到的实时事实数据。
            </div>
          </div>
        </div>
@@ -540,7 +601,7 @@
            当前正在编辑草稿 v{{ editor.version || '-' }}。保存只会更新这份草稿,发布后它会替换当前场景的线上版本。
          </div>
          <div v-else>
            当前是新草稿。你一新建它就会出现在左侧版本列表里;可以先保存,再单独发布。
            当前是新草稿。你现在编辑的是 4 个持久化层:Base Policy、Tool Policy、Output Contract、Scene Playbook;Dynamic 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 '正在编辑已有草稿,保存后不会影响线上版本,只有发布才会切换。';
          return '正在编辑已有草稿。修改的是 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;