发布 v1.10 #5

Merged
czm merged 147 commits from develop into main 2026-08-20 11:36:27 +08:00
6 changed files with 125 additions and 30 deletions
Showing only changes of commit 908eb5583d - Show all commits

View File

@@ -578,6 +578,9 @@ watch(
{ flush: 'post' }, { flush: 'post' },
); );
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (sending.value) {
stopSse();
}
if (bubbleListScrollElement.value) { if (bubbleListScrollElement.value) {
bubbleListScrollElement.value.removeEventListener( bubbleListScrollElement.value.removeEventListener(
'scroll', 'scroll',

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { onBeforeUnmount, ref, watch } from 'vue';
import { EasyFlowPanelModal } from '@easyflow/common-ui'; import { EasyFlowPanelModal } from '@easyflow/common-ui';
import { $t } from '@easyflow/locales'; import { $t } from '@easyflow/locales';
@@ -18,6 +18,16 @@ const rules = {
}; };
const loading = ref(false); const loading = ref(false);
const stopStreaming = () => {
if (!loading.value) return;
sseClient.abort();
loading.value = false;
};
const closeDialog = () => {
dialogVisible.value = false;
};
const handleSubmit = async () => { const handleSubmit = async () => {
loading.value = true; loading.value = true;
const data = { const data = {
@@ -40,12 +50,28 @@ const handleSubmit = async () => {
const delta = sseData.payload?.delta; const delta = sseData.payload?.delta;
formData.value.prompt += delta; formData.value.prompt += delta;
}, },
onError(error) {
loading.value = false;
console.error('提示词优化流式请求失败', error);
},
onFinished() {
loading.value = false;
},
}); });
}; };
const handleReplace = () => { const handleReplace = () => {
emit('success', formData.value.prompt); emit('success', formData.value.prompt);
dialogVisible.value = false; closeDialog();
}; };
watch(dialogVisible, (visible) => {
if (!visible) {
stopStreaming();
}
});
onBeforeUnmount(stopStreaming);
defineExpose({ defineExpose({
open(botId: string, systemPrompt: string) { open(botId: string, systemPrompt: string) {
formData.value = { formData.value = {
@@ -80,7 +106,7 @@ defineExpose({
</ElForm> </ElForm>
<template #footer> <template #footer>
<ElButton @click="dialogVisible = false"> <ElButton @click="closeDialog">
{{ $t('button.cancel') }} {{ $t('button.cancel') }}
</ElButton> </ElButton>
<ElButton type="primary" @click="handleReplace" v-if="!loading"> <ElButton type="primary" @click="handleReplace" v-if="!loading">

View File

@@ -351,6 +351,9 @@ watch(
); );
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (sending.value || activeStreamSessionId.value) {
abortActiveStream();
}
window.removeEventListener('click', closeSessionContextMenu); window.removeEventListener('click', closeSessionContextMenu);
window.removeEventListener('resize', closeSessionContextMenu); window.removeEventListener('resize', closeSessionContextMenu);
window.removeEventListener('scroll', closeSessionContextMenu, true); window.removeEventListener('scroll', closeSessionContextMenu, true);

View File

@@ -21,6 +21,8 @@ const props = withDefaults(defineProps<Props>(), {
let isCheckingUpdates = false; let isCheckingUpdates = false;
let resumeCheckFrame: number | undefined; let resumeCheckFrame: number | undefined;
let versionCheckController: AbortController | null = null;
const VERSION_CHECK_TIMEOUT_MS = 10_000;
const currentVersionTag = ref(''); const currentVersionTag = ref('');
const lastVersionTag = ref(''); const lastVersionTag = ref('');
const timer = ref<ReturnType<typeof setInterval>>(); const timer = ref<ReturnType<typeof setInterval>>();
@@ -37,6 +39,12 @@ const [UpdateNoticeModal, modalApi] = useEasyFlowModal({
}); });
async function getVersionTag() { async function getVersionTag() {
const controller = new AbortController();
versionCheckController = controller;
const timeout = setTimeout(
() => controller.abort(),
VERSION_CHECK_TIMEOUT_MS,
);
try { try {
if ( if (
location.hostname === 'localhost' || location.hostname === 'localhost' ||
@@ -48,14 +56,22 @@ async function getVersionTag() {
cache: 'no-cache', cache: 'no-cache',
method: 'HEAD', method: 'HEAD',
redirect: 'manual', redirect: 'manual',
signal: controller.signal,
}); });
return ( return (
response.headers.get('etag') || response.headers.get('last-modified') response.headers.get('etag') || response.headers.get('last-modified')
); );
} catch { } catch (error) {
console.error('Failed to fetch version tag'); if (!controller.signal.aborted) {
console.error('Failed to fetch version tag', error);
}
return null; return null;
} finally {
clearTimeout(timeout);
if (versionCheckController === controller) {
versionCheckController = null;
}
} }
} }
@@ -72,10 +88,21 @@ async function checkForUpdates() {
} }
if (lastVersionTag.value !== versionTag && versionTag) { if (lastVersionTag.value !== versionTag && versionTag) {
clearInterval(timer.value); stop();
handleNotice(versionTag); handleNotice(versionTag);
} }
} }
async function runUpdateCheck() {
if (isCheckingUpdates) return;
isCheckingUpdates = true;
try {
await checkForUpdates();
} finally {
isCheckingUpdates = false;
}
}
function handleNotice(versionTag: string) { function handleNotice(versionTag: string) {
currentVersionTag.value = versionTag; currentVersionTag.value = versionTag;
modalApi.open(); modalApi.open();
@@ -89,7 +116,7 @@ function start() {
// 每 checkUpdatesInterval(默认值为1) 分钟检查一次 // 每 checkUpdatesInterval(默认值为1) 分钟检查一次
timer.value = setInterval( timer.value = setInterval(
checkForUpdates, () => void runUpdateCheck(),
props.checkUpdatesInterval * 60 * 1000, props.checkUpdatesInterval * 60 * 1000,
); );
} }
@@ -108,13 +135,11 @@ function scheduleResumeCheck() {
resumeCheckFrame = requestAnimationFrame(() => { resumeCheckFrame = requestAnimationFrame(() => {
resumeCheckFrame = requestAnimationFrame(() => { resumeCheckFrame = requestAnimationFrame(() => {
resumeCheckFrame = undefined; resumeCheckFrame = undefined;
if (document.hidden || isCheckingUpdates) { if (document.hidden) return;
return; runUpdateCheck().finally(() => {
} if (!document.hidden) {
isCheckingUpdates = true;
checkForUpdates().finally(() => {
isCheckingUpdates = false;
start(); start();
}
}); });
}); });
}); });
@@ -130,12 +155,13 @@ function handleVisibilitychange() {
} }
function stop() { function stop() {
if (timer.value === undefined) { if (timer.value !== undefined) {
return;
}
clearInterval(timer.value); clearInterval(timer.value);
timer.value = undefined; timer.value = undefined;
} }
versionCheckController?.abort();
versionCheckController = null;
}
onMounted(() => { onMounted(() => {
start(); start();

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ChatTimeTimelineItem } from '@easyflow/types'; import type { ChatTimeTimelineItem } from '@easyflow/types';
import { inject, ref } from 'vue'; import { inject, onBeforeUnmount, ref } from 'vue';
import { ChatTimeTimelineBuilder, uuid } from '@easyflow/utils'; import { ChatTimeTimelineBuilder, uuid } from '@easyflow/utils';
@@ -237,6 +237,12 @@ function normalizeSseRoundMeta(meta: any): SseRoundMeta {
variantIndex, variantIndex,
}; };
} }
onBeforeUnmount(() => {
if (btnLoading.value) {
stopSse();
}
});
</script> </script>
<template> <template>

View File

@@ -20,6 +20,8 @@ const props = withDefaults(defineProps<Props>(), {
}); });
let isCheckingUpdates = false; let isCheckingUpdates = false;
let versionCheckController: AbortController | null = null;
const VERSION_CHECK_TIMEOUT_MS = 10_000;
const currentVersionTag = ref(''); const currentVersionTag = ref('');
const lastVersionTag = ref(''); const lastVersionTag = ref('');
const timer = ref<ReturnType<typeof setInterval>>(); const timer = ref<ReturnType<typeof setInterval>>();
@@ -36,6 +38,12 @@ const [UpdateNoticeModal, modalApi] = useEasyFlowModal({
}); });
async function getVersionTag() { async function getVersionTag() {
const controller = new AbortController();
versionCheckController = controller;
const timeout = setTimeout(
() => controller.abort(),
VERSION_CHECK_TIMEOUT_MS,
);
try { try {
if ( if (
location.hostname === 'localhost' || location.hostname === 'localhost' ||
@@ -47,14 +55,22 @@ async function getVersionTag() {
cache: 'no-cache', cache: 'no-cache',
method: 'HEAD', method: 'HEAD',
redirect: 'manual', redirect: 'manual',
signal: controller.signal,
}); });
return ( return (
response.headers.get('etag') || response.headers.get('last-modified') response.headers.get('etag') || response.headers.get('last-modified')
); );
} catch { } catch (error) {
console.error('Failed to fetch version tag'); if (!controller.signal.aborted) {
console.error('Failed to fetch version tag', error);
}
return null; return null;
} finally {
clearTimeout(timeout);
if (versionCheckController === controller) {
versionCheckController = null;
}
} }
} }
@@ -71,23 +87,35 @@ async function checkForUpdates() {
} }
if (lastVersionTag.value !== versionTag && versionTag) { if (lastVersionTag.value !== versionTag && versionTag) {
clearInterval(timer.value); stop();
handleNotice(versionTag); handleNotice(versionTag);
} }
} }
async function runUpdateCheck() {
if (isCheckingUpdates) return;
isCheckingUpdates = true;
try {
await checkForUpdates();
} finally {
isCheckingUpdates = false;
}
}
function handleNotice(versionTag: string) { function handleNotice(versionTag: string) {
currentVersionTag.value = versionTag; currentVersionTag.value = versionTag;
modalApi.open(); modalApi.open();
} }
function start() { function start() {
stop();
if (props.checkUpdatesInterval <= 0) { if (props.checkUpdatesInterval <= 0) {
return; return;
} }
// 每 checkUpdatesInterval(默认值为1) 分钟检查一次 // 每 checkUpdatesInterval(默认值为1) 分钟检查一次
timer.value = setInterval( timer.value = setInterval(
checkForUpdates, () => void runUpdateCheck(),
props.checkUpdatesInterval * 60 * 1000, props.checkUpdatesInterval * 60 * 1000,
); );
} }
@@ -96,18 +124,21 @@ function handleVisibilitychange() {
if (document.hidden) { if (document.hidden) {
stop(); stop();
} else { } else {
if (!isCheckingUpdates) { runUpdateCheck().finally(() => {
isCheckingUpdates = true; if (!document.hidden) {
checkForUpdates().finally(() => {
isCheckingUpdates = false;
start(); start();
});
} }
});
} }
} }
function stop() { function stop() {
if (timer.value !== undefined) {
clearInterval(timer.value); clearInterval(timer.value);
timer.value = undefined;
}
versionCheckController?.abort();
versionCheckController = null;
} }
onMounted(() => { onMounted(() => {