Skip to content
Closed
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
267 changes: 216 additions & 51 deletions components/StandaloneShell.js

Large diffs are not rendered by default.

48 changes: 44 additions & 4 deletions packages/studio/src/components/AiInfluencerStudio.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { useState, useCallback } from "react";
import toast, { Toaster } from "react-hot-toast";
import { generateImage } from "../muapi.js";
import { formatErrorMessage } from "../utils/formatError.js";
import MobileGenerationActions, {
GenerationCopyButtons,
} from "./MobileGenerationActions.jsx";

const CDN = "https://cdn.muapi.ai/influencer";

Expand Down Expand Up @@ -332,7 +335,15 @@ function HoverPill({ label, img, onClick }) {
}

// ─── Main Component ─────────────────────────────────────────────────────────
export default function AiInfluencerStudio({ apiKey, onGenerate, isGenerating: externalIsGenerating }) {
export default function AiInfluencerStudio({
apiKey,
onGenerate,
onGenerationStart,
onGenerationEnd,
onGenerationComplete,
onGenerationError,
isGenerating: externalIsGenerating,
}) {
const [activeTab, setActiveTab] = useState("face");

const [selectedOptions, setSelectedOptions] = useState(() => {
Expand Down Expand Up @@ -389,6 +400,7 @@ export default function AiInfluencerStudio({ apiKey, onGenerate, isGenerating: e
// ── Generate ──────────────────────────────────────────────────────────────
const handleGenerate = async () => {
if (isGenerating) return;
onGenerationStart?.();
setIsGeneratingInternal(true);
setErrorMsg("");

Expand All @@ -406,13 +418,22 @@ export default function AiInfluencerStudio({ apiKey, onGenerate, isGenerating: e
}
if (res?.url) {
setCurrentResult(res.url);
setHistory((prev) => [{ url: res.url, ts: Date.now() }, ...prev]);
setHistory((prev) => [{ url: res.url, prompt, ts: Date.now() }, ...prev]);
setSelectedHistoryIdx(0);
onGenerationComplete?.({
url: res.url,
model: INFLUENCER_MODEL,
prompt,
type: "image",
});
}
} catch (err) {
toast.error(formatErrorMessage(err, "Generation failed. Please try again."));
const message = formatErrorMessage(err, "Generation failed. Please try again.");
if (onGenerationError) onGenerationError(message);
else toast.error(message);
} finally {
setIsGeneratingInternal(false);
onGenerationEnd?.();
}
};

Expand Down Expand Up @@ -722,7 +743,14 @@ export default function AiInfluencerStudio({ apiKey, onGenerate, isGenerating: e
>
<img src={item.url} alt={`Character ${idx + 1}`} className="w-full h-full object-cover" />
{/* Download on hover */}
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-end justify-center pb-2">
<div className="absolute inset-0 hidden md:flex bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity items-end justify-center pb-2">
<div className="absolute right-2 top-2 flex flex-col gap-2">
<GenerationCopyButtons
prompt={item.prompt}
imageUrl={item.url}
onCopyError={onGenerationError}
/>
</div>
<div
role="button"
tabIndex={0}
Expand All @@ -733,6 +761,18 @@ export default function AiInfluencerStudio({ apiKey, onGenerate, isGenerating: e
<DownloadIcon />
</div>
</div>
<MobileGenerationActions
prompt={item.prompt}
imageUrl={item.url}
onCopyError={onGenerationError}
actions={[
{
kind: "download",
label: "Download",
onSelect: () => downloadImg(item.url),
},
]}
/>
{/* Index badge */}
<div className="absolute top-1 left-1 px-1.5 py-0.5 rounded-md bg-black/60 backdrop-blur-sm text-[8px] text-gray-300 font-bold">
#{history.length - idx}
Expand Down
8 changes: 6 additions & 2 deletions packages/studio/src/components/AudioStudio.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ function PremiumAudioPlayer({ url, title }) {
// ---------------------------------------------------------------------------
export default function AudioStudio({
apiKey,
onGenerationStart,
onGenerationEnd,
onGenerationComplete,
onGenerationError,
historyItems,
Expand Down Expand Up @@ -639,6 +641,7 @@ export default function AudioStudio({
}
}

onGenerationStart?.();
setIsGenerating(true);
setGenerateError(null);

Expand Down Expand Up @@ -683,10 +686,11 @@ export default function AudioStudio({
} catch (e) {
console.error("[AudioStudio]", e);
const errMsg = formatErrorMessage(e, "Audio generation failed");
toast.error(errMsg);
onGenerationError?.(errMsg);
if (onGenerationError) onGenerationError(errMsg);
else toast.error(errMsg);
} finally {
setIsGenerating(false);
onGenerationEnd?.();
}
};

Expand Down
Loading