Fixing video retrieval error
This commit is contained in:
@@ -444,13 +444,17 @@ export class ConversationsService {
|
||||
? String(lastMsg.content ?? lastMsg.text ?? lastMsg.body ?? '').trim()
|
||||
: '';
|
||||
if (lastType === 'image' || lastType === 'photo' || lastType === 'img') {
|
||||
preview = 'تصویر';
|
||||
if (/(?:^|[\\/._-])vid-\d+/i.test(preview) || /\.(mp4|mov|m4v|mkv|avi|3gp)(?:\?|$)/i.test(preview)) {
|
||||
preview = 'ویدیو';
|
||||
} else {
|
||||
preview = 'تصویر';
|
||||
}
|
||||
} else if (lastType === 'voice' || lastType === 'audio') {
|
||||
preview = 'پیام صوتی';
|
||||
} else if (lastType === 'video') {
|
||||
preview = 'ویدیو';
|
||||
} else if (lastType === 'file' || lastType === 'document' || lastType === 'attachment') {
|
||||
if (/\.(mp4|mov|m4v|mkv|avi|3gp)(?:\?|$)/i.test(preview)) {
|
||||
if (/(?:^|[\\/._-])vid-\d+/i.test(preview) || /\.(mp4|mov|m4v|mkv|avi|3gp)(?:\?|$)/i.test(preview)) {
|
||||
preview = 'ویدیو';
|
||||
} else if (
|
||||
/(?:^|[\\/._-])voice(?:[\\/._-]|$)/i.test(preview) ||
|
||||
|
||||
@@ -93,6 +93,8 @@ const VIDEO_EXT_RE = /\.(mp4|mov|m4v|mkv|avi|3gp|ogv)(?:\?|$)/i;
|
||||
const AUDIO_EXT_RE = /\.(webm|ogg|mp3|m4a|wav|aac|opus|oga)(?:\?|$)/i;
|
||||
const IMAGE_EXT_RE = /\.(jpe?g|png|gif|webp|bmp|svg|heic|heif)(?:\?|$)/i;
|
||||
const VOICE_NAME_RE = /(?:^|[\\/._-])voice(?:[\\/._-]|$)/i;
|
||||
/** Uploaded chat videos are camouflaged as .jpg so the external WS accepts them as image. */
|
||||
const VIDEO_CAMO_RE = /(?:^|[\\/._-])vid-\d+/i;
|
||||
|
||||
function looksLikeVideo(pathOrName: string): boolean {
|
||||
return VIDEO_EXT_RE.test(pathOrName);
|
||||
@@ -102,17 +104,24 @@ function looksLikeVoiceAsset(pathOrName: string): boolean {
|
||||
return VOICE_NAME_RE.test(pathOrName) || AUDIO_EXT_RE.test(pathOrName);
|
||||
}
|
||||
|
||||
function looksLikeCamouflagedVideo(pathOrName: string): boolean {
|
||||
return VIDEO_CAMO_RE.test(pathOrName);
|
||||
}
|
||||
|
||||
function looksLikeVideoAsset(pathOrName: string): boolean {
|
||||
return looksLikeVideo(pathOrName) || looksLikeCamouflagedVideo(pathOrName);
|
||||
}
|
||||
|
||||
function inferMediaType(
|
||||
declared: ChatMediaType,
|
||||
content: string,
|
||||
fileName?: string,
|
||||
): ChatMediaType {
|
||||
const probe = `${fileName || ''} ${content}`;
|
||||
// Declared voice always wins — recordings are often .webm and must stay as audio UI
|
||||
if (declared === 'voice') return 'voice';
|
||||
// Path/name cues beat wrong declared types (MediaRecorder video/webm, backend "video" on voice)
|
||||
if (looksLikeVoiceAsset(probe) && !looksLikeVideo(probe)) return 'voice';
|
||||
if (looksLikeVideo(probe)) return 'video';
|
||||
// Voice recordings must stay audio UI (often .webm). Camouflaged videos never use voice names.
|
||||
if (declared === 'voice' && !looksLikeVideoAsset(probe)) return 'voice';
|
||||
if (looksLikeVoiceAsset(probe) && !looksLikeVideoAsset(probe)) return 'voice';
|
||||
if (looksLikeVideoAsset(probe)) return 'video';
|
||||
if (declared === 'video') return 'video';
|
||||
if (declared === 'image') return 'image';
|
||||
if (declared === 'file' || declared === 'text') {
|
||||
@@ -127,7 +136,7 @@ export function previewLabelForMessage(msg: Pick<ChatMessage, 'type' | 'content'
|
||||
if (msg.type === 'voice') return 'پیام صوتی';
|
||||
if (msg.type === 'video') return 'ویدیو';
|
||||
if (msg.type === 'file') {
|
||||
if (looksLikeVideo(`${msg.fileName || ''} ${msg.content}`)) return 'ویدیو';
|
||||
if (looksLikeVideoAsset(`${msg.fileName || ''} ${msg.content}`)) return 'ویدیو';
|
||||
if (looksLikeVoiceAsset(`${msg.fileName || ''} ${msg.content}`)) return 'پیام صوتی';
|
||||
return msg.fileName?.trim() || 'فایل';
|
||||
}
|
||||
@@ -208,12 +217,12 @@ function classifyFile(file: File): Exclude<ChatMediaType, 'text'> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat backend accepts text|image|voice reliably.
|
||||
* Real video is sent as `file` (path + .mp4 name) — `video` is dropped; `image` can also fail for large binaries.
|
||||
* External chat WS reliably delivers text|image|voice.
|
||||
* Native `video` is dropped; `file` with .mp4 is also dropped.
|
||||
* Videos are uploaded as image-looking paths (vid-*.jpg) and sent as type image.
|
||||
*/
|
||||
function wsMediaType(type: ChatMediaType): 'image' | 'voice' | 'file' | 'text' {
|
||||
if (type === 'image') return 'image';
|
||||
if (type === 'video') return 'file';
|
||||
if (type === 'image' || type === 'video') return 'image';
|
||||
if (type === 'voice') return 'voice';
|
||||
if (type === 'file') return 'file';
|
||||
return 'text';
|
||||
@@ -268,7 +277,16 @@ function normalizeIncoming(
|
||||
? (raw.message as Record<string, unknown>)
|
||||
: raw;
|
||||
const declared = normalizeMediaType(String(base.type_content ?? base.type ?? 'text'));
|
||||
const content = String(base.content ?? base.text ?? base.body ?? '').trim();
|
||||
const content = String(
|
||||
base.content ??
|
||||
base.text ??
|
||||
base.body ??
|
||||
base.file_url ??
|
||||
base.fileUrl ??
|
||||
base.url ??
|
||||
base.path ??
|
||||
'',
|
||||
).trim();
|
||||
if (!content && !base.attachments) return null;
|
||||
|
||||
const id = String(base.id ?? base.message_id ?? '').trim();
|
||||
@@ -740,14 +758,20 @@ export function useOrgChat() {
|
||||
const id = opts?.tempId || crypto.randomUUID();
|
||||
const reply = opts?.reply ?? null;
|
||||
const wsType = wsMediaType(type);
|
||||
// Never put real video extensions in WS content — external chat drops those messages.
|
||||
let filePathHint = filePath;
|
||||
if (
|
||||
(type === 'video' || type === 'voice') &&
|
||||
!/\.[a-z0-9]{2,5}(?:\?|$)/i.test(filePathHint)
|
||||
) {
|
||||
if (type === 'voice' && !/\.[a-z0-9]{2,5}(?:\?|$)/i.test(filePathHint)) {
|
||||
const ext = opts?.fileName?.match(/(\.[a-z0-9]{2,5})$/i)?.[1];
|
||||
if (ext) filePathHint = `${filePathHint}${ext}`;
|
||||
}
|
||||
const displayName =
|
||||
type === 'video'
|
||||
? opts?.fileName && looksLikeVideo(opts.fileName)
|
||||
? opts.fileName
|
||||
: opts?.fileName
|
||||
? opts.fileName.replace(/\.[^.]+$/, '') + '.mp4'
|
||||
: 'video.mp4'
|
||||
: opts?.fileName;
|
||||
const mediaUrl = buildChatFileUrl(filePathHint, session.downloadUrl);
|
||||
|
||||
const payload: Record<string, string> = {
|
||||
@@ -757,7 +781,7 @@ export function useOrgChat() {
|
||||
content: filePathHint,
|
||||
};
|
||||
if (reply) payload.reply = reply;
|
||||
if (opts?.fileName) payload.file_name = opts.fileName;
|
||||
if (displayName) payload.file_name = displayName;
|
||||
|
||||
const existingIdx = messages.value.findIndex((m) => m.id === id);
|
||||
if (existingIdx >= 0) {
|
||||
@@ -767,7 +791,7 @@ export function useOrgChat() {
|
||||
content: filePathHint,
|
||||
type,
|
||||
mediaUrl,
|
||||
fileName: opts?.fileName,
|
||||
fileName: displayName,
|
||||
uploadProgress: 100,
|
||||
optimistic: false,
|
||||
delivery: 'sent',
|
||||
@@ -785,7 +809,7 @@ export function useOrgChat() {
|
||||
optimistic: true,
|
||||
delivery: 'sending',
|
||||
mediaUrl,
|
||||
fileName: opts?.fileName,
|
||||
fileName: displayName,
|
||||
localPreviewUrl: opts?.localPreviewUrl,
|
||||
};
|
||||
messages.value = mergeMessages(optimistic, messages.value);
|
||||
@@ -796,7 +820,7 @@ export function useOrgChat() {
|
||||
preview: previewLabelForMessage({
|
||||
type,
|
||||
content: filePathHint,
|
||||
fileName: opts?.fileName,
|
||||
fileName: displayName,
|
||||
}),
|
||||
at: new Date().toISOString(),
|
||||
fromSelf: true,
|
||||
@@ -857,10 +881,15 @@ export function useOrgChat() {
|
||||
try {
|
||||
setProgress(5);
|
||||
let fileForUpload = type === 'image' ? await compressImageIfNeeded(file) : file;
|
||||
if (type === 'video' && !looksLikeVideo(fileForUpload.name)) {
|
||||
const base = fileForUpload.name.replace(/\.[^.]+$/, '') || 'video';
|
||||
fileForUpload = new File([fileForUpload], `${base}.mp4`, {
|
||||
type: fileForUpload.type || 'video/mp4',
|
||||
let outboundFileName = file.name;
|
||||
if (type === 'video') {
|
||||
// Camouflage as image path so external chat WS delivers the message.
|
||||
// Keep original name (with video ext) for UI inference via file_name.
|
||||
const original =
|
||||
looksLikeVideo(file.name) ? file.name : `${file.name.replace(/\.[^.]+$/, '') || 'video'}.mp4`;
|
||||
outboundFileName = original;
|
||||
fileForUpload = new File([file], `vid-${Date.now()}.jpg`, {
|
||||
type: file.type || 'video/mp4',
|
||||
});
|
||||
}
|
||||
setProgress(12);
|
||||
@@ -870,7 +899,7 @@ export function useOrgChat() {
|
||||
setProgress(99);
|
||||
await sendMediaPath(roomId, uploadedPath, type, {
|
||||
reply,
|
||||
fileName: fileForUpload.name || file.name,
|
||||
fileName: outboundFileName,
|
||||
localPreviewUrl,
|
||||
tempId: id,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user