Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spike/images inslides #410

Draft
wants to merge 32 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
76c8bc0
spike: cloudinary in
tomwisecodes Nov 25, 2024
9f43157
chore: google
tomwisecodes Nov 26, 2024
b2ee2c6
chore: add google
tomwisecodes Nov 27, 2024
45746a9
feat: image comparison branch
tomwisecodes Nov 28, 2024
c5835ae
chore: types
tomwisecodes Nov 28, 2024
5493388
chore: handle redirect
tomwisecodes Nov 28, 2024
ac63e4a
chore: fall back on title
tomwisecodes Nov 28, 2024
bbcf89a
chore: next image
tomwisecodes Nov 28, 2024
168ae75
chore: add dale
tomwisecodes Nov 29, 2024
8233576
custom pipeline
tomwisecodes Nov 29, 2024
9903e94
chore: types
tomwisecodes Nov 29, 2024
985b84d
chore: custom pipelines
tomwisecodes Dec 2, 2024
a78ef4b
chore: make it alot faster
tomwisecodes Dec 2, 2024
0956dcf
chore: playground working with timigns
tomwisecodes Dec 4, 2024
f4668c1
chore: loggin
tomwisecodes Dec 4, 2024
7e4dcb1
chore: types
tomwisecodes Dec 4, 2024
60fc633
chore: error handling in the end points
tomwisecodes Dec 4, 2024
a39930e
chore: regeneration
tomwisecodes Dec 5, 2024
7390037
chore: buttons
tomwisecodes Dec 5, 2024
215dc86
chore: stuff
tomwisecodes Dec 17, 2024
8b10700
chore: asd
tomwisecodes Dec 18, 2024
a1bbf77
chore: custom prompt
tomwisecodes Jan 7, 2025
8c1570a
chore: useBestImage
tomwisecodes Jan 7, 2025
812e140
chore: new page
tomwisecodes Jan 9, 2025
2a0ef21
chore: types
tomwisecodes Jan 9, 2025
fb0605b
chore: bugs
tomwisecodes Jan 10, 2025
27f48b3
chore: test
tomwisecodes Jan 10, 2025
ba711c3
chore: Next image and some bugs
tomwisecodes Jan 10, 2025
ff07917
chore: optional message ids
tomwisecodes Jan 10, 2025
6d713ae
chore: everything optional
tomwisecodes Jan 10, 2025
519de26
chore: build
tomwisecodes Jan 10, 2025
73eb125
chore: bugz
tomwisecodes Jan 13, 2025
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
21 changes: 20 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,5 +204,24 @@
"file:///c%3A/Users/Simon/.vscode/extensions/atlassian.atlascode-2.8.6/resources/schemas/pipelines-schema.json": "bitbucket-pipelines.yml",
"https://www.artillery.io/schema.json": []
},
"typescript.preferences.preferTypeOnlyAutoImports": true
"typescript.preferences.preferTypeOnlyAutoImports": true,
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#b2bfe1",
"activityBar.background": "#b2bfe1",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#c05e7a",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#15202b99",
"sash.hoverBorder": "#b2bfe1",
"statusBar.background": "#8da0d3",
"statusBar.foreground": "#15202b",
"statusBarItem.hoverBackground": "#6881c5",
"statusBarItem.remoteBackground": "#8da0d3",
"statusBarItem.remoteForeground": "#15202b",
"titleBar.activeBackground": "#8da0d3",
"titleBar.activeForeground": "#15202b",
"titleBar.inactiveBackground": "#8da0d399",
"titleBar.inactiveForeground": "#15202b99"
}
}
2 changes: 1 addition & 1 deletion apps/nextjs/next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
2 changes: 1 addition & 1 deletion apps/nextjs/src/app/api/trpc/main/[trpc]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const handler = (req: NextRequest, res: NextResponse) =>
},
onError: (e) => {
if (process.env.NODE_ENV === "development") {
log.error(e);
// log.error(e);
}
Sentry.captureException(e.error);
},
Expand Down
308 changes: 308 additions & 0 deletions apps/nextjs/src/app/image-spike/[slug]/images.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
"use client";

import { useCallback, useState } from "react";

import type { Resource } from "ai-apps/image-alt-generation/types";
import Link from "next/link";

import LoadingWheel from "@/components/LoadingWheel";
import { trpc } from "@/utils/trpc";

export interface PageData {
id: string;
path: string;
title: string;
lessonPlan: {
cycle1: { explanation: { imagePrompt: string } };
cycle2: { explanation: { imagePrompt: string } };
cycle3: { explanation: { imagePrompt: string } };
};
}

interface ImageResponse {
id: string;
url: string;
title?: string;
alt?: string;
license: string;
photographer?: string;
}

type ImagesFromCloudinary = {
total_count: number;
time: number;
next_cursor: string;
resources: Resource[];
rate_limit_allowed: number;
rate_limit_reset_at: string;
rate_limit_remaining: number;
};

interface ComparisonColumn {
id: string;
selectedImageSource: string | null;
imageSearchBatch: ImageResponse[] | null;
isLoading: boolean;
error: string | null;
}

const ImagesPage = ({ pageData }: { pageData: PageData }) => {
const [selectedImagePrompt, setSelectedImagePrompt] = useState<string>("");
const [comparisonColumns, setComparisonColumns] = useState<
ComparisonColumn[]
>([
{
id: "column-1",
selectedImageSource: null,
imageSearchBatch: null,
isLoading: false,
error: null,
},
]);

const slideTexts = {
cycle1: pageData.lessonPlan.cycle1.explanation.imagePrompt,
cycle2: pageData.lessonPlan.cycle2.explanation.imagePrompt,
cycle3: pageData.lessonPlan.cycle3.explanation.imagePrompt,
};

const trpcMutations = {
"Stable Diffusion Ultra": trpc.imageGen.stableDifUltra.useMutation(),
"Stable Diffusion 3.0 & 3.5": trpc.imageGen.stableDif3.useMutation(),
"Stable Diffusion Core": trpc.imageGen.stableDifCore.useMutation(),
"DAL-E": trpc.imageGen.openAi.useMutation(),
Flickr: trpc.imageSearch.getImagesFromFlickr.useMutation(),
Unsplash: trpc.imageSearch.getImagesFromUnsplash.useMutation(),
Cloudinary:
trpc.cloudinaryRouter.getCloudinaryImagesBySearchExpression.useMutation(),
Google: trpc.imageSearch.getImagesFromGoogle.useMutation(),
"Simple Google": trpc.imageSearch.simpleGoogleSearch.useMutation(),
};

const fetchImages = useCallback(
async (columnId: string, source: string, prompt: string) => {
if (!trpcMutations[source]) {
updateColumn(columnId, { error: "Invalid image source selected." });
return;
}

const mutation = trpcMutations[source];
updateColumn(columnId, {
isLoading: true,
error: null,
imageSearchBatch: null,
});

try {
const response = await mutation.mutateAsync({
searchExpression: prompt,
});

if (
source === "Stable Diffusion Ultra" ||
source === "Stable Diffusion 3.0 & 3.5" ||
source === "Stable Diffusion Core"
) {
updateColumn(columnId, {
imageSearchBatch: [
{
id: "1",
url: response as string,
license: "Stable Diffusion",
},
],
});
} else if (source === "DAL-E") {
updateColumn(columnId, {
imageSearchBatch: [
{
id: "1",
url: response as string,
license: "OpenAI",
},
],
});
} else if (source === "Cloudinary") {
const cloudinaryData = (
response as ImagesFromCloudinary
).resources.map((image) => ({
id: image.public_id,
url: image.url,
license: "Cloudinary",
}));
updateColumn(columnId, { imageSearchBatch: cloudinaryData });
} else if (Array.isArray(response) && response.length === 0) {
updateColumn(columnId, {
error: "No images found for the selected prompt.",
});
} else {
updateColumn(columnId, {
imageSearchBatch: response as ImageResponse[],
});
}
} catch (err: any) {
updateColumn(columnId, {
error: err?.message || "An error occurred while fetching images.",
});
} finally {
updateColumn(columnId, { isLoading: false });
}
},
[trpcMutations],
);

const updateColumn = (
columnId: string,
updates: Partial<ComparisonColumn>,
) => {
setComparisonColumns((prev) =>
prev.map((col) => (col.id === columnId ? { ...col, ...updates } : col)),
);
};

const addComparisonColumn = () => {
if (comparisonColumns.length < 3) {
setComparisonColumns((prev) => [
...prev,
{
id: `column-${prev.length + 1}`,
selectedImageSource: null,
imageSearchBatch: null,
isLoading: false,
error: null,
},
]);
}
};

const removeComparisonColumn = (columnId: string) => {
setComparisonColumns((prev) => prev.filter((col) => col.id !== columnId));
};

return (
<div className="mx-auto mt-20 max-w-[1200px]">
<Link href="/image-spike" className="opacity-75">{`<-back`}</Link>
<h1 className="my-20 text-center text-2xl font-bold">{pageData.title}</h1>

<div className="my-20 flex gap-10">
{Object.entries(slideTexts).map(
([cycle, prompt]) =>
prompt && (
<button
key={cycle}
className={`w-1/3 rounded-lg border-2 border-black p-11 ${
selectedImagePrompt === prompt ? "bg-black text-white" : ""
}`}
onClick={() => {
setSelectedImagePrompt(prompt);
setComparisonColumns((prev) =>
prev.map((col) => ({
...col,
imageSearchBatch: null,
selectedImageSource: null,
})),
);
}}
>
<h2 className="text-sm opacity-70">{`${cycle.replace(
"cycle",
"Cycle ",
)} image prompt`}</h2>
<p>{prompt}</p>
</button>
),
)}
</div>
<div className="mt-16 flex flex-col gap-6 border-b border-t border-black border-opacity-25 py-16 text-center">
<p className="">
{selectedImagePrompt
? "Prompt: " + selectedImagePrompt
: "Select an image prompt to view it here"}
</p>
</div>
<div className="mt-16">
{selectedImagePrompt && comparisonColumns.length < 3 && (
<button
onClick={addComparisonColumn}
className="mx-auto mt-4 flex items-center gap-2 rounded-lg border border-gray-400 px-4 py-2 hover:bg-gray-100"
>
Add comparison
</button>
)}
</div>

{selectedImagePrompt && (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{comparisonColumns.map((column, index) => (
<div key={column.id} className="relative">
{index > 0 && (
<button
onClick={() => removeComparisonColumn(column.id)}
className="absolute -right-2 -top-2 rounded-full p-1 hover:bg-gray-300"
>
<p>X</p>
</button>
)}
<div className="my-10">
<label
htmlFor={`imageSource-${column.id}`}
className="block text-center"
>
Select an image source:
</label>
<select
id={`imageSource-${column.id}`}
className="mx-auto mt-4 block w-full rounded border border-gray-400 p-2"
value={column.selectedImageSource || ""}
onChange={(e) => {
const source = e.target.value;
updateColumn(column.id, { selectedImageSource: source });
if (source) {
fetchImages(column.id, source, selectedImagePrompt);
}
}}
>
<option value="" disabled>
Choose a source
</option>
{Object.keys(trpcMutations).map((source) => (
<option key={source} value={source}>
{source}
</option>
))}
</select>
</div>
{column.isLoading && <LoadingWheel />}
{column.error && (
<p className="text-center text-red-600">{column.error}</p>
)}
{column.imageSearchBatch && (
<div className="my-20 grid grid-cols-1 gap-10">
{column.imageSearchBatch?.map((image, i) => {
return (
<div
key={image.id}
className="flex flex-col items-center gap-4"
>
<img
src={image.url}
alt={image.alt || "Image"}
className="w-full"
/>
<p>License: {image.license}</p>
{image.photographer && <p>By: {image.photographer}</p>}
{image.title && <p>Title: {image.title}</p>}
</div>
);
})}
</div>
)}
</div>
))}
</div>
)}
</div>
);
};

export default ImagesPage;
19 changes: 19 additions & 0 deletions apps/nextjs/src/app/image-spike/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { notFound } from "next/navigation";

import { getChatById } from "@/app/actions";

import ImagesPage from "./images";

export default async function ImageTestPage({
params,
}: {
params: { slug: string };
}) {
const pageData = await getChatById(params.slug);

if (!pageData) {
notFound();
}

return <ImagesPage pageData={pageData} />;
}
Loading
Loading