Skip to content

Commit d1330d3

Browse files
authored
fix: image edit (#6)
1 parent 3d9e6b0 commit d1330d3

File tree

6 files changed

+16
-98
lines changed

6 files changed

+16
-98
lines changed

.env.node.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
DATABASE_URL="file:./db.sqlite"
55

66
# File Storage
7-
FILE_STORAGE="base64" # Options: "base64"(default), "local", "r2"
7+
FILE_STORAGE="base64" # Options: "base64"(default), "disk", "r2"
88

99
# Local File Storage Configuration
10-
FILE_STORAGE_LOCAL_PATH=".files" # Path to store files locally, default is .files directory
10+
# FILE_STORAGE_DISK_PATH=".files" # Path to store files locally, default is .files directory
1111

1212
# For more Environment Variables, see wrangler.toml [vars]
1313
# ...

src/app/components/dev/DatabaseStudio.tsx

Lines changed: 5 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { Badge } from "@/app/components/ui/badge";
22
import { Button } from "@/app/components/ui/button";
3-
import { Card, CardContent, CardHeader, CardTitle } from "@/app/components/ui/card";
43
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/app/components/ui/dialog";
5-
import { ScrollArea } from "@/app/components/ui/scroll-area";
6-
import { Separator } from "@/app/components/ui/separator";
74
import { Textarea } from "@/app/components/ui/textarea";
85
import { getDb } from "@/app/lib/db-client";
96
import { Database } from "lucide-react";
@@ -106,85 +103,6 @@ export function DatabaseStudio() {
106103
setIsOpen(false);
107104
};
108105

109-
const renderTable = () => {
110-
if (!result?.columns || !result?.rows) return null;
111-
112-
return (
113-
<div className="rounded-md border">
114-
<ScrollArea className="h-[400px]">
115-
<table className="w-full text-sm">
116-
<thead className="border-b bg-muted/50">
117-
<tr>
118-
{result.columns.map((column) => (
119-
<th key={column} className="p-2 text-left font-medium">
120-
{column}
121-
</th>
122-
))}
123-
</tr>
124-
</thead>
125-
<tbody>
126-
{result.rows.map((row, rowIndex) => (
127-
<tr key={`row-${JSON.stringify(row)}-${rowIndex}`} className="border-b hover:bg-muted/50">
128-
{result.columns!.map((column) => (
129-
<td key={`${rowIndex}-${column}`} className="p-2">
130-
{row[column] !== null && row[column] !== undefined ? (
131-
String(row[column])
132-
) : (
133-
<span className="text-muted-foreground italic">NULL</span>
134-
)}
135-
</td>
136-
))}
137-
</tr>
138-
))}
139-
</tbody>
140-
</table>
141-
</ScrollArea>
142-
</div>
143-
);
144-
};
145-
146-
const renderResult = () => {
147-
if (!result) return null;
148-
149-
return (
150-
<Card>
151-
<CardHeader className="flex flex-row items-center justify-between">
152-
<CardTitle className="text-lg">Query Results</CardTitle>
153-
<div className="flex items-center gap-2">
154-
{result.executionTime && <Badge variant="secondary">{result.executionTime.toFixed(2)}ms</Badge>}
155-
<Button variant="outline" size="sm" onClick={clearResult}>
156-
Clear
157-
</Button>
158-
</div>
159-
</CardHeader>
160-
<CardContent>
161-
{result.error ? (
162-
<div className="rounded-md border border-destructive/20 bg-destructive/10 p-4">
163-
<p className="font-medium text-destructive">Error:</p>
164-
<p className="mt-1 text-destructive/80 text-sm">{result.error}</p>
165-
</div>
166-
) : result.columns && result.rows ? (
167-
<div>
168-
<div className="mb-4 flex items-center gap-4">
169-
<Badge variant="outline">{result.rows.length} rows</Badge>
170-
<Badge variant="outline">{result.columns.length} columns</Badge>
171-
</div>
172-
{renderTable()}
173-
</div>
174-
) : (
175-
<div>
176-
<div className="flex items-center gap-4">
177-
{typeof result.changes === "number" && <Badge variant="outline">Affected rows: {result.changes}</Badge>}
178-
{result.lastInsertRowid && <Badge variant="outline">Insert ID: {String(result.lastInsertRowid)}</Badge>}
179-
</div>
180-
<p className="mt-2 text-muted-foreground">Query execution completed</p>
181-
</div>
182-
)}
183-
</CardContent>
184-
</Card>
185-
);
186-
};
187-
188106
return (
189107
<>
190108
{/* Floating Button */}
@@ -207,7 +125,7 @@ export function DatabaseStudio() {
207125
</DialogTitle>
208126
</DialogHeader>
209127

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

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

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

290208
{/* Table - Fill remaining space */}
291209
<div className="w-full flex-1 overflow-hidden">
292-
<ScrollArea className="h-full w-full">
210+
<div className="w-full overflow-x-auto pb-2">
293211
<table className="w-full text-sm" style={{ tableLayout: "auto", minWidth: "100%" }}>
294212
<thead className="sticky top-0 border-b bg-muted/50">
295213
<tr>
@@ -335,8 +253,7 @@ export function DatabaseStudio() {
335253
))}
336254
</tbody>
337255
</table>
338-
<div className="h-6" />
339-
</ScrollArea>
256+
</div>
340257
</div>
341258
</div>
342259
) : (

src/server/ai/provider/cloudflare.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const createFormData = (params: any, model: CloudflareAiModel, request: TypixGen
2727

2828
// Append images with numbered parameter names
2929
for (let i = 0; i < Math.min(images.length, maxInputImages); i++) {
30-
const imageBlob = base64ToBlob(images[i]!);
30+
const imageBlob = base64ToBlob(dataURItoBase64(images[i]!));
3131
form.append(`input_image_${i}`, imageBlob);
3232
}
3333
}
@@ -76,7 +76,7 @@ const generateSingle = async (request: TypixGenerateRequest, settings: ApiProvid
7676
params.height = size?.height;
7777
}
7878
if (genType === "i2i") {
79-
params.image_b64 = request.images![0]!;
79+
params.image_b64 = dataURItoBase64(request.images![0]!);
8080
}
8181

8282
// Built-in Cloudflare Worker AI

src/server/lib/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function base64ToBlob(base64: string, mimeType = "image/png") {
1313
}
1414

1515
export function dataURItoBase64(dataURI: string) {
16-
return dataURI.split(",")[1];
16+
return dataURI.split(",")[1]!;
1717
}
1818

1919
export async function readableStreamToDataURI(stream: ReadableStream<Uint8Array>, fmt = "png") {

src/server/service/file/storage.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type Storage, files } from "@/server/db/schemas";
22
import { inBrowser } from "@/server/lib/env";
3-
import { fetchUrlToDataURI } from "@/server/lib/util";
3+
import { base64ToDataURI, fetchUrlToDataURI } from "@/server/lib/util";
44
import { and, eq } from "drizzle-orm";
55
import { getContext } from "../context";
66

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

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

139139
switch (metadata.protocol) {
140140
case "data:":
141-
return metadata.accessUrl.split(",")[1]!; // Return base64 part only
141+
return metadata.accessUrl;
142142
case "file:": {
143143
const fs = await import("node:fs/promises");
144-
return await fs.readFile(metadata.accessUrl, "base64");
144+
const fileSuffix = metadata.accessUrl.split(".").pop();
145+
return base64ToDataURI(await fs.readFile(metadata.accessUrl, "base64"), fileSuffix);
145146
}
146147
default:
147148
return await fetchUrlToDataURI(metadata.accessUrl);

wrangler.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ binding = "AI"
3030
# Authenticated users benefit from cross-device data sync and server-side data persistence.
3131
# Provides the best of both worlds: instant local access with optional cloud features.
3232
MODE = "client"
33-
GOOGLE_ANALYTICS_ID = "G-0T65G1J5DT"
33+
# GOOGLE_ANALYTICS_ID = "G-0T65G1J5DT"
3434

3535
# Authentication configuration, only for mixed mode
3636
#

0 commit comments

Comments
 (0)