write-prompt-editor.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. const fs = require('fs')
  2. const paths = [
  3. 'd:/ylrz_saas_new/saas-companyui/src/views/lobster/components/PromptStructuredEditor.vue',
  4. 'd:/ylrz_saas_new/saas-mgnui/src/views/lobster/components/PromptStructuredEditor.vue',
  5. 'd:/ylrz_saas_new/adminui/src/views/lobster/components/PromptStructuredEditor.vue'
  6. ]
  7. const content = `<template>
  8. <div class="prompt-structured-editor">
  9. <div class="editor-toolbar">
  10. <span class="toolbar-label">\u7f16\u8f91\u65b9\u5f0f</span>
  11. <el-radio-group v-model="editMode" size="mini" @change="handleModeChange">
  12. <el-radio-button label="structured">\u5206\u6bb5\u7f16\u8f91\uff08\u63a8\u8350\uff09</el-radio-button>
  13. <el-radio-button label="raw">\u539f\u6587\u7f16\u8f91</el-radio-button>
  14. </el-radio-group>
  15. </div>
  16. <div v-if="editMode === 'structured'" class="structured-body">
  17. <div v-if="!hasVariables" class="single-text">
  18. <el-input
  19. type="textarea"
  20. :rows="12"
  21. v-model="plainText"
  22. placeholder="\u8bf7\u8f93\u5165\u63d0\u793a\u8bcd\u8bf4\u660e\u6587\u5b57"
  23. @input="emitPlain"
  24. />
  25. </div>
  26. <div v-else class="segment-list">
  27. <div
  28. v-for="(seg, idx) in segments"
  29. :key="idx"
  30. :class="['segment-item', seg.type === 'var' ? 'is-var' : 'is-text']"
  31. >
  32. <template v-if="seg.type === 'var'">
  33. <div class="var-chip">
  34. <i class="el-icon-connection" />
  35. <span class="var-chip-label">{{ variableLabel(seg.name) }}</span>
  36. </div>
  37. <div class="var-chip-note">\u7cfb\u7edf\u81ea\u52a8\u586b\u5165 \u00b7 \u4e0d\u53ef\u5220\u9664\u6216\u6539\u540d</div>
  38. </template>
  39. <el-input
  40. v-else
  41. type="textarea"
  42. :autosize="{ minRows: 2, maxRows: 8 }"
  43. v-model="seg.value"
  44. placeholder="\u53ef\u4fee\u6539\u7684\u8bf4\u660e\u6587\u5b57"
  45. @input="emitFromSegments"
  46. />
  47. </div>
  48. </div>
  49. </div>
  50. <div v-else class="raw-body">
  51. <el-input
  52. type="textarea"
  53. :rows="14"
  54. v-model="rawText"
  55. placeholder="\u652f\u6301 {\u53d8\u91cf\u540d} \u6216 \${\u53d8\u91cf\u540d}\uff0c\u8fd0\u884c\u65f6\u7531\u5f15\u64ce\u81ea\u52a8\u66ff\u6362"
  56. @input="emitRaw"
  57. />
  58. <div class="raw-tip">\u9ad8\u7ea7\u6a21\u5f0f\uff1a\u8bf7\u4fdd\u7559\u6240\u6709 {\u53d8\u91cf}\uff0c\u5426\u5219\u8fd0\u884c\u65f6\u4f1a\u7f3a\u5c11\u6570\u636e\u3002</div>
  59. </div>
  60. <div v-if="showPreview" class="preview-panel">
  61. <div class="preview-title">
  62. <i class="el-icon-view" /> \u6548\u679c\u9884\u89c8\uff08\u793a\u4f8b\u6570\u636e\uff0c\u975e\u771f\u5b9e\u5ba2\u6237\u4fe1\u606f\uff09
  63. </div>
  64. <pre class="preview-content">{{ previewText }}</pre>
  65. </div>
  66. <div v-if="variableHints.length" class="variable-legend">
  67. <span class="legend-label">\u672c\u573a\u666f\u53ef\u7528\u53d8\u91cf\uff1a</span>
  68. <el-tag
  69. v-for="v in variableHints"
  70. :key="v.name"
  71. size="mini"
  72. effect="plain"
  73. type="info"
  74. class="legend-tag"
  75. >
  76. {{ v.desc }}\uff08{{ v.name }}\uff09
  77. </el-tag>
  78. </div>
  79. </div>
  80. </template>
  81. <script>
  82. import {
  83. parsePromptTemplate,
  84. buildPromptTemplate,
  85. previewPromptContent,
  86. getVariableHints,
  87. variableLabel as variableLabelFn
  88. } from '@/utils/lobster/promptCatalog'
  89. export default {
  90. name: 'PromptStructuredEditor',
  91. props: {
  92. value: {
  93. type: String,
  94. default: ''
  95. },
  96. promptKey: {
  97. type: String,
  98. default: ''
  99. },
  100. showPreview: {
  101. type: Boolean,
  102. default: true
  103. }
  104. },
  105. data() {
  106. return {
  107. editMode: 'structured',
  108. segments: [],
  109. plainText: '',
  110. rawText: '',
  111. syncing: false
  112. }
  113. },
  114. computed: {
  115. variableHints() {
  116. return getVariableHints(this.promptKey)
  117. },
  118. hasVariables() {
  119. return this.segments.some(seg => seg.type === 'var')
  120. },
  121. previewText() {
  122. return previewPromptContent(this.currentContent, this.promptKey)
  123. },
  124. currentContent() {
  125. if (this.editMode === 'raw') {
  126. return this.rawText
  127. }
  128. if (!this.hasVariables) {
  129. return this.plainText
  130. }
  131. return buildPromptTemplate(this.segments)
  132. }
  133. },
  134. watch: {
  135. value: {
  136. immediate: true,
  137. handler(val) {
  138. if (this.syncing) return
  139. this.syncFromValue(val || '')
  140. }
  141. },
  142. promptKey() {
  143. this.syncFromValue(this.value || '')
  144. }
  145. },
  146. methods: {
  147. variableLabel(name) {
  148. return variableLabelFn(name, this.promptKey)
  149. },
  150. syncFromValue(val) {
  151. this.rawText = val
  152. this.segments = parsePromptTemplate(val)
  153. this.plainText = val
  154. },
  155. emitContent(content) {
  156. this.syncing = true
  157. this.$emit('input', content)
  158. this.$nextTick(() => {
  159. this.syncing = false
  160. })
  161. },
  162. emitFromSegments() {
  163. this.emitContent(buildPromptTemplate(this.segments))
  164. },
  165. emitPlain() {
  166. this.emitContent(this.plainText)
  167. },
  168. emitRaw() {
  169. this.emitContent(this.rawText)
  170. },
  171. handleModeChange(mode) {
  172. if (mode === 'raw') {
  173. this.rawText = this.currentContent
  174. this.emitRaw()
  175. return
  176. }
  177. this.syncFromValue(this.rawText || this.value || '')
  178. this.emitFromSegments()
  179. }
  180. }
  181. }
  182. </script>
  183. <style scoped lang="scss">
  184. .prompt-structured-editor {
  185. .editor-toolbar {
  186. display: flex;
  187. align-items: center;
  188. gap: 12px;
  189. margin-bottom: 12px;
  190. .toolbar-label {
  191. font-size: 13px;
  192. color: #606266;
  193. }
  194. }
  195. .segment-list {
  196. border: 1px solid #ebeef5;
  197. border-radius: 4px;
  198. padding: 8px;
  199. max-height: 360px;
  200. overflow-y: auto;
  201. background: #fafafa;
  202. }
  203. .segment-item {
  204. margin-bottom: 8px;
  205. &:last-child {
  206. margin-bottom: 0;
  207. }
  208. &.is-var {
  209. padding: 8px 10px;
  210. background: #f0f2f5;
  211. border: 1px dashed #c0c4cc;
  212. border-radius: 4px;
  213. }
  214. }
  215. .var-chip {
  216. display: flex;
  217. align-items: center;
  218. gap: 6px;
  219. color: #606266;
  220. font-size: 13px;
  221. font-weight: 500;
  222. .el-icon-connection {
  223. color: #909399;
  224. }
  225. }
  226. .var-chip-note {
  227. margin-top: 4px;
  228. font-size: 12px;
  229. color: #909399;
  230. }
  231. .raw-tip {
  232. margin-top: 8px;
  233. font-size: 12px;
  234. color: #e6a23c;
  235. }
  236. .preview-panel {
  237. margin-top: 14px;
  238. border: 1px solid #dcdfe6;
  239. border-radius: 4px;
  240. background: #fff;
  241. }
  242. .preview-title {
  243. padding: 8px 12px;
  244. font-size: 13px;
  245. color: #606266;
  246. background: #f5f7fa;
  247. border-bottom: 1px solid #ebeef5;
  248. }
  249. .preview-content {
  250. margin: 0;
  251. padding: 12px;
  252. white-space: pre-wrap;
  253. word-break: break-word;
  254. font-size: 13px;
  255. line-height: 1.6;
  256. color: #303133;
  257. max-height: 200px;
  258. overflow-y: auto;
  259. font-family: inherit;
  260. }
  261. .variable-legend {
  262. margin-top: 12px;
  263. line-height: 1.8;
  264. .legend-label {
  265. font-size: 12px;
  266. color: #909399;
  267. margin-right: 6px;
  268. }
  269. .legend-tag {
  270. margin: 2px 4px 2px 0;
  271. }
  272. }
  273. }
  274. </style>
  275. `
  276. paths.forEach(p => {
  277. fs.writeFileSync(p, content, 'utf8')
  278. console.log('wrote', p)
  279. })