fix: make it work with new vite

This commit is contained in:
Max Richter
2026-01-10 19:28:09 +01:00
parent 694feb083d
commit 8d712322c0
66 changed files with 590 additions and 1544 deletions

View File

@@ -43,7 +43,7 @@ export const performanceTable = sqliteTable("performance", {
export const imageTable = sqliteTable("image", {
createdAt: integer("created_at", { mode: "timestamp" }).default(
sql`(current_timestamp)`,
sql`(unixepoch())`,
),
url: text().notNull(),
average: text().notNull(),

View File

@@ -4,7 +4,6 @@ import path from "node:path";
const DB_FILE = "file:" + path.resolve(DATA_DIR, "db.sqlite");
// You can specify any property from the libsql connection options
export const db = drizzle({
connection: {
url: DB_FILE,

View File

@@ -36,9 +36,10 @@ export async function getLogs() {
.map((line) => {
const [date, ...rest] = line.split(" | ");
const parsed = JSON.parse(rest.join(" | ")) as Log;
const dateObj = new Date(date);
return {
...parsed,
date: new Date(date),
date: isNaN(dateObj.getTime()) ? new Date() : dateObj,
} as Log;
});

View File

@@ -1,105 +0,0 @@
/**
* Interface zur Beschreibung eines eingereihten Promises in der `PromiseQueue`.
*/
interface QueuedPromise<T = unknown> {
promise: () => Promise<T>;
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: unknown) => void;
}
/**
* Eine einfache Promise Queue, die es ermöglicht mehrere Aufgaben in kontrollierter
* Reihenfolge abzuarbeiten.
*
* Lizenz: CC BY-NC-SA 4.0
* (c) Peter Müller <peter@crycode.de> (https://crycode.de/promise-queue-in-typescript)
*/
export class PromiseQueue {
/**
* Eingereihte Promises.
*/
private queue: QueuedPromise<unknown>[] = [];
/**
* Indikator, dass aktuell ein Promise abgearbeitet wird.
*/
private working = false;
/**
* Ein Promise einreihen.
* Dies fügt das Promise der Warteschlange hinzu. Wenn die Warteschlange leer
* ist, dann wird das Promise sofort gestartet.
* @param promise Funktion, die das Promise zurückgibt.
* @returns Ein Promise, welches eingelöst (oder zurückgewiesen) wird sobald das eingereihte Promise abgearbeitet ist.
*/
public enqueue<T = void>(promise: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push({
promise,
resolve: resolve as (value: unknown) => void,
reject,
});
this.dequeue();
});
}
/**
* Das erste Promise aus der Warteschlange holen und starten, sofern nicht
* bereits ein Promise aktiv ist.
* @returns `true` wenn ein Promise aus der Warteschlange gestartet wurde oder `false` wenn bereits ein Promise aktiv oder die Warteschlange leer ist.
*/
private dequeue(): boolean {
if (this.working) {
return false;
}
const item = this.queue.shift();
if (!item) {
return false;
}
try {
this.working = true;
item.promise()
.then((value) => {
item.resolve(value);
})
.catch((err) => {
item.reject(err);
})
.finally(() => {
this.working = false;
this.dequeue();
});
} catch (err) {
item.reject(err);
this.working = false;
this.dequeue();
}
return true;
}
}
export class ConcurrentPromiseQueue {
/**
* Eingereihte Promises.
*/
private queues: PromiseQueue[] = [];
constructor(concurrency: number = 1) {
this.queues = Array.from({ length: concurrency }).map(() => {
return new PromiseQueue();
});
}
private queueIndex = 0;
private getQueue() {
this.queueIndex = (this.queueIndex + 1) % this.queues.length;
return this.queues[this.queueIndex];
}
public enqueue<T = void>(promise: () => Promise<T>): Promise<T> {
return this.getQueue().enqueue(promise);
}
}

View File

@@ -61,7 +61,9 @@ export async function createRecommendationResource(
const d = typeof datePublished === "string"
? new Date(datePublished)
: datePublished;
resource.year = d.getFullYear();
if (!isNaN(d.getTime())) {
resource.year = d.getFullYear();
}
}
cache.set(cacheId, JSON.stringify(resource));

View File

@@ -1,110 +0,0 @@
import { transcribe } from "@lib/openai.ts";
import { createResource } from "@lib/marka/index.ts";
import { createLogger } from "./log/index.ts";
import { convertOggToMp3 } from "./helpers.ts";
const log = createLogger("taskManager");
// In-memory task state
const activeTasks: Record<
string,
{
noteName: string;
entries: Array<
{ type: string; content: string | ArrayBufferLike; fileName?: string }
>;
}
> = {};
export function startTask(chatId: string, noteName: string) {
activeTasks[chatId] = { noteName, entries: [] };
log.info(`Started note: ${noteName}`);
}
export async function endTask(chatId: string): Promise<string | null> {
const task = activeTasks[chatId];
if (!task) return null;
log.info("Ending note", task.noteName);
let finalNote = `# ${task.noteName}\n\n`;
const photoTasks: { content: ArrayBuffer; path: string }[] = [];
let photoIndex = 0;
for (const entry of task.entries) {
if (entry.type === "text") {
finalNote += entry.content + "\n\n";
} else if (entry.type === "voice") {
try {
log.info("Converting OGG to MP3");
const mp3Data = await convertOggToMp3(entry.content as ArrayBuffer);
log.info("Finished converting OGG to MP3, transcribing...");
const transcript = await transcribe(mp3Data);
finalNote += `**Voice Transcript:**\n${transcript}\n\n`;
log.info("Finished transcribing");
} catch (error) {
log.error(error);
finalNote += "**[Voice message could not be transcribed]**\n\n";
}
} else if (entry.type === "photo") {
const photoUrl = `${
task.noteName.replace(/\.md$/, "")
}/photo-${photoIndex++}.jpg`;
finalNote += `**Photo**:\n ${photoUrl}\n\n`;
photoTasks.push({
content: entry.content as ArrayBuffer,
path: photoUrl,
});
}
}
try {
for (const entry of photoTasks) {
await createResource(entry.path, entry.content);
}
} catch (err) {
log.error("Error creating photo document:", err);
}
try {
await createResource(task.noteName, finalNote);
} catch (error) {
log.error("Error creating document:", error);
return error instanceof Error
? error.toString()
: "Error creating document";
}
delete activeTasks[chatId];
log.debug({ finalNote });
return finalNote;
}
export function addTextEntry(chatId: string, text: string) {
const task = activeTasks[chatId];
if (!task) return;
const entry = { type: "text", content: text };
log.debug("New Entry", entry);
task.entries.push(entry);
}
export function addVoiceEntry(chatId: string, buffer: ArrayBufferLike) {
const task = activeTasks[chatId];
if (!task) return;
const entry = { type: "voice", content: buffer };
log.debug("New Entry", entry);
task.entries.push(entry);
}
export function addPhotoEntry(
chatId: string,
buffer: ArrayBufferLike,
fileName: string,
) {
const task = activeTasks[chatId];
if (!task) return;
const entry = { type: "photo", content: buffer, fileName };
log.debug("New Entry", entry);
task.entries.push(entry);
}

View File

@@ -1,65 +0,0 @@
import { Bot } from "grammy";
import { TELEGRAM_API_KEY } from "@lib/env.ts";
import { createLogger } from "./log/index.ts";
const bot = new Bot(TELEGRAM_API_KEY);
const log = createLogger("telegram");
import * as manager from "./taskManager.ts";
async function downloadFile(filePath: string): Promise<Uint8Array> {
log.info(`Downloading file from path: ${filePath}`);
const url = `https://api.telegram.org/file/bot${bot.token}/${filePath}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error("Failed to download file: " + response.statusText);
}
log.info("File downloaded successfully");
const buffer = await response.arrayBuffer();
return new Uint8Array(buffer);
}
bot.command("start", async (ctx) => {
log.info("Received /start command");
const [_, noteName] = ctx.message?.text?.split(" ") || [];
if (!noteName) {
return ctx.reply("Please provide a note name. Usage: /start NoteName");
}
manager.startTask(ctx.chat.id.toString(), noteName);
await ctx.reply(`Started note: ${noteName}`);
});
bot.command("end", async (ctx) => {
log.info("Received /end command");
const finalNote = await manager.endTask(ctx.chat.id.toString());
if (!finalNote) return ctx.reply("No active note found.");
try {
await ctx.reply("Note complete. Here is your markdown:");
await ctx.reply(finalNote, { parse_mode: "MarkdownV2" });
} catch (err) {
console.error("Error sending final note:", err);
}
});
bot.on("message:text", (ctx) => {
log.info("Received text message");
manager.addTextEntry(ctx.chat.id.toString(), ctx.message.text);
});
bot.on("message:voice", async (ctx) => {
log.info("Received photo message");
log.info("Received voice message");
const file = await ctx.getFile();
const buffer = await downloadFile(file.file_path!);
manager.addVoiceEntry(ctx.chat.id.toString(), buffer.buffer);
});
bot.on("message:photo", async (ctx) => {
const file = await ctx.getFile();
const buffer = await downloadFile(file.file_path!);
const fileName = file.file_path!.split("/").pop()!;
manager.addPhotoEntry(ctx.chat.id.toString(), buffer.buffer, fileName);
});
bot.start();