Skip to content
Open
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
22 changes: 12 additions & 10 deletions prompts/pkg/chat.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DocTypes } from "@fireproof/core-types-base";
import type { DocTypes, DocFileMeta } from "@fireproof/core-types-base";
import type { RuntimeError } from "use-vibes";
import { ViewType } from "./view-state.js";

Expand Down Expand Up @@ -48,6 +48,14 @@ export interface VibeDocument {
* When undefined, use LLM decision.
*/
demoDataOverride?: boolean;
/**
* Screenshot and source code file attachments for catalog storage.
* Stored in the catalog database for persistent vibe screenshots and source.
*/
_files?: {
screenshot: File;
source: File;
};
}

// ===== Content Segment Types =====
Expand Down Expand Up @@ -86,21 +94,15 @@ export type ChatMessageDocument =
| AiChatMessageDocument
| SystemChatMessageDocument;

/**
* Base document interface with common properties
*/
export interface DocBase {
_id: string;
}

/**
* Document type for screenshot entries
*/
export interface ScreenshotDocument extends DocBase {
export interface ScreenshotDocument extends DocTypes {
type: "screenshot";
session_id: string;
cid?: string; // Content identifier for deduplication
_files?: {
screenshot: { file: () => Promise<File>; type: string };
screenshot: DocFileMeta;
};
}

Expand Down
98 changes: 98 additions & 0 deletions vibes.diy/pkg/app/components/VibeCardCatalog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { VibeCard } from "./VibeCard.js";
import type { LocalVibe } from "../utils/vibeUtils.js";
import { useVibes } from "../hooks/useVibes.js";
import { DocFileMeta } from "use-fireproof";

interface VibeCardCatalogProps {
catalogVibe: LocalVibe;
}

export function VibeCardCatalog({ catalogVibe }: VibeCardCatalogProps) {
const [confirmDelete, setConfirmDelete] = useState<string | null>(null);
const navigate = useNavigate();
const { toggleFavorite, deleteVibe } = useVibes();

// Debug logging for screenshot data
// console.log(`🐛 VibeCardCatalog:`, catalogVibe);

// Navigation functions
const handleEditClick = (id: string, encodedTitle: string) => {
navigate(`/chat/${id}/${encodedTitle}/app`);
};

const handleRemixClick = (
slug: string,
event: React.MouseEvent<HTMLButtonElement>,
) => {
event.stopPropagation();
navigate(`/remix/${slug}`);
};

// Handle toggling the favorite status
const handleToggleFavorite = async (vibeId: string, e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();

try {
await toggleFavorite(vibeId);
} catch (error) {
// Error handling is managed by the useVibes hook
}
};

// Handle deleting a vibe
const handleDeleteClick = async (vibeId: string, e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();

if (confirmDelete === vibeId) {
try {
// Immediately set confirmDelete to null to prevent accidental clicks
setConfirmDelete(null);
// Delete the vibe
await deleteVibe(vibeId);
} catch (error) {
// Error handling is managed by the useVibes hook
}
} else {
setConfirmDelete(vibeId);

// Prevent the global click handler from immediately clearing the confirmation
// by stopping the event from bubbling up
e.nativeEvent.stopImmediatePropagation();
}
};

// Clear confirmation when clicking elsewhere
useEffect(() => {
const handlePageClick = (e: MouseEvent) => {
// Don't clear if the click originated from a delete button
if (
confirmDelete &&
!(e.target as Element).closest('button[data-action="delete"]')
) {
setConfirmDelete(null);
}
};

// Use capture phase to handle document clicks before other handlers
document.addEventListener("click", handlePageClick, true);
return () => {
document.removeEventListener("click", handlePageClick, true);
};
}, [confirmDelete]);

return (
<VibeCard
vibe={catalogVibe}
screenshot={catalogVibe.screenshot as DocFileMeta | undefined}
confirmDelete={confirmDelete}
onEditClick={handleEditClick}
onToggleFavorite={handleToggleFavorite}
onDeleteClick={handleDeleteClick}
onRemixClick={handleRemixClick}
/>
);
}
2 changes: 2 additions & 0 deletions vibes.diy/pkg/app/components/VibeCardData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export function VibeCardData({ vibeId }: VibeCardDataProps) {
const loadData = async () => {
try {
setIsLoading(true);
console.log(`Loading vibe document for ${vibeId}`);
const vibeData = await loadVibeDocument(vibeId);
if (isMounted) {
setVibe(vibeData);
Expand Down Expand Up @@ -171,6 +172,7 @@ export function VibeCardData({ vibeId }: VibeCardDataProps) {

// If the vibe wasn't found (not loading and no data), return null to filter it out
if (!vibe) {
console.log(`Vibe not found: ${vibeId}`);
return null;
}

Expand Down
13 changes: 11 additions & 2 deletions vibes.diy/pkg/app/components/VibespaceComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import Wild from "./vibespace/Wild.js";
import ExplodingBrain from "./vibespace/ExplodingBrain.js";
import Cyberpunk from "./vibespace/Cyberpunk.js";
import type { ReactElement } from "react";
import { useFireproof } from "use-fireproof";
import { toCloud, useFireproof } from "use-fireproof";
import { useUserSettings } from "../hooks/useUserSettings.js";
import { useAuth } from "../contexts/AuthContext.js";

// Define the structure of our vibe documents
interface VibeDocument {
Expand Down Expand Up @@ -362,8 +364,15 @@ export default function VibespaceComponent({
return <div>Invalid user space</div>;
}

// Get sync setting
const { isAuthenticated } = useAuth();
const { isEnableSyncEnabled } = useUserSettings();

// Use Fireproof with the user-specific database
const { useAllDocs } = useFireproof(`vu-${userId}`);
const { useAllDocs } = useFireproof(
`vu-${userId}`,
isEnableSyncEnabled && isAuthenticated ? { attach: toCloud() } : {},
);

// Query all documents in the database
const allDocsResult = useAllDocs() as { docs: VibeDocument[] };
Expand Down
19 changes: 19 additions & 0 deletions vibes.diy/pkg/app/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,25 @@ class vibesDiyEnv {
(this.env().get("VITE_VIBES_CHAT_HISTORY") ?? "vibes-chats") +
getVersionSuffix(),
);

// Catalog Database
readonly CATALOG_DBNAME = Lazy(
() => this.env().get("VITE_CATALOG_DBNAME") ?? "v-catalog",
);
}

export const VibesDiyEnv = new vibesDiyEnv();

// Set up Fireproof debugging if in browser environment
if (typeof window !== "undefined") {
// Method 1: Using FP_ENV Symbol (direct approach)
// @ts-expect-error - Setting up Fireproof debug environment
window[Symbol.for("FP_ENV")] = window[Symbol.for("FP_ENV")] || new Map();
// @ts-expect-error - Enable full Fireproof debugging
window[Symbol.for("FP_ENV")].set("FP_DEBUG", "*");

// Method 2: Using logger (alternative approach from Fireproof README)
// Uncomment this if you prefer using the logger method:
// import { logger } from 'use-fireproof';
// logger.setDebug('*');
}
38 changes: 38 additions & 0 deletions vibes.diy/pkg/app/hooks/VibeCatalog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react";
import { useCatalog } from "./useCatalog.js";
import type { LocalVibe } from "../utils/vibeUtils.js";

/**
* Implementation component that contains all the hooks and logic.
* This is only rendered when userId and vibes are available.
*/
function VibeCatalogImpl({
userId,
vibes,
}: {
userId?: string;
vibes: LocalVibe[];
}) {
const { count } = useCatalog(userId, vibes);

return (
<span style={{ display: "inline-block", marginBottom: 8, fontWeight: 500 }}>
Cataloging {count} vibes locally
</span>
);
}

/**
* Public wrapper component that handles conditional rendering.
* Users can include this without checking for userId themselves.
*/
export function VibeCatalog({
userId,
vibes,
}: {
userId?: string;
vibes?: LocalVibe[];
}) {
if (!vibes) return null;
return <VibeCatalogImpl userId={userId} vibes={vibes} />;
}
Loading