Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.node.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
DATABASE_URL="file:./db.sqlite"

# File Storage
FILE_STORAGE="base64" # Options: "base64"(default), "local", "r2"
FILE_STORAGE="base64" # Options: "base64"(default), "disk", "r2"

# Local File Storage Configuration
FILE_STORAGE_LOCAL_PATH=".files" # Path to store files locally, default is .files directory
# FILE_STORAGE_DISK_PATH=".files" # Path to store files locally, default is .files directory

# For more Environment Variables, see wrangler.toml [vars]
# ...
93 changes: 5 additions & 88 deletions src/app/components/dev/DatabaseStudio.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { Badge } from "@/app/components/ui/badge";
import { Button } from "@/app/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/app/components/ui/card";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/app/components/ui/dialog";
import { ScrollArea } from "@/app/components/ui/scroll-area";
import { Separator } from "@/app/components/ui/separator";
import { Textarea } from "@/app/components/ui/textarea";
import { getDb } from "@/app/lib/db-client";
import { Database } from "lucide-react";
Expand Down Expand Up @@ -106,85 +103,6 @@ export function DatabaseStudio() {
setIsOpen(false);
};

const renderTable = () => {
if (!result?.columns || !result?.rows) return null;

return (
<div className="rounded-md border">
<ScrollArea className="h-[400px]">
<table className="w-full text-sm">
<thead className="border-b bg-muted/50">
<tr>
{result.columns.map((column) => (
<th key={column} className="p-2 text-left font-medium">
{column}
</th>
))}
</tr>
</thead>
<tbody>
{result.rows.map((row, rowIndex) => (
<tr key={`row-${JSON.stringify(row)}-${rowIndex}`} className="border-b hover:bg-muted/50">
{result.columns!.map((column) => (
<td key={`${rowIndex}-${column}`} className="p-2">
{row[column] !== null && row[column] !== undefined ? (
String(row[column])
) : (
<span className="text-muted-foreground italic">NULL</span>
)}
</td>
))}
</tr>
))}
</tbody>
</table>
</ScrollArea>
</div>
);
};

const renderResult = () => {
if (!result) return null;

return (
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle className="text-lg">Query Results</CardTitle>
<div className="flex items-center gap-2">
{result.executionTime && <Badge variant="secondary">{result.executionTime.toFixed(2)}ms</Badge>}
<Button variant="outline" size="sm" onClick={clearResult}>
Clear
</Button>
</div>
</CardHeader>
<CardContent>
{result.error ? (
<div className="rounded-md border border-destructive/20 bg-destructive/10 p-4">
<p className="font-medium text-destructive">Error:</p>
<p className="mt-1 text-destructive/80 text-sm">{result.error}</p>
</div>
) : result.columns && result.rows ? (
<div>
<div className="mb-4 flex items-center gap-4">
<Badge variant="outline">{result.rows.length} rows</Badge>
<Badge variant="outline">{result.columns.length} columns</Badge>
</div>
{renderTable()}
</div>
) : (
<div>
<div className="flex items-center gap-4">
{typeof result.changes === "number" && <Badge variant="outline">Affected rows: {result.changes}</Badge>}
{result.lastInsertRowid && <Badge variant="outline">Insert ID: {String(result.lastInsertRowid)}</Badge>}
</div>
<p className="mt-2 text-muted-foreground">Query execution completed</p>
</div>
)}
</CardContent>
</Card>
);
};

return (
<>
{/* Floating Button */}
Expand All @@ -207,7 +125,7 @@ export function DatabaseStudio() {
</DialogTitle>
</DialogHeader>

<div className="flex h-[calc(100vh-80px)] flex-col">
<div className="flex h-[calc(100vh-80px)] flex-col overflow-y-auto pb-6">
{/* SQL Input Area */}
<div className="flex-shrink-0 border-b p-6">
<p className="mb-4 text-muted-foreground text-sm">
Expand Down Expand Up @@ -254,7 +172,7 @@ export function DatabaseStudio() {

{/* Results Display Area - Fill remaining space */}
{result ? (
<div className="flex flex-1 flex-col overflow-hidden">
<div className="flex flex-1 flex-col">
{/* Results Header */}
<div className="flex-shrink-0 border-b p-4">
<div className="flex items-center justify-between">
Expand All @@ -269,7 +187,7 @@ export function DatabaseStudio() {
</div>

{/* Results Content - Fill remaining space */}
<div className="flex-1 overflow-hidden">
<div className="flex-1">
{result.error ? (
<div className="p-6">
<div className="rounded-md border border-destructive/20 bg-destructive/10 p-4">
Expand All @@ -289,7 +207,7 @@ export function DatabaseStudio() {

{/* Table - Fill remaining space */}
<div className="w-full flex-1 overflow-hidden">
<ScrollArea className="h-full w-full">
<div className="w-full overflow-x-auto pb-2">
<table className="w-full text-sm" style={{ tableLayout: "auto", minWidth: "100%" }}>
<thead className="sticky top-0 border-b bg-muted/50">
<tr>
Expand Down Expand Up @@ -335,8 +253,7 @@ export function DatabaseStudio() {
))}
</tbody>
</table>
<div className="h-6" />
</ScrollArea>
</div>
</div>
</div>
) : (
Expand Down
4 changes: 2 additions & 2 deletions src/server/ai/provider/cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const createFormData = (params: any, model: CloudflareAiModel, request: TypixGen

// Append images with numbered parameter names
for (let i = 0; i < Math.min(images.length, maxInputImages); i++) {
const imageBlob = base64ToBlob(images[i]!);
const imageBlob = base64ToBlob(dataURItoBase64(images[i]!));
form.append(`input_image_${i}`, imageBlob);
}
}
Expand Down Expand Up @@ -76,7 +76,7 @@ const generateSingle = async (request: TypixGenerateRequest, settings: ApiProvid
params.height = size?.height;
}
if (genType === "i2i") {
params.image_b64 = request.images![0]!;
params.image_b64 = dataURItoBase64(request.images![0]!);
}

// Built-in Cloudflare Worker AI
Expand Down
2 changes: 1 addition & 1 deletion src/server/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function base64ToBlob(base64: string, mimeType = "image/png") {
}

export function dataURItoBase64(dataURI: string) {
return dataURI.split(",")[1];
return dataURI.split(",")[1]!;
}

export async function readableStreamToDataURI(stream: ReadableStream<Uint8Array>, fmt = "png") {
Expand Down
9 changes: 5 additions & 4 deletions src/server/service/file/storage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Storage, files } from "@/server/db/schemas";
import { inBrowser } from "@/server/lib/env";
import { fetchUrlToDataURI } from "@/server/lib/util";
import { base64ToDataURI, fetchUrlToDataURI } from "@/server/lib/util";
import { and, eq } from "drizzle-orm";
import { getContext } from "../context";

Expand Down Expand Up @@ -120,7 +120,7 @@ export const getFileMetadata = async (fileId: string, userId: string) => {
};

/**
* Get file base64-encoded string
* Get file base64 data URL
* @param fileId File ID to get data for
* @param userId User ID to check access
* @param redirect
Expand All @@ -138,10 +138,11 @@ export const getFileData = async (fileId: string, userId: string) => {

switch (metadata.protocol) {
case "data:":
return metadata.accessUrl.split(",")[1]!; // Return base64 part only
return metadata.accessUrl;
case "file:": {
const fs = await import("node:fs/promises");
return await fs.readFile(metadata.accessUrl, "base64");
const fileSuffix = metadata.accessUrl.split(".").pop();
return base64ToDataURI(await fs.readFile(metadata.accessUrl, "base64"), fileSuffix);
}
default:
return await fetchUrlToDataURI(metadata.accessUrl);
Expand Down
2 changes: 1 addition & 1 deletion wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ binding = "AI"
# Authenticated users benefit from cross-device data sync and server-side data persistence.
# Provides the best of both worlds: instant local access with optional cloud features.
MODE = "client"
GOOGLE_ANALYTICS_ID = "G-0T65G1J5DT"
# GOOGLE_ANALYTICS_ID = "G-0T65G1J5DT"

# Authentication configuration, only for mixed mode
#
Expand Down