feat: 1-to-1 message render + web data-lake backend
This commit is contained in:
1 parent
f0afb7ec5b
commit
75425d1bee
110 files changed
+10199
-54
No files matched your search
@@ -0,0 +1,175 @@
|
||||
import { requestMedia } from "$lib/api/client";
|
||||
import { getMessageMedia } from "$lib/api/endpoints";
|
||||
import type { MediaRef } from "$lib/api/types";
|
||||
import { accounts } from "$lib/stores/accounts.svelte";
|
||||
|
||||
export type InlineMedia =
|
||||
| {
|
||||
state: "ready";
|
||||
mediaId: number;
|
||||
kind: string;
|
||||
mime: string | null;
|
||||
url: string;
|
||||
transcript: string | null;
|
||||
}
|
||||
| { state: "not-downloaded"; mediaId: number; kind: string }
|
||||
| { state: "missing" };
|
||||
|
||||
export interface ViewerItem {
|
||||
downloaded: boolean;
|
||||
kind: string;
|
||||
mediaId: number | null;
|
||||
messageId: number;
|
||||
}
|
||||
|
||||
export function viewerItemsFrom(
|
||||
messageId: number,
|
||||
media: MediaRef[]
|
||||
): ViewerItem[] {
|
||||
if (media.length === 0) {
|
||||
return [{ messageId, mediaId: null, kind: "", downloaded: false }];
|
||||
}
|
||||
return media.map((item) => ({
|
||||
messageId: item.message_id,
|
||||
mediaId: item.id,
|
||||
kind: item.kind,
|
||||
downloaded: item.downloaded,
|
||||
}));
|
||||
}
|
||||
|
||||
export type VisualKind = "image" | "video" | "other";
|
||||
|
||||
const VIDEO_KINDS = new Set(["video", "video_note", "animation", "gif"]);
|
||||
|
||||
export function visualKind(kind: string): VisualKind {
|
||||
if (kind === "photo") {
|
||||
return "image";
|
||||
}
|
||||
if (VIDEO_KINDS.has(kind)) {
|
||||
return "video";
|
||||
}
|
||||
return "other";
|
||||
}
|
||||
|
||||
const ready = new Map<string, InlineMedia>();
|
||||
const inflight = new Map<string, Promise<InlineMedia>>();
|
||||
|
||||
function cacheKey(account: number, chatId: number, messageId: number): string {
|
||||
return `${account}:${chatId}:${messageId}`;
|
||||
}
|
||||
|
||||
async function resolve(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<InlineMedia> {
|
||||
let meta: Awaited<ReturnType<typeof getMessageMedia>>;
|
||||
try {
|
||||
meta = await getMessageMedia(chatId, messageId);
|
||||
} catch {
|
||||
return { state: "missing" };
|
||||
}
|
||||
if (!meta.downloaded) {
|
||||
return { state: "not-downloaded", mediaId: meta.id, kind: meta.kind };
|
||||
}
|
||||
const blob = await requestMedia(meta.id);
|
||||
if (blob.state === "ready") {
|
||||
return {
|
||||
state: "ready",
|
||||
mediaId: meta.id,
|
||||
kind: meta.kind,
|
||||
mime: blob.mime,
|
||||
url: blob.url,
|
||||
transcript: meta.extracted_text,
|
||||
};
|
||||
}
|
||||
if (blob.state === "not-downloaded") {
|
||||
return { state: "not-downloaded", mediaId: meta.id, kind: meta.kind };
|
||||
}
|
||||
return { state: "missing" };
|
||||
}
|
||||
|
||||
const byId = new Map<string, InlineMedia>();
|
||||
const byIdInflight = new Map<string, Promise<InlineMedia>>();
|
||||
|
||||
async function resolveById(media: MediaRef): Promise<InlineMedia> {
|
||||
if (media.id === null) {
|
||||
return { state: "missing" };
|
||||
}
|
||||
if (!media.downloaded) {
|
||||
return { state: "not-downloaded", mediaId: media.id, kind: media.kind };
|
||||
}
|
||||
const blob = await requestMedia(media.id);
|
||||
if (blob.state === "ready") {
|
||||
return {
|
||||
state: "ready",
|
||||
mediaId: media.id,
|
||||
kind: media.kind,
|
||||
mime: blob.mime,
|
||||
url: blob.url,
|
||||
transcript: null,
|
||||
};
|
||||
}
|
||||
if (blob.state === "not-downloaded") {
|
||||
return { state: "not-downloaded", mediaId: media.id, kind: media.kind };
|
||||
}
|
||||
return { state: "missing" };
|
||||
}
|
||||
|
||||
export function loadMediaItem(media: MediaRef): Promise<InlineMedia> {
|
||||
const account = accounts.selectedId;
|
||||
if (account === null || media.id === null) {
|
||||
return Promise.resolve<InlineMedia>({ state: "missing" });
|
||||
}
|
||||
const key = `${account}:${media.id}`;
|
||||
const cached = byId.get(key);
|
||||
if (cached) {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
const existing = byIdInflight.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const promise = resolveById(media)
|
||||
.then((result) => {
|
||||
if (result.state === "ready") {
|
||||
byId.set(key, result);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.finally(() => {
|
||||
byIdInflight.delete(key);
|
||||
});
|
||||
byIdInflight.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
export function loadInlineMedia(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<InlineMedia> {
|
||||
const account = accounts.selectedId;
|
||||
if (account === null) {
|
||||
return Promise.resolve<InlineMedia>({ state: "missing" });
|
||||
}
|
||||
const key = cacheKey(account, chatId, messageId);
|
||||
const cached = ready.get(key);
|
||||
if (cached) {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
const existing = inflight.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const promise = resolve(chatId, messageId)
|
||||
.then((result) => {
|
||||
if (result.state === "ready") {
|
||||
ready.set(key, result);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.finally(() => {
|
||||
inflight.delete(key);
|
||||
});
|
||||
inflight.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
Reference in new issue
Block a user