fix: Prompt generate

--bug=1061828 --user=张展玮 【提示词生成】- AI对话节点中,生成的提示词点击替换不管用 https://www.tapd.cn/62980211/s/1773916
This commit is contained in:
zhangzhanwei 2025-09-16 14:22:47 +08:00 committed by zhanweizhang7
parent b4cdd87e6e
commit e489a635af
5 changed files with 170 additions and 28 deletions

View File

@ -135,6 +135,12 @@ export default {
label: 'Generate',
generatePrompt: 'Generate Prompt',
placeholder: 'Please enter the prompt topic',
title: 'The prompt is displayed here',
remake: 'Regenerate',
stop: 'Stop Generating',
continue: 'Continue Generating',
replace: 'Replace',
exit: 'Are you sure you want to exit and discard the AI-generated content?',
},
dialog: {
addKnowledge: 'Add Related Knowledge',

View File

@ -127,6 +127,12 @@ export default {
label: '生成',
generatePrompt: '生成提示词',
placeholder: '请输入提示词主题',
title: '提示词显示在这里',
remake: '重新生成',
stop: '停止生成',
continue: '继续生成',
replace: '替换',
exit: '确认退出并舍弃 AI 生成的内容吗?',
},
dialog: {
addKnowledge: '添加关联知识库',

View File

@ -126,6 +126,12 @@ export default {
label: '生成',
generatePrompt: '生成提示詞',
placeholder: '請輸入提示詞主題',
title: '提示詞顯示在這裡',
remake: '重新生成',
stop: '停止生成',
continue: '繼續生成',
replace: '替換',
exit: '確認退出並捨棄 AI 生成的內容嗎?',
},
dialog: {
addKnowledge: '新增關聯知識庫',

View File

@ -5,8 +5,9 @@
v-model="dialogVisible"
style="width: 600px"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="false"
:close-on-click-modal="true"
:close-on-press-escape="true"
:before-close="handleDialogClose"
>
<div class="generate-prompt-dialog-bg border-r-8">
<div class="scrollbar-height">
@ -28,13 +29,13 @@
</p>
<p v-else class="flex align-center">
<AppIcon iconName="app-generate-star" class="color-primary mr-4"></AppIcon>
提示词显示在这里
{{ $t('views.application.generateDialog.title') }}
</p>
</el-scrollbar>
<div v-if="answer && !loading">
<el-button type="primary" @click="() => emit('replace', answer)"> 替换 </el-button>
<div v-if="answer && !loading && !isStreaming && !showContinueButton">
<el-button type="primary" @click="() => emit('replace', answer)"> {{ $t('views.application.generateDialog.replace') }} </el-button>
<el-button @click="reAnswerClick" :disabled="!answer || loading" :loading="loading">
重新生成
{{ $t('views.application.generateDialog.remake') }}
</el-button>
</div>
</div>
@ -42,13 +43,18 @@
<!-- 文本输入框 -->
<div class="generate-prompt-operate p-16">
<div class="text-center mb-8" v-if="loading">
<el-button class="border-primary video-stop-button" @click="stopChat">
<div v-if="showStopButton" class="text-center mb-8">
<el-button class="border-primary video-stop-button" @click="pauseStreaming">
<app-icon iconName="app-video-stop" class="mr-8"></app-icon>
停止生成
{{ $t('views.application.generateDialog.stop') }}
</el-button>
</div>
<div v-if="showContinueButton" class="text-center mb-8">
<el-button class="border-primary video-stop-button" @click="continueStreaming">
<app-icon iconName="app-video-stop" class="mr-8"></app-icon>
{{ $t('views.application.generateDialog.continue') }}
</el-button>
</div>
<div class="operate-textarea">
<el-input
ref="quickInputRef"
@ -66,11 +72,11 @@
<el-button
text
class="sent-button"
:disabled="!inputValue.trim() || loading"
:disabled="!inputValue.trim() || loading || isStreaming"
@click="handleSubmit"
>
<img v-show="!inputValue.trim() || loading" src="@/assets/icon_send.svg" alt="" />
<SendIcon v-show="inputValue.trim() && !loading" />
<img v-show="!inputValue.trim() || loading || isStreaming" src="@/assets/icon_send.svg" alt="" />
<SendIcon v-show="inputValue.trim() && !loading && !isStreaming" />
</el-button>
</div>
</div>
@ -82,8 +88,10 @@
</template>
<script setup lang="ts">
import { computed, reactive, ref, nextTick, watch } from 'vue'
import { computed, onUnmounted,reactive, ref, nextTick, watch } from 'vue'
import { useRoute } from 'vue-router'
import { MsgConfirm } from '@/utils/message'
import { t } from '@/locales'
import systemGeneratePromptAPI from '@/api/system-resource-management/application'
import generatePromptAPI from '@/api/application/application'
import useStore from '@/stores'
@ -146,6 +154,71 @@ const promptTemplates = {
`,
}
const isStreaming = ref<boolean>(false) //
const isPaused = ref<boolean>(false) //
const fullContent = ref<string>('') //
const currentDisplayIndex = ref<number>(0) //
let streamTimer: number | null = null //
const isOutputComplete = ref<boolean>(false)
//
const startStreamingOutput = () => {
if (streamTimer) {
clearInterval(streamTimer)
}
isStreaming.value = true
isPaused.value = false
streamTimer = setInterval(() => {
if (!isPaused.value && currentDisplayIndex.value < fullContent.value.length) {
// 1-3
const step = Math.min(3, fullContent.value.length - currentDisplayIndex.value)
currentDisplayIndex.value += step
//
const currentAnswer = chatMessages.value[chatMessages.value.length - 1]
if (currentAnswer && currentAnswer.role === 'ai') {
currentAnswer.content = fullContent.value.substring(0, currentDisplayIndex.value)
}
} else if (loading.value === false && currentDisplayIndex.value >= fullContent.value.length) {
stopStreaming()
}
}, 50) // 50ms
}
//
const stopStreaming = () => {
if (streamTimer) {
clearInterval(streamTimer)
streamTimer = null
}
isStreaming.value = false
isPaused.value = false
loading.value = false
isOutputComplete.value = true
}
const showStopButton = computed(() => {
return isStreaming.value
})
//
const pauseStreaming = () => {
isPaused.value = true
isStreaming.value = false
}
//
const continueStreaming = () => {
if (currentDisplayIndex.value < fullContent.value.length) {
startStreamingOutput()
}
}
/**
* 获取一个递归函数,处理流式数据
* @param chat 每一条对话记录
@ -154,8 +227,16 @@ const promptTemplates = {
*/
const getWrite = (reader: any) => {
let tempResult = ''
const answer = reactive({ content: '', role: 'ai' })
chatMessages.value.push(answer)
const middleAnswer = reactive({ content: '', role: 'ai' })
chatMessages.value.push(middleAnswer )
//
fullContent.value = ''
currentDisplayIndex.value = 0
isOutputComplete.value = false
let streamingStarted = false
/**
*
* @param done 是否结束
@ -164,8 +245,8 @@ const getWrite = (reader: any) => {
const write_stream = ({ done, value }: { done: boolean; value: any }) => {
try {
if (done) {
//
loading.value = false
// console.log('')
return
}
const decoder = new TextDecoder('utf-8')
@ -185,11 +266,15 @@ const getWrite = (reader: any) => {
for (const index in split) {
const chunk = JSON?.parse(split[index].replace('data:', ''))
if (!chunk.is_end) {
answer.content += chunk.content
//
fullContent.value += chunk.content
if (!streamingStarted) {
streamingStarted = true
startStreamingOutput()
}
}
if (chunk.is_end) {
//
loading.value = false
isApiComplete.value = true
return Promise.resolve()
}
}
@ -197,6 +282,7 @@ const getWrite = (reader: any) => {
}
} catch (e) {
loading.value = false
stopStreaming()
return Promise.reject(e)
}
return reader.read().then(write_stream)
@ -204,7 +290,7 @@ const getWrite = (reader: any) => {
return write_stream
}
const isApiComplete = ref<boolean>(false)
const answer = computed(() => {
const result = chatMessages.value[chatMessages.value.length - 1]
@ -214,6 +300,12 @@ const answer = computed(() => {
return ''
})
//
const showContinueButton = computed(() => {
return !isStreaming.value && isPaused.value && currentDisplayIndex.value < fullContent.value.length
})
function generatePrompt(inputValue: any) {
loading.value = true
const workspaceId = user.getWorkspaceId() || 'default'
@ -268,8 +360,12 @@ const handleSubmit = (event?: any) => {
if (!originalUserInput.value) {
originalUserInput.value = inputValue.value
}
generatePrompt(inputValue.value)
if (inputValue.value) {
generatePrompt(inputValue.value)
inputValue.value = ''
}
} else {
// ctrl/shift/cmd/opt +enter
insertNewlineAtCursor(event)
@ -290,11 +386,6 @@ const insertNewlineAtCursor = (event?: any) => {
})
}
const stopChat = () => {
loading.value = false
chatMessages.value = []
}
const open = (modelId: string, applicationId: string) => {
modelID.value = modelId
applicationID.value = applicationId
@ -323,6 +414,38 @@ const handleScroll = () => {
}
}
const handleDialogClose = (done: () => void) => {
//
MsgConfirm(
t('common.tip'),
t('views.application.generateDialog.exit'),
{
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
distinguishCancelAndClose: true,
}
)
.then(() => {
//
stopStreaming()
chatMessages.value = []
fullContent.value = ''
currentDisplayIndex.value = 0
isOutputComplete.value = false
done() //
})
.catch(() => {
//
}
)
}
//
onUnmounted(() => {
stopStreaming()
})
watch(
answer,
() => {

View File

@ -443,7 +443,8 @@ const openGeneratePromptDialog = (modelId: string) => {
}
}
const replace = (v: any) => {
set(props.nodeModel.properties.node_data.model_setting, 'system', v)
console.log(props.nodeModel.properties.node_data.model_setting)
set(props.nodeModel.properties.node_data, 'system', v)
}
const openReasoningParamSettingDialog = () => {
ReasoningParamSettingDialogRef.value?.open(chat_data.value.model_setting)