-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix: Prompt generate #4041
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Prompt generate #4041
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -127,6 +127,12 @@ export default { | |
| label: '生成', | ||
| generatePrompt: '生成提示词', | ||
| placeholder: '请输入提示词主题', | ||
| title: '提示词显示在这里', | ||
| remake: '重新生成', | ||
| stop: '停止生成', | ||
| continue: '继续生成', | ||
| replace: '替换', | ||
| exit: '确认退出并舍弃 AI 生成的内容吗?', | ||
| }, | ||
| dialog: { | ||
| addKnowledge: '添加关联知识库', | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your code looks mostly correct, but there are a few improvements that could be made:
Here's an example of how you can improve the readability and robustness of the export default {
common: {
cancel: '取消',
confirm: '确认',
},
settings: {
saveButtonLabel: {
type: 'save_settings_dialog_save_button_label'
}
// other settings...
}
// Example of adding error handling for placeholder
const placeholder = '请输入提示词主题';
if (!placeholder || typeof placeholder !== 'string') {
throw new Error('Placeholder must be a non-empty string');
}This way, when someone tries to set an invalid value for |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,27 +29,32 @@ | |
| </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> | ||
|
|
||
| <!-- 文本输入框 --> | ||
|
|
||
| <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,26 +266,31 @@ 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() | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } catch (e) { | ||
| loading.value = false | ||
| stopStreaming() | ||
| return Promise.reject(e) | ||
| } | ||
| return reader.read().then(write_stream) | ||
| } | ||
|
|
||
| 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, | ||
| () => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The provided code looks generally correct but lacks some critical optimizations and security considerations. Here are my observations:
Suggested Improvements
startStreamingOutput() {
if(isPaused.value || streamTimer) {
return;
}
isStreaming.value = true;
streamTimer = setInterval(async () => {
if(!isPaused.value && currentDisplayIndex.value < fullContent.value.length){
const step = Math.min(3, fullContent.value.length - currentDisplayIndex.value);
currentDisplayIndex.value += step;
// Update displayed content
const currentAnswer = chatMessages.value[chatMessages.value.length - 1];
if(currentAnswer && currentAnswer.role === 'ai'){
currentAnswer.content = fullContent.value.substring(0, currentDisplayIndex.value);
}
// Check if we have reached the end gracefully
const lastChunk = fullContentvalue.split('\n').pop();
if(lastChunk && /END_OF_STREAM/.test(lastChunk)){
clearInterval(this.streamTimer);
this.streamTimer = null;
this.isStreaming = false;
this.loading = false; // Optional depending on how you want to finalize the dialog UI
}
}
}, 50); // Adjust polling interval as needed
}This ensures smoother transitions between short chunks and completes the display when appropriate, avoiding abrupt stops in large outputs. By implementing these suggested improvements, your component should become more robust and easier to maintain. Feel free to further clarify any concerns or adjust solutions as per your app's specific requirements! |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The provided code appears to be part of a configuration object used for localization in a web application or similar project. The
exportstatement exports an object with various keys, each containing human-readable strings that will be displayed to users.Here's a brief summary of the corrections needed:
Typo: There might be a typo in the key
'remake'. It should likely be'regenerate'.Consistency: Ensure consistency throughout the translation entries. For example, both
'generatePrompt'should use consistent terminology and formatting (e.g., 'prompt', no extra spaces after it).Here’s the corrected version of the block:
In this revised version:
'replace'has been replaced with'replaceContent'which seems more natural if the intention is to change the generated text.'exitConfirmation'has been added to confirm whether the user wants to cancel their action while still having access to the AI-generated content. This improves clarity as it provides options on how to handle changes versus abandoning them entirely.