refactor(posts): enhance regeneration dialog logic and type safety
- Improved type definitions for regeneration status and event handling, increasing clarity and maintainability. - Streamlined the regeneration result handling by introducing a dedicated function to manage success and error states. - Added computed properties to optimize form submission logic, ensuring better user experience and validation. - Refactored the subscription logic to improve readability and error handling during media regeneration.
This commit is contained in:
parent
a008555589
commit
4d8452dbc8
1 changed files with 59 additions and 36 deletions
|
|
@ -9,14 +9,25 @@ import { Button } from '@/components/ui/button';
|
|||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import type { MediaItem } from '@/types/media';
|
||||
import { regenerateMedia as regeneratePostAiMedia } from '@/routes/app/posts/ai';
|
||||
import type { MediaItem } from '@/types/media';
|
||||
|
||||
interface RegenerationPayload {
|
||||
media: MediaItem;
|
||||
targetMediaId: string;
|
||||
}
|
||||
|
||||
type RegenerationStatus = 'idle' | 'starting' | 'processing';
|
||||
|
||||
interface RegenerationStartResponse {
|
||||
channel?: string;
|
||||
}
|
||||
|
||||
interface RegenerationEvent {
|
||||
media: MediaItem | null;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
postId: string;
|
||||
mediaItem: MediaItem | null;
|
||||
|
|
@ -30,7 +41,7 @@ const emit = defineEmits<{
|
|||
|
||||
const instruction = ref('');
|
||||
const errorMessage = ref<string | null>(null);
|
||||
const status = ref<'idle' | 'starting' | 'processing'>('idle');
|
||||
const status = ref<RegenerationStatus>('idle');
|
||||
|
||||
let subscribedChannel: string | null = null;
|
||||
let regenerationTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
|
@ -42,6 +53,8 @@ const httpRegenerate = useHttp<{ instruction: string }>({
|
|||
});
|
||||
|
||||
const isBusy = computed(() => status.value !== 'idle');
|
||||
const normalizedInstruction = computed(() => instruction.value.trim());
|
||||
const canSubmit = computed(() => normalizedInstruction.value.length > 0 && !isBusy.value);
|
||||
|
||||
const unsubscribe = () => {
|
||||
if (subscribedChannel) {
|
||||
|
|
@ -57,6 +70,11 @@ const clearRegenerationTimeout = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const setIdleWithError = (message: string) => {
|
||||
errorMessage.value = message;
|
||||
status.value = 'idle';
|
||||
};
|
||||
|
||||
const resetState = () => {
|
||||
instruction.value = '';
|
||||
errorMessage.value = null;
|
||||
|
|
@ -71,68 +89,72 @@ const blockDismissWhileBusy = (event: Event) => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleRegenerationResult = (event: RegenerationEvent) => {
|
||||
clearRegenerationTimeout();
|
||||
|
||||
if (event.error || !event.media || !props.mediaItem) {
|
||||
setIdleWithError(event.error ?? trans('posts.ai.image_regenerate.errors.unavailable'));
|
||||
unsubscribe();
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(trans('posts.ai.image_regenerate.success'));
|
||||
|
||||
emit('regenerated', {
|
||||
media: event.media,
|
||||
targetMediaId: props.mediaItem.id,
|
||||
});
|
||||
|
||||
resetState();
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const subscribe = (channel: string) => {
|
||||
subscribedChannel = channel;
|
||||
status.value = 'processing';
|
||||
|
||||
clearRegenerationTimeout();
|
||||
regenerationTimeout = setTimeout(() => {
|
||||
errorMessage.value = trans('posts.ai.image_regenerate.errors.timeout');
|
||||
status.value = 'idle';
|
||||
setIdleWithError(trans('posts.ai.image_regenerate.errors.timeout'));
|
||||
unsubscribe();
|
||||
}, REGENERATION_TIMEOUT_MS);
|
||||
|
||||
echo()
|
||||
.private(channel)
|
||||
.listen('.ai.media.regenerated', (event: {
|
||||
media: MediaItem | null;
|
||||
error?: string | null;
|
||||
}) => {
|
||||
clearRegenerationTimeout();
|
||||
|
||||
if (event.error || !event.media || !props.mediaItem) {
|
||||
errorMessage.value = event.error ?? trans('posts.ai.image_regenerate.errors.unavailable');
|
||||
status.value = 'idle';
|
||||
unsubscribe();
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(trans('posts.ai.image_regenerate.success'));
|
||||
|
||||
emit('regenerated', {
|
||||
media: event.media,
|
||||
targetMediaId: props.mediaItem.id,
|
||||
});
|
||||
|
||||
resetState();
|
||||
open.value = false;
|
||||
});
|
||||
.listen('.ai.media.regenerated', (event: RegenerationEvent) => handleRegenerationResult(event));
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
if (!props.mediaItem) return;
|
||||
if (!instruction.value.trim()) {
|
||||
errorMessage.value = trans('posts.ai.image_regenerate.errors.required');
|
||||
const mediaItem = props.mediaItem;
|
||||
const instructionValue = normalizedInstruction.value;
|
||||
|
||||
if (!mediaItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!instructionValue) {
|
||||
setIdleWithError(trans('posts.ai.image_regenerate.errors.required'));
|
||||
return;
|
||||
}
|
||||
|
||||
errorMessage.value = null;
|
||||
status.value = 'starting';
|
||||
httpRegenerate.instruction = instruction.value.trim();
|
||||
httpRegenerate.instruction = instructionValue;
|
||||
|
||||
try {
|
||||
const response = await httpRegenerate.post(
|
||||
regeneratePostAiMedia.url({ post: props.postId, mediaId: props.mediaItem.id }),
|
||||
) as { channel?: string };
|
||||
regeneratePostAiMedia.url({ post: props.postId, mediaId: mediaItem.id }),
|
||||
) as RegenerationStartResponse;
|
||||
const channel = String(response.channel ?? '');
|
||||
|
||||
if (!channel) {
|
||||
throw new Error('Missing channel in regeneration response.');
|
||||
}
|
||||
|
||||
subscribe(channel);
|
||||
} catch (error: unknown) {
|
||||
status.value = 'idle';
|
||||
const responseMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message;
|
||||
errorMessage.value = responseMessage ?? trans('posts.ai.image_regenerate.errors.start_failed');
|
||||
setIdleWithError(responseMessage ?? trans('posts.ai.image_regenerate.errors.start_failed'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -147,6 +169,7 @@ watch(open, (isOpen) => {
|
|||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearRegenerationTimeout();
|
||||
unsubscribe();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -185,7 +208,7 @@ onBeforeUnmount(() => {
|
|||
<DialogFooter>
|
||||
<Button
|
||||
:loading="isBusy"
|
||||
:disabled="!instruction.trim()"
|
||||
:disabled="!canSubmit"
|
||||
@click="submit"
|
||||
>
|
||||
{{ $t('posts.ai.image_regenerate.submit') }}
|
||||
|
|
|
|||
Loading…
Reference in a new issue