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
3 changes: 2 additions & 1 deletion packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ export * from "./throttler.js";
export * from "./timestamp.js";
export * from "./fetch-with-creds.js";
export * from "./iterator-from-stream.js";
export * from "./data.js";
export * from "./data.js";
export * from "./veo.js";
31 changes: 31 additions & 0 deletions packages/utils/src/veo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export const veoDailyLimitStorageKey = "veoDailyLimitReached";

export const isVeoDailyLimitReached = () => {
const itemStr = localStorage.getItem(veoDailyLimitStorageKey);
if (!itemStr) {
return false;
}
const item = JSON.parse(itemStr);
const now = new Date();
if (now.getTime() > item.expiresAt) {
// Item has expired, remove from storage
localStorage.removeItem(veoDailyLimitStorageKey);
return false;
}

return true;
};

export const setVeoDailyLimitExpirationKey = () => {
localStorage.setItem(
veoDailyLimitStorageKey,
JSON.stringify({
value: "yes",
expiresAt: new Date().getTime() + 24 * 60 * 60 * 1000,
})
);
};

export const removeVeoDailyLimitExpirationKey = () => {
localStorage.removeItem(veoDailyLimitStorageKey);
};
31 changes: 31 additions & 0 deletions packages/visual-editor/src/a2/a2/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ export type ErrorWithMetadata = { $error: string; metadata?: ErrorMetadata };

export type NonPromise<T> = T extends Promise<unknown> ? never : T;

export type SnackbarAction = {
title: string;
action: string;
value?: string;
callback?: () => Promise<void> | void;
cssClass?: string;
};

function ok<T>(o: Outcome<NonPromise<T>>): o is NonPromise<T> {
return !(o && typeof o === "object" && "$error" in o);
}
Expand Down Expand Up @@ -472,6 +480,29 @@ function tr(strings: TemplateStringsArray, ...values: unknown[]): string {
.trim();
}

export function dispatchShowCustomSnackbarEvent(
message: string,
actions: SnackbarAction[] = [],
snackType: string = "info"
) {
if (typeof window !== "undefined") {
const snackbarEvent = new CustomEvent("showCustomSnackbarEvent", {
bubbles: true,
cancelable: true,
composed: true,
detail: {
snackbarId: globalThis.crypto.randomUUID(),
message,
snackType,
actions,
persistent: true,
replaceAll: false,
},
});
window.dispatchEvent(snackbarEvent);
}
}

const DOC_MIME_TYPE = "application/vnd.google-apps.document";
const SHEETS_MIME_TYPE = "application/vnd.google-apps.spreadsheet";
const SLIDES_MIME_TYPE = "application/vnd.google-apps.presentation";
Expand Down
64 changes: 64 additions & 0 deletions packages/visual-editor/src/a2/video-generator/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Template } from "../a2/template.js";
import { ToolManager } from "../a2/tool-manager.js";
import {
defaultLLMContent,
dispatchShowCustomSnackbarEvent,
encodeBase64,
err,
ErrorReason,
Expand All @@ -18,6 +19,7 @@ import {
isStoredData,
joinContent,
ok,
SnackbarAction,
toLLMContent,
toText,
toTextConcat,
Expand All @@ -35,6 +37,10 @@ import {
Outcome,
Schema,
} from "@breadboard-ai/types";
import {
removeVeoDailyLimitExpirationKey,
setVeoDailyLimitExpirationKey,
} from "@breadboard-ai/utils";
import { A2ModuleArgs } from "../runnable-module-factory.js";
import { driveFileToBlob, toGcsAwareChunk } from "../a2/data-transforms.js";

Expand Down Expand Up @@ -249,6 +255,8 @@ async function invoke(
});
}

initAiCreditsLimitation();

console.log(`PROMPT(${modelName}): ${combinedInstruction}`);

// 2) Call backend to generate video.
Expand Down Expand Up @@ -412,3 +420,59 @@ async function describe(
},
};
}

const showSnackWithActionButton = (message: string, action: SnackbarAction) => {
const showCustomSnackbarEventTimeout = setTimeout(() => {
clearTimeout(showCustomSnackbarEventTimeout);
dispatchShowCustomSnackbarEvent("", [action], "error");
});

throw new Error(message);
};

const initAiCreditsLimitation = () => {
// @TODO integrate the backend once it's ready for google user detection and if limit is reached
const limitReached = false;
const isGoogleUser = true;

if (!limitReached) {
removeVeoDailyLimitExpirationKey();
return;
}

if (isGoogleUser) {
// @TODO integrate the backend once it's ready for out of credits detection
const outOfCredits = false;
if (outOfCredits) {
showSnackWithActionButton(
"You need more AI credits to create more videos. Each video you generate uses 20 AI credits from your Google AI plan.",
{
title: "Get more AI credits",
action: "getMoreAiCredits",
callback: () => {
// window.open("url goes here");
},
cssClass: "long-button",
}
);
} else {
// Set expiration time of the localstorage key
setVeoDailyLimitExpirationKey();
dispatchShowCustomSnackbarEvent(
"You’ve reached the daily limit for creating videos. Each video you generate after that will use 20 AI credits from your Google AI plan."
);
}
} else {
showSnackWithActionButton(
"You’ve reached the daily limit for creating videos. If you want to create more videos, come back tomorrow, or upgrade to a Google AI plan",
{
title: "See Google AI plans",
action: "seeGoogleAiPlans",
cssClass: "long-button",
callback: () => {
// window.open("url goes here");
},
}
);
}
};
16 changes: 16 additions & 0 deletions packages/visual-editor/src/main-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ abstract class MainBase extends SignalWatcher(LitElement) {
this.embedHandler = args.embedHandler;

this.#addRuntimeEventHandlers();
this.#addCustomEventHandlers();

this.boardServer = this.runtime.googleDriveBoardServer;

Expand Down Expand Up @@ -443,6 +444,21 @@ abstract class MainBase extends SignalWatcher(LitElement) {
}
}

#addCustomEventHandlers() {
window.addEventListener("showCustomSnackbarEvent", ((evt: CustomEvent) => {
if (evt.detail) {
this.snackbar(
evt.detail.message,
evt.detail.snackType,
evt.detail.actions || [],
evt.detail.persistent || false,
evt.detail.snackbarId,
evt.detail.replaceAll || false
);
}
}) as EventListener);
}

#addRuntimeEventHandlers() {
if (!this.runtime) {
console.error("No runtime found");
Expand Down
8 changes: 7 additions & 1 deletion packages/visual-editor/src/ui/elements/toast/snackbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class Snackbar extends LitElement {
#messages {
color: var(--text-color);
flex: 1 1 auto;
margin-right: var(--bb-grid-size-11);
margin-right: var(--bb-grid-size-3);
a,
a:visited {
color: var(--light-dark-p-40);
Expand Down Expand Up @@ -108,6 +108,11 @@ export class Snackbar extends LitElement {
opacity: 0.7;
transition: opacity 0.2s cubic-bezier(0, 0, 0.3, 1);

&.long-button {
min-width: 160px;
margin: 0;
}

&:not([disabled]) {
cursor: pointer;

Expand Down Expand Up @@ -257,6 +262,7 @@ export class Snackbar extends LitElement {
(action) => action.value,
(action) => {
return html`<button
class="${action?.cssClass}"
@click=${() => {
this.hide();
this.dispatchEvent(
Expand Down
1 change: 1 addition & 0 deletions packages/visual-editor/src/ui/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ export type SnackbarAction = {
action: string;
value?: HTMLTemplateResult | string;
callback?: () => Promise<void> | void;
cssClass?: string;
};

export type SnackbarMessage = {
Expand Down