Skip to content

Commit

Permalink
Merge pull request #4 from KennethWussmann/task/fix-previews
Browse files Browse the repository at this point in the history
  • Loading branch information
KennethWussmann authored Nov 3, 2024
2 parents 91192d6 + 4b2589d commit f97c7b5
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 100 deletions.
4 changes: 1 addition & 3 deletions src/components/caption/components/caption-list-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ import { useAtom } from "jotai/react";
import { settings } from "@/lib/settings";
import { CaptionSuggestionsAI } from "./caption-suggestions-ai";
import { useOllamaStatus } from "@/hooks/use-ollama-status";
import { useCaptionEditor } from "@/hooks/provider/caption-editor-provider";

export const CaptionListFooter = () => {
const [isOllamaEnabled] = useAtom(settings.ai.ollamaEnabled);
const [strategy] = useAtom(settings.caption.strategy);
const { isOnline } = useOllamaStatus();
const { isDirty } = useCaptionEditor();
return (
<div>
{isOllamaEnabled && isOnline && <CaptionSuggestionsAI />}
{isOllamaEnabled && isOnline && strategy === "ai" && isDirty ? (
{isOllamaEnabled && isOnline && strategy === "ai" ? (
<CaptionPreviewAI />
) : (
<CaptionPreview />
Expand Down
27 changes: 7 additions & 20 deletions src/components/caption/components/caption-preview-ai.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,28 @@
import { Card } from "@/components/ui";
import { useCaptionRefiner } from "@/hooks/ai/use-caption-refiner";
import { useCaptionEditor } from "@/hooks/provider/caption-editor-provider";
import { LoaderCircle, RefreshCw } from "lucide-react";

export const CaptionPreviewAI = () => {
const { parts, isDirty } = useCaptionEditor();
const { captionSuggestion, isLoading, refetch } = useCaptionRefiner({
skip: !isDirty,
});
const { parts, preview, isLoadingPreview, refetchPreview } = useCaptionEditor();
const isEmpty = parts.length === 0;

// TODO: Dont write caption to file, but to the database and then export from there to file
// useEffect(() => {
// if (!imageFile || !captionSuggestion || captionSuggestion.length < 3) {
// return;
// }
// writeCaption(captionSuggestion, imageFile);
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [captionSuggestion, writeCaption]);

return (
<div className="my-2 py-2">
<div className="flex flex-row gap-2 mb-2 font-semibold items-center">
Preview {isLoading && <LoaderCircle className="h-4 w-4 animate-spin" />}
{!isLoading && (
<RefreshCw className="h-4 w-4" onClick={() => refetch()} />
Preview {isLoadingPreview && <LoaderCircle className="h-4 w-4 animate-spin" />}
{!isLoadingPreview && (
<RefreshCw className="h-4 w-4" onClick={() => refetchPreview()} />
)}
</div>

{(isEmpty || !captionSuggestion || captionSuggestion.length === 0) && (
{(isEmpty || !preview || preview.length === 0) && (
<Card className="p-1 px-2 italic text-muted-foreground text-center">
No caption
</Card>
)}

{!isEmpty && captionSuggestion && (
<Card className="p-1 px-2">{captionSuggestion}</Card>
{!isEmpty && preview && (
<Card className="p-1 px-2 max-h-[200px] overflow-y-auto">{preview}</Card>
)}
</div>
);
Expand Down
30 changes: 3 additions & 27 deletions src/components/caption/components/caption-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,22 @@
import { Card } from "@/components/ui";
import { useCaptionEditor } from "@/hooks/provider/caption-editor-provider";
import { settings } from "@/lib/settings";
import { useAtom } from "jotai/react";

export const CaptionPreview = () => {
const { parts } = useCaptionEditor();
const [separator] = useAtom(settings.caption.separator);
const [endWithSeparator] = useAtom(settings.caption.endWithSeparator);

let finalText = parts
.map((part) => part.text.trim())
.join(separator)
.trim();

if (endWithSeparator && !finalText.endsWith(separator.trim())) {
finalText += separator;
}

const isEmpty = parts.length === 0;

// TODO: Dont write caption to file, but to the database and then export from there to file
// useEffect(() => {
// if (!imageFile || finalText.length < 3) {
// return;
// }
// writeCaption(finalText, imageFile);
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [finalText, writeCaption]);
const { preview } = useCaptionEditor();

return (
<div className="my-2 py-2">
<div className="flex flex-row gap-2 mb-2 font-semibold items-center">
Preview
</div>

{isEmpty && (
{!preview && (
<Card className="p-1 px-2 italic text-muted-foreground text-center">
No caption
</Card>
)}

{!isEmpty && <Card className="p-1 px-2">{finalText}</Card>}
{preview && <Card className="p-1 px-2">{preview}</Card>}
</div>
);
};
32 changes: 17 additions & 15 deletions src/components/caption/components/caption-suggestions-ai.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,23 @@ export const CaptionSuggestionsAI = () => {
</Card>
)}

{suggestions.length > 0 && (
<AnimatedGroup preset="blur-slide" className="flex flex-col gap-2">
{suggestions.map((text) => (
<Card
key={text}
className="p-2 px-4 hover:bg-muted cursor-pointer"
onClick={() => {
applySuggestion(text);
}}
>
{text}
</Card>
))}
</AnimatedGroup>
)}
<div className="max-h-[200px] overflow-y-auto">
{suggestions.length > 0 && (
<AnimatedGroup preset="blur-slide" className="flex flex-col gap-2">
{suggestions.map((text) => (
<Card
key={text}
className="p-2 px-4 hover:bg-muted cursor-pointer"
onClick={() => {
applySuggestion(text);
}}
>
{text}
</Card>
))}
</AnimatedGroup>
)}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useDatabase } from "@/lib/database/database-provider";
import { useDatasetDirectory } from "@/hooks/provider/dataset-directory-provider";
import { useState } from "react";
import { useToast } from "@/hooks/use-toast";
import { deleteAllIndexedDBs } from "@/lib/utils";

const AdvancedSettingsContent = () => {
const { deleteAllTextFiles } = useDatasetDirectory();
Expand All @@ -24,9 +25,9 @@ const AdvancedSettingsContent = () => {
})
}

const reloadWithWarning = async () => {
const reloadWithWarning = async (title: string) => {
toast({
title: "Database deleted",
title,
description: "The page will reload now!",
})
await new Promise(resolve => setTimeout(resolve, 2000));
Expand All @@ -35,12 +36,13 @@ const AdvancedSettingsContent = () => {

const deleteDatabase = async () => {
await deleteDatabaseBackup();
await reloadWithWarning();
await deleteAllIndexedDBs();
await reloadWithWarning("Database deleted");
}

const resetSettings = async () => {
await resetLocalStorage();
await reloadWithWarning();
await reloadWithWarning("Settings reset");
};

return (
Expand Down
29 changes: 10 additions & 19 deletions src/hooks/ai/use-caption-refiner.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,64 @@
import { settings } from "@/lib/settings";
import { useAtom } from "jotai/react";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import { useOllamaStatus } from "../use-ollama-status";
import { useQuery } from "@tanstack/react-query";
import { chat } from "@/lib/ollama-api-client";
import { useCaptionEditor } from "../provider/caption-editor-provider";
import { useImageNavigation } from "../provider/image-navigation-provider";

export const useCaptionRefiner = ({
initialValue,
simplePreview,
skip,
}: {
initialValue?: string | null;
simplePreview?: string;
skip?: boolean;
} = {}) => {
const [separator] = useAtom(settings.caption.separator);
const [captionModel] = useAtom(settings.ai.caption.model);
const [userPromptTemplate] = useAtom(settings.ai.caption.userPrompt);
const { isOnline } = useOllamaStatus();
const { parts } = useCaptionEditor();
const { currentImage } = useImageNavigation()
const text = useMemo(() => {
return parts.map((part) => part.text.trim()).join(separator);
}, [parts, separator]);
const { currentImage } = useImageNavigation();
const [captionSuggestion, setCaptionSuggestion] = useState<string | null>(
initialValue ?? null
);

const isEmpty = parts.length === 0;

const {
data: aiResponse,
isLoading,
isRefetching,
refetch,
} = useQuery({
enabled: isOnline && !isEmpty && !skip,
queryKey: ["caption", text],
enabled: isOnline && !skip,
queryKey: ["caption", simplePreview],
queryFn: () =>
chat({
model: captionModel,
messages: [
{
role: "user",
content: userPromptTemplate.replace("%text%", text),
content: userPromptTemplate.replace("%text%", simplePreview ?? ""),
},
],
}),
staleTime: Infinity,
});

useEffect(() => {
if (!aiResponse || aiResponse.length < 3 || parts.length === 0) {
if (!aiResponse || aiResponse.length < 3) {
return;
}
setCaptionSuggestion(aiResponse);
}, [aiResponse, parts.length]);
}, [aiResponse]);

useEffect(() => {
setCaptionSuggestion(null);
}, [currentImage]);
}, [currentImage?.id]);

return {
captionSuggestion,
isLoading: isLoading || isRefetching,
refetch: () => {
if (parts.length === 0) {
return;
}
refetch();
},
};
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/ai/use-caption-suggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const useCaptionSuggestions = () => {

useEffect(() => {
setSuggestions([]);
}, [currentImage]);
}, [currentImage?.id]);

useEffect(() => {
setDebounced(true);
Expand Down
19 changes: 10 additions & 9 deletions src/hooks/provider/caption-editor-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@ import React, {
import { CaptionPart } from "@/lib/types";
import { arrayMove } from "@dnd-kit/sortable";
import { useImageNavigation } from "./image-navigation-provider";
import { useAtom } from "jotai/react";
import { settings } from "@/lib/settings";
import { usePreventClose } from "./prevent-close-provider";
import { uuid } from "@/lib/utils";
import { usePrevious } from "@uidotdev/usehooks"
import { useShortcut } from "../use-shortcut";
import { database } from "@/lib/database/database";
import { useCaptionPreview } from "../use-caption-preview";

interface CaptionEditorContextType {
parts: CaptionPart[];
preview: string | null;
setPreview: (preview: string | null) => void;
addPart: (part: string) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handleDragEnd: (event: any) => void;
Expand All @@ -31,6 +29,8 @@ interface CaptionEditorContextType {
save: () => void;
isEditing: CaptionPart | null;
isDirty: boolean;
refetchPreview: () => void;
isLoadingPreview: boolean;
}

const CaptionEditorContext = createContext<CaptionEditorContextType | undefined>(
Expand All @@ -44,13 +44,15 @@ interface CaptionEditorProviderProps {
export const CaptionEditorProvider: React.FC<CaptionEditorProviderProps> = ({
children,
}) => {
const [separator] = useAtom(settings.caption.separator);
const { currentImage } = useImageNavigation();
const [isDirty, setDirty] = useState(false);
const [preview, setPreview] = useState<string | null>(null);
const [parts, setParts] = useState<CaptionPart[]>([]);
const [editingPart, setEditingPart] = useState<CaptionPart | null>(null);
const previousImage = usePrevious(currentImage);
const { preview, ai: { isLoading, refetch } } = useCaptionPreview({
parts,
initialValue: currentImage?.caption,
});

usePreventClose(isDirty);
useShortcut("save", () => {
Expand Down Expand Up @@ -134,7 +136,6 @@ export const CaptionEditorProvider: React.FC<CaptionEditorProviderProps> = ({

const resetEditor = () => {
setParts([]);
setPreview(null);
setEditingPart(null);
setDirty(false);
};
Expand All @@ -151,7 +152,7 @@ export const CaptionEditorProvider: React.FC<CaptionEditorProviderProps> = ({
await database.images.put({
id: filename ?? currentImage.filename,
filename: filename ?? currentImage.filename,
caption: parts.map((part) => part.text).join(separator),
caption: preview ?? undefined,
captionParts: parts,
})
setDirty(false);
Expand All @@ -162,7 +163,6 @@ export const CaptionEditorProvider: React.FC<CaptionEditorProviderProps> = ({
return
}
setParts(currentImage.captionParts?.map(part => part as CaptionPart) ?? [])
setPreview(currentImage.caption ?? null)
}

useEffect(() => {
Expand All @@ -177,7 +177,6 @@ export const CaptionEditorProvider: React.FC<CaptionEditorProviderProps> = ({
const value: CaptionEditorContextType = {
parts,
preview,
setPreview,
addPart,
handleDragEnd,
enterEditMode,
Expand All @@ -188,6 +187,8 @@ export const CaptionEditorProvider: React.FC<CaptionEditorProviderProps> = ({
isDirty,
save,
clearParts,
refetchPreview: refetch,
isLoadingPreview: isLoading,
};

return (
Expand Down
3 changes: 1 addition & 2 deletions src/hooks/provider/use-caption-clipboard-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const CaptionClipboardContext = createContext<CaptionClipboardContextType | unde

export const CaptionClipboardProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [caption, setCaption] = useState<Caption | null>(null);
const { parts, preview, addPart, setPreview } = useCaptionEditor();
const { parts, preview, addPart } = useCaptionEditor();
useShortcut("copyCaptionParts", () => copy());
useShortcut("pasteCaptionParts", () => paste());

Expand All @@ -36,7 +36,6 @@ export const CaptionClipboardProvider: React.FC<{ children: ReactNode }> = ({ ch
return;
}
caption.parts.forEach(part => addPart(part.text));
setPreview(caption.preview ?? null);
}

return (
Expand Down
Loading

0 comments on commit f97c7b5

Please sign in to comment.