Delete image-refine-vibe reference folder

- Removed the `image-refine-vibe` directory as the redesign based on it has been successfully implemented and approved.
This commit is contained in:
google-labs-jules[bot] committed 2026-02-16 08:18:06 +00:00
1 parent 180408b883
commit b5296e8a08
15 files changed
-829

No files matched your search

-24
View File
@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
-124
View File
@@ -1,124 +0,0 @@
import React, { useState, useCallback } from 'react'
import { UploadStage } from './components/UploadStage'
import { AnalyzeStage } from './components/AnalyzeStage'
import { SplitStage } from './components/SplitStage'
import { RefineStage } from './components/RefineStage'
import { sliceImage } from './utils/imageProcessor'
import { AppStage, ImageSegment } from './types'
const App: React.FC = () => {
const [stage, setStage] = useState<AppStage>('upload')
const [uploadedImage, setUploadedImage] = useState<string | null>(null)
const [segments, setSegments] = useState<ImageSegment[]>([])
// 1. Handle Upload
const handleImageUpload = useCallback((imageDataUrl: string) => {
setUploadedImage(imageDataUrl)
setStage('analyze')
}, [])
// 2. Handle Analysis Completion
const handleAnalysisComplete = useCallback(async () => {
if (!uploadedImage) return
try {
// Slice image into 3x3 grid
const slicedImages = await sliceImage(uploadedImage, 3, 3)
const newSegments: ImageSegment[] = slicedImages.map((src, index) => ({
id: `seg-${index}`,
src,
selected: true, // Default all selected
status: 'pending',
}))
setSegments(newSegments)
setStage('split')
} catch (error) {
console.error('Failed to slice image', error)
// Fallback or error handling could go here
}
}, [uploadedImage])
// 3. Handle Split Confirmation
const handleSplitConfirm = useCallback(
(selectedIds: string[]) => {
// Filter out unselected items for the refine view
const selectedSegments = segments.filter((seg) => selectedIds.includes(seg.id))
setSegments(selectedSegments)
setStage('refine')
},
[segments],
)
// 4. Reset
const handleReset = useCallback(() => {
setUploadedImage(null)
setSegments([])
setStage('upload')
}, [])
return (
<div className="min-h-screen bg-slate-950 text-slate-100 selection:bg-sky-500/30 selection:text-sky-200 overflow-hidden">
{/* Background Ambience */}
<div className="fixed inset-0 pointer-events-none">
<div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-purple-900/20 rounded-full blur-[120px]" />
<div className="absolute bottom-[-10%] right-[-10%] w-[40%] h-[40%] bg-sky-900/20 rounded-full blur-[120px]" />
</div>
<main className="relative z-10 w-full h-screen flex flex-col">
{/* Navigation / Header (Minimal) */}
<header className="px-6 py-4 flex items-center justify-between border-b border-white/5 bg-slate-900/50 backdrop-blur-sm">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-gradient-to-tr from-sky-500 to-purple-600 rounded-lg flex items-center justify-center shadow-lg">
<span className="font-bold text-white text-lg">AI</span>
</div>
<h1 className="font-semibold text-lg tracking-tight text-slate-200">
Refine<span className="text-sky-400">Lab</span>
</h1>
</div>
{/* Progress Steps */}
{stage !== 'upload' && (
<div className="hidden sm:flex items-center gap-2 text-xs font-medium bg-black/20 p-1 rounded-full border border-white/5">
<div
className={`px-3 py-1 rounded-full transition-colors ${stage === 'analyze' ? 'bg-sky-500/20 text-sky-400' : 'text-slate-500'}`}
>
Analyze
</div>
<div className="w-4 h-[1px] bg-slate-800"></div>
<div
className={`px-3 py-1 rounded-full transition-colors ${stage === 'split' ? 'bg-sky-500/20 text-sky-400' : 'text-slate-500'}`}
>
Split
</div>
<div className="w-4 h-[1px] bg-slate-800"></div>
<div
className={`px-3 py-1 rounded-full transition-colors ${stage === 'refine' ? 'bg-purple-500/20 text-purple-400' : 'text-slate-500'}`}
>
Refine
</div>
</div>
)}
</header>
{/* Content Area */}
<div className="flex-1 relative overflow-hidden">
{stage === 'upload' && <UploadStage onImageUpload={handleImageUpload} />}
{stage === 'analyze' && uploadedImage && (
<AnalyzeStage imageSrc={uploadedImage} onAnalysisComplete={handleAnalysisComplete} />
)}
{stage === 'split' && (
<SplitStage segments={segments} onConfirm={handleSplitConfirm} onBack={handleReset} />
)}
{stage === 'refine' && <RefineStage initialSegments={segments} onDone={handleReset} />}
</div>
</main>
</div>
)
}
export default App
-19
View File
@@ -1,19 +0,0 @@
<div align="center">
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
</div>
# Run and deploy your AI Studio app
This contains everything you need to run your app locally.
View your app in AI Studio: https://ai.studio/apps/drive/1P0JLZhnyDHgV03_9rLIzEixF3AY0-NQU
## Run Locally
**Prerequisites:** Node.js
1. Install dependencies:
`npm install`
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
3. Run the app:
`npm run dev`
@@ -1,62 +0,0 @@
import React, { useEffect } from 'react'
import { Scan, BrainCircuit } from 'lucide-react'
interface AnalyzeStageProps {
imageSrc: string
onAnalysisComplete: () => void
}
export const AnalyzeStage: React.FC<AnalyzeStageProps> = ({ imageSrc, onAnalysisComplete }) => {
useEffect(() => {
const timer = setTimeout(() => {
onAnalysisComplete()
}, 5000)
return () => clearTimeout(timer)
}, [onAnalysisComplete])
return (
<div className="relative flex flex-col items-center justify-center w-full h-full overflow-hidden bg-slate-900">
{/* Background Frosted Glass Layer */}
<div
className="absolute inset-0 w-full h-full z-0 opacity-40 blur-3xl scale-110 pointer-events-none transition-opacity duration-1000"
style={{
backgroundImage: `url(${imageSrc})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
}}
/>
{/* Main Container */}
<div className="relative z-10 flex flex-col items-center justify-center w-full max-w-4xl h-[80vh] p-8">
{/* Image Container with Scanning Effect */}
<div className="relative w-full h-full rounded-2xl overflow-hidden shadow-2xl ring-1 ring-white/10 bg-black/40 backdrop-blur-sm group">
<img src={imageSrc} alt="Analyzing" className="w-full h-full object-contain" />
{/* Scan Line Animation */}
<div className="scan-line"></div>
{/* Grid Overlay Effect (Subtle) */}
<div className="absolute inset-0 bg-[linear-gradient(rgba(56,189,248,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(56,189,248,0.03)_1px,transparent_1px)] bg-[size:40px_40px] pointer-events-none"></div>
{/* Corner Brackets UI */}
<div className="absolute top-4 left-4 w-8 h-8 border-t-2 border-l-2 border-sky-500/50 rounded-tl-lg"></div>
<div className="absolute top-4 right-4 w-8 h-8 border-t-2 border-r-2 border-sky-500/50 rounded-tr-lg"></div>
<div className="absolute bottom-4 left-4 w-8 h-8 border-b-2 border-l-2 border-sky-500/50 rounded-bl-lg"></div>
<div className="absolute bottom-4 right-4 w-8 h-8 border-b-2 border-r-2 border-sky-500/50 rounded-br-lg"></div>
</div>
{/* Status Text */}
<div className="mt-8 flex items-center space-x-3 px-6 py-3 rounded-full bg-slate-800/80 backdrop-blur-md border border-white/10 shadow-xl">
<BrainCircuit className="w-5 h-5 text-sky-400 animate-pulse" />
<span className="text-slate-200 font-medium tracking-wide">Analyzing Structure...</span>
<div className="flex space-x-1 ml-2">
<div className="w-1.5 h-1.5 rounded-full bg-sky-400 animate-bounce [animation-delay:-0.3s]"></div>
<div className="w-1.5 h-1.5 rounded-full bg-sky-400 animate-bounce [animation-delay:-0.15s]"></div>
<div className="w-1.5 h-1.5 rounded-full bg-sky-400 animate-bounce"></div>
</div>
</div>
</div>
</div>
)
}
@@ -1,135 +0,0 @@
import React, { useEffect, useState, useRef } from 'react'
import { ImageSegment } from '../types'
import { Loader2, CheckCircle2, Sparkles, RefreshCw } from 'lucide-react'
interface RefineStageProps {
initialSegments: ImageSegment[]
onDone: () => void
}
export const RefineStage: React.FC<RefineStageProps> = ({ initialSegments, onDone }) => {
const [segments, setSegments] = useState<ImageSegment[]>(initialSegments)
const [processingIndex, setProcessingIndex] = useState(0)
const [isAllDone, setIsAllDone] = useState(false)
const processingRef = useRef(false)
useEffect(() => {
// We only process segments that were selected
const selectedSegments = segments.filter((s) => s.selected)
if (processingIndex >= selectedSegments.length) {
setIsAllDone(true)
return
}
if (processingRef.current) return
processingRef.current = true
const currentSegmentId = selectedSegments[processingIndex].id
// Set current to processing
setSegments((prev) =>
prev.map((s) => (s.id === currentSegmentId ? { ...s, status: 'processing' } : s)),
)
const processTimer = setTimeout(() => {
// Mark as completed and move next
setSegments((prev) =>
prev.map((s) => (s.id === currentSegmentId ? { ...s, status: 'completed' } : s)),
)
setProcessingIndex((prev) => prev + 1)
processingRef.current = false
}, 2000)
return () => clearTimeout(processTimer)
}, [processingIndex, segments])
return (
<div className="flex flex-col h-full w-full max-w-6xl mx-auto px-4 py-6">
{/* Header */}
<div className="flex items-center justify-between mb-8">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Sparkles className="text-purple-400" />
Refining Images
</h2>
<p className="text-slate-400 text-sm mt-1">
{isAllDone
? 'All selected segments have been processed successfully.'
: `Processing item ${processingIndex + 1} of ${segments.length}...`}
</p>
</div>
{isAllDone && (
<button
onClick={onDone}
className="flex items-center gap-2 px-6 py-2.5 rounded-lg bg-green-500 hover:bg-green-400 text-white font-medium shadow-lg shadow-green-500/20 transition-all"
>
<RefreshCw className="w-4 h-4" />
Start Over
</button>
)}
</div>
{/* Grid */}
<div className="flex-1 overflow-y-auto pr-2 pb-10">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
{segments.map((seg) => (
<div
key={seg.id}
className={`
relative aspect-square rounded-2xl overflow-hidden border-2 transition-all duration-500
${
seg.status === 'processing'
? 'border-purple-500 shadow-xl shadow-purple-500/20 scale-105 z-10'
: seg.status === 'completed'
? 'border-green-500/50 shadow-md'
: 'border-slate-800 opacity-60 grayscale'
}
`}
>
<img src={seg.src} alt="segment" className="w-full h-full object-cover" />
{/* Overlay states */}
<div className="absolute inset-0 flex items-center justify-center bg-black/40 backdrop-blur-[2px] transition-opacity duration-300">
{seg.status === 'pending' && (
<div className="px-3 py-1 rounded-full bg-slate-900/80 text-xs text-slate-400 border border-white/10">
Pending
</div>
)}
{seg.status === 'processing' && (
<div className="flex flex-col items-center gap-3">
<div className="relative">
<div className="absolute inset-0 bg-purple-500 rounded-full blur-md opacity-50 animate-pulse"></div>
<div className="relative bg-slate-900 rounded-full p-3 border border-purple-500/50">
<Loader2 className="w-8 h-8 text-purple-400 animate-spin" />
</div>
</div>
<span className="text-purple-200 text-sm font-medium animate-pulse">
Refining...
</span>
</div>
)}
{seg.status === 'completed' && (
<div className="absolute inset-0 flex items-center justify-center bg-transparent backdrop-blur-none">
{/* Clean view for completed, just a subtle indicator */}
<div className="absolute top-3 right-3 bg-green-500 text-white p-1.5 rounded-full shadow-lg animate-scale-in">
<CheckCircle2 className="w-5 h-5" />
</div>
</div>
)}
</div>
{/* Scanning effect for processing item only */}
{seg.status === 'processing' && (
<div className="absolute inset-0 w-full h-full bg-gradient-to-b from-transparent via-purple-500/10 to-transparent -translate-y-full animate-[scan_1.5s_linear_infinite]" />
)}
</div>
))}
</div>
</div>
</div>
)
}
-120
View File
@@ -1,120 +0,0 @@
import React, { useState } from 'react'
import { ImageSegment } from '../types'
import { Check, Grid2X2, ArrowRight } from 'lucide-react'
interface SplitStageProps {
segments: ImageSegment[]
onConfirm: (selectedIds: string[]) => void
onBack: () => void
}
export const SplitStage: React.FC<SplitStageProps> = ({ segments, onConfirm, onBack }) => {
const [localSegments, setLocalSegments] = useState<ImageSegment[]>(segments)
const toggleSelection = (id: string) => {
setLocalSegments((prev) =>
prev.map((seg) => (seg.id === id ? { ...seg, selected: !seg.selected } : seg)),
)
}
const selectedCount = localSegments.filter((s) => s.selected).length
const handleConfirm = () => {
if (selectedCount === 0) return
const selectedIds = localSegments.filter((s) => s.selected).map((s) => s.id)
onConfirm(selectedIds)
}
return (
<div className="flex flex-col h-full w-full max-w-6xl mx-auto px-4 py-6">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Grid2X2 className="text-sky-400" />
Select Segments
</h2>
<p className="text-slate-400 text-sm mt-1">
Unselect any areas you wish to exclude from processing.
</p>
</div>
<div className="px-4 py-2 bg-slate-800 rounded-lg border border-slate-700">
<span className="text-sky-400 font-bold text-lg">{selectedCount}</span>
<span className="text-slate-400 ml-2 text-sm">Selected</span>
</div>
</div>
{/* Grid */}
<div className="flex-1 overflow-y-auto min-h-0 pr-2">
<div className="grid grid-cols-3 gap-4 pb-20">
{localSegments.map((seg) => (
<div
key={seg.id}
onClick={() => toggleSelection(seg.id)}
className={`
group relative aspect-square rounded-xl overflow-hidden cursor-pointer transition-all duration-300
border-2
${
seg.selected
? 'border-sky-500 shadow-lg shadow-sky-500/20 scale-[1.01]'
: 'border-slate-700 opacity-60 hover:opacity-100 hover:border-slate-500'
}
`}
>
<img src={seg.src} alt="segment" className="w-full h-full object-cover" />
{/* Selection Overlay */}
<div
className={`
absolute inset-0 bg-sky-900/20 transition-opacity duration-200
${seg.selected ? 'opacity-100' : 'opacity-0'}
`}
/>
{/* Checkbox Indicator */}
<div
className={`
absolute top-3 right-3 w-8 h-8 rounded-full flex items-center justify-center transition-all duration-200
${
seg.selected
? 'bg-sky-500 text-white shadow-lg scale-100'
: 'bg-black/50 border border-white/30 text-transparent scale-90 group-hover:border-white/60'
}
`}
>
<Check className="w-5 h-5" />
</div>
</div>
))}
</div>
</div>
{/* Footer Actions */}
<div className="fixed bottom-0 left-0 right-0 p-6 bg-slate-900/90 backdrop-blur-md border-t border-slate-800 z-50">
<div className="max-w-6xl mx-auto flex justify-end items-center gap-4">
<button
onClick={onBack}
className="px-6 py-3 rounded-xl text-slate-400 hover:text-white hover:bg-slate-800 transition-colors"
>
Cancel
</button>
<button
onClick={handleConfirm}
disabled={selectedCount === 0}
className={`
flex items-center gap-2 px-8 py-3 rounded-xl font-semibold transition-all shadow-lg
${
selectedCount > 0
? 'bg-sky-500 hover:bg-sky-400 text-white shadow-sky-500/25 hover:shadow-sky-500/40 translate-y-0'
: 'bg-slate-800 text-slate-500 cursor-not-allowed'
}
`}
>
Start Refining
<ArrowRight className="w-4 h-4" />
</button>
</div>
</div>
</div>
)
}
@@ -1,94 +0,0 @@
import React, { useCallback, useState } from 'react'
import { Upload, Image as ImageIcon } from 'lucide-react'
import { readFileAsDataURL } from '../utils/imageProcessor'
interface UploadStageProps {
onImageUpload: (imageDataUrl: string) => void
}
export const UploadStage: React.FC<UploadStageProps> = ({ onImageUpload }) => {
const [isDragging, setIsDragging] = useState(false)
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault()
setIsDragging(true)
}, [])
const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault()
setIsDragging(false)
}, [])
const handleDrop = useCallback(
async (e: React.DragEvent) => {
e.preventDefault()
setIsDragging(false)
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
const file = e.dataTransfer.files[0]
if (file.type.startsWith('image/')) {
try {
const dataUrl = await readFileAsDataURL(file)
onImageUpload(dataUrl)
} catch (error) {
console.error('Error reading file:', error)
}
}
}
},
[onImageUpload],
)
const handleFileChange = useCallback(
async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
try {
const dataUrl = await readFileAsDataURL(e.target.files[0])
onImageUpload(dataUrl)
} catch (error) {
console.error('Error reading file:', error)
}
}
},
[onImageUpload],
)
return (
<div className="flex flex-col items-center justify-center h-full w-full p-8 animate-fade-in">
<div
className={`
relative flex flex-col items-center justify-center w-full max-w-2xl h-96
rounded-3xl border-2 border-dashed transition-all duration-300 ease-in-out
${
isDragging
? 'border-sky-400 bg-sky-400/10 scale-[1.02]'
: 'border-slate-600 bg-slate-800/50 hover:border-slate-500 hover:bg-slate-800/80'
}
`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<div className="flex flex-col items-center justify-center space-y-4 text-center p-6">
<div className="p-4 rounded-full bg-slate-700/50 ring-1 ring-white/10">
<Upload className="w-8 h-8 text-sky-400" />
</div>
<div className="space-y-2">
<h3 className="text-xl font-semibold text-slate-200">Upload an Image</h3>
<p className="text-sm text-slate-400 max-w-xs mx-auto">
Drag and drop your image here, or click to browse files
</p>
</div>
<label className="cursor-pointer">
<input type="file" className="hidden" accept="image/*" onChange={handleFileChange} />
<span className="inline-flex items-center px-6 py-2.5 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-medium transition-colors shadow-lg shadow-sky-500/20">
<ImageIcon className="w-4 h-4 mr-2" />
Select File
</span>
</label>
</div>
</div>
<p className="mt-8 text-slate-500 text-sm">Supports JPG, PNG, WEBP up to 10MB</p>
</div>
)
}
-76
View File
@@ -1,76 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Image Refine Tool</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #0f172a; /* Slate 900 */
color: #f8fafc;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #334155;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #475569;
}
/* Scan Animation */
@keyframes scan {
0% {
top: 0%;
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
top: 100%;
opacity: 0;
}
}
.scan-line {
position: absolute;
width: 100%;
height: 4px;
background: linear-gradient(90deg, transparent, #38bdf8, transparent);
box-shadow: 0 0 15px #38bdf8;
animation: scan 2.5s linear infinite;
left: 0;
z-index: 10;
}
</style>
<script type="importmap">
{
"imports": {
"react/": "https://esm.sh/react@^19.2.4/",
"react": "https://esm.sh/react@^19.2.4",
"lucide-react": "https://esm.sh/lucide-react@^0.564.0",
"react-dom/": "https://esm.sh/react-dom@^19.2.4/"
}
}
</script>
<link rel="stylesheet" href="/index.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/index.tsx"></script>
</body>
</html>
-15
View File
@@ -1,15 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
const rootElement = document.getElementById('root')
if (!rootElement) {
throw new Error('Could not find root element to mount to')
}
const root = ReactDOM.createRoot(rootElement)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
-5
View File
@@ -1,5 +0,0 @@
{
"name": "Image Split & Refine Tool",
"description": "A sophisticated image processing tool that analyzes, splits, and refines images with a modern frosted glass UI.",
"requestFramePermissions": []
}
-22
View File
@@ -1,22 +0,0 @@
{
"name": "image-split-&-refine-tool",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.2.4",
"lucide-react": "^0.564.0",
"react-dom": "^19.2.4"
},
"devDependencies": {
"@types/node": "^22.14.0",
"@vitejs/plugin-react": "^5.0.0",
"typescript": "~5.8.2",
"vite": "^6.2.0"
}
}
-21
View File
@@ -1,21 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"types": ["node"],
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"allowJs": true,
"jsx": "react-jsx",
"paths": {
"@/*": ["./*"]
},
"allowImportingTsExtensions": true,
"noEmit": true
}
}
-13
View File
@@ -1,13 +0,0 @@
export type AppStage = 'upload' | 'analyze' | 'split' | 'refine' | 'completed'
export interface ImageSegment {
id: string
src: string
selected: boolean
status: 'pending' | 'processing' | 'completed'
}
export interface ProcessingState {
isProcessing: boolean
currentId: string | null
}
-76
View File
@@ -1,76 +0,0 @@
/**
* Slices an image into a grid of smaller images.
* @param imageSrc The source image data URL.
* @param rows Number of rows.
* @param cols Number of columns.
* @returns A promise resolving to an array of data URLs for the segments.
*/
export const sliceImage = (
imageSrc: string,
rows: number = 3,
cols: number = 3,
): Promise<string[]> => {
return new Promise((resolve, reject) => {
const img = new Image()
img.crossOrigin = 'Anonymous'
img.onload = () => {
const segments: string[] = []
const segmentWidth = Math.floor(img.width / cols)
const segmentHeight = Math.floor(img.height / rows)
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx) {
reject(new Error('Could not get canvas context'))
return
}
canvas.width = segmentWidth
canvas.height = segmentHeight
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
// Clear canvas for next segment
ctx.clearRect(0, 0, segmentWidth, segmentHeight)
// Draw the specific slice
ctx.drawImage(
img,
x * segmentWidth,
y * segmentHeight,
segmentWidth,
segmentHeight,
0,
0,
segmentWidth,
segmentHeight,
)
segments.push(canvas.toDataURL('image/png'))
}
}
resolve(segments)
}
img.onerror = (err) => reject(err)
img.src = imageSrc
})
}
/**
* Reads a file input as a Data URL.
*/
export const readFileAsDataURL = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (e) => {
if (e.target?.result) {
resolve(e.target.result as string)
} else {
reject(new Error('Failed to read file'))
}
}
reader.onerror = reject
reader.readAsDataURL(file)
})
}
-23
View File
@@ -1,23 +0,0 @@
import path from 'path'
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, '.', '')
return {
server: {
port: 3000,
host: '0.0.0.0',
},
plugins: [react()],
define: {
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
},
},
}
})