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

feat: client proxy + nextjs support #9

Merged
merged 13 commits into from
Sep 11, 2023
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

/dist
/coverage
package-lock.json
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.formatting.provider": "none"
}
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# The fal-serverless JS Client

![NPM client](https://img.shields.io/npm/v/@fal-ai/serverless-client?color=%237527D7&label=client)
![Build](https://img.shields.io/github/actions/workflow/status/fal-ai/serverless-js/build.yml)
![License](https://img.shields.io/github/license/fal-ai/serverless-js)
![@fal-ai/serverless-client npm package](https://img.shields.io/npm/v/@fal-ai/serverless-client?color=%237527D7&label=client&style=flat-square)
![@fal-ai/serverless-nextjs npm package](https://img.shields.io/npm/v/@fal-ai/serverless-nextjs?color=%237527D7&label=nextjs-proxy&style=flat-square)
![Build](https://img.shields.io/github/actions/workflow/status/fal-ai/serverless-js/build.yml?style=flat-square)
![License](https://img.shields.io/github/license/fal-ai/serverless-js?style=flat-square)

## About the project

Expand Down
3 changes: 3 additions & 0 deletions apps/demo-app/pages/api/_fal/proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @snippet:start(client.proxy.nextjs)
export { config, handler as default } from '@fal-ai/serverless-nextjs';
// @snippet:end
21 changes: 0 additions & 21 deletions apps/demo-app/pages/api/generateImage.ts

This file was deleted.

72 changes: 0 additions & 72 deletions apps/demo-app/pages/diffusion.tsx

This file was deleted.

161 changes: 123 additions & 38 deletions apps/demo-app/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { getJoke } from '../services/getJoke';
import * as fal from '@fal-ai/serverless-client';
import { withNextProxy } from '@fal-ai/serverless-nextjs';
import { useMemo, useState } from 'react';

export async function getServerSideProps(context) {
try {
const result = await getJoke();
return {
props: {
...result,
},
};
} catch (error) {
return {
props: {
error: error.message,
},
};
}
}
// @snippet:start(client.config)
fal.config({
requestMiddleware: withNextProxy(),
});
// @snippet:end

// @snippet:start(client.result.type)
type Image = {
url: string;
file_name: string;
file_size: number;
};
type Result = {
images: Image[];
};
// @snippet:end

function Error(props) {
if (!props.error) {
Expand All @@ -26,43 +28,126 @@ function Error(props) {
className="p-4 mb-4 text-sm text-red-800 rounded bg-red-50 dark:bg-gray-800 dark:text-red-400"
role="alert"
>
<span className="font-medium">Error</span> {props.error}
<span className="font-medium">Error</span> {props.error.message}
</div>
);
}

export function Index(props) {
const handleClick = async (e) => {
const DEFAULT_PROMPT = "a city landscape of a cyberpunk metropolis, raining, purple, pink and teal neon lights, highly detailed, uhd";

export function Index() {
// @snippet:start(client.ui.state)
// Input state
const [prompt, setPrompt] = useState<string>(DEFAULT_PROMPT);
// Result state
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [result, setResult] = useState<Result | null>(null);
const [logs, setLogs] = useState<string[]>([]);
const [elapsedTime, setElapsedTime] = useState<number>(0);
// @snippet:end
const image = useMemo(() => {
if (!result) {
return null;
}
return result.images[0];
}, [result]);

const reset = () => {
setLoading(false);
setError(null);
setResult(null);
setLogs([]);
setElapsedTime(0);
};

const handleOnClick = async (e) => {
e.preventDefault();
reset();
// @snippet:start(client.queue.subscribe)
setLoading(true);
const start = Date.now();
try {
const joke = await getJoke();
console.log(joke);
} catch (e) {
console.log(e);
const result: Result = await fal.queue.subscribe('110602490-lora', {
input: {
prompt,
model_name: 'stabilityai/stable-diffusion-xl-base-1.0',
image_size: 'square_hd',
},
onQueueUpdate(status) {
setElapsedTime(Date.now() - start);
if (status.status === 'IN_PROGRESS') {
setLogs(status.logs.map((log) => log.message));
}
},
});
setResult(result);
} catch (error) {
setError(error);
} finally {
setLoading(false);
setElapsedTime(Date.now() - start);
}
// @snippet:end
};
return (
<div className="min-h-screen dark:bg-gray-900 dark:text-white bg-white text-black">
<main className="flex flex-col items-center justify-center w-full flex-1 px-20 py-10">
<div className="min-h-screen dark:bg-gray-800 dark:text-gray-50 bg-gray-100 text-gray-800">
<main className="flex flex-col items-center justify-center w-full flex-1 px-20 py-10 space-y-8">
<h1 className="text-4xl font-bold mb-8">
Hello <code>fal-serverless</code>
Hello <code className="font-light text-pink-600">fal</code>
</h1>
<p className="text-lg mb-10">
This page can access <strong>fal-serverless</strong> functions when
it&apos;s rendering.
</p>
<Error error={props.error} />
<div className="text-lg w-full">
<label htmlFor="prompt" className="block mb-2 text-current">
Prompt
</label>
<input
className="w-full text-lg p-2 rounded bg-black/10 dark:bg-white/5 border border-black/20 dark:border-white/10"
id="prompt"
name="prompt"
placeholder="Imagine..."
value={prompt}
autoComplete="off"
onChange={(e) => setPrompt(e.target.value)}
onBlur={(e) => setPrompt(e.target.value.trim())}
/>
</div>

<button
onClick={handleClick}
className="mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold text-xl py-4 px-8 mx-auto rounded focus:outline-none focus:shadow-outline"
onClick={handleOnClick}
className="bg-indigo-600 hover:bg-indigo-700 text-white font-bold text-lg py-3 px-6 mx-auto rounded focus:outline-none focus:shadow-outline"
disabled={loading}
>
Get Joke
{loading ? 'Generating...' : 'Generate Image'}
</button>

<p className="mt-10">
Here&apos;s a joke: <strong>{props.joke}</strong>
</p>
<Error error={error} />

<div className="w-full flex flex-col space-y-4">
<div className="mx-auto">
{image && (
// eslint-disable-next-line @next/next/no-img-element
<img src={image.url} alt="" />
)}
</div>
<div className="space-y-2">
<h3 className="text-xl font-light">JSON Result</h3>
<p className="text-sm text-current/80">
{`Elapsed Time (seconds): ${(elapsedTime / 1000).toFixed(2)}`}
</p>
<pre className="text-sm bg-black/80 text-white/80 font-mono h-60 rounded whitespace-pre overflow-auto w-full">
{result
? JSON.stringify(result, null, 2)
: '// result pending...'}
</pre>
</div>

<div className="space-y-2">
<h3 className="text-xl font-light">Logs</h3>
<pre className="text-sm bg-black/80 text-white/80 font-mono h-60 rounded whitespace-pre overflow-auto w-full">
{logs.filter(Boolean).join('\n')}
</pre>
</div>
</div>
</main>
</div>
);
Expand Down
27 changes: 0 additions & 27 deletions apps/demo-app/services/generateImage.ts

This file was deleted.

18 changes: 0 additions & 18 deletions apps/demo-app/services/getJoke.ts

This file was deleted.

1 change: 0 additions & 1 deletion apps/demo-app/specs/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { render } from '@testing-library/react';

import Index from '../pages/index';
Expand Down
2 changes: 1 addition & 1 deletion libs/client/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@fal-ai/serverless-client",
"description": "The fal serverless JS/TS client",
"version": "0.1.0",
"version": "0.2.0",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions libs/client/src/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ describe('The config test suite', () => {
credentials: {
keyId: 'key-id',
keySecret: 'key-secret',
userId: 'user-id',
},
};
config(newConfig);
const currentConfig = getConfig();
expect(currentConfig).toEqual(newConfig);
expect(currentConfig.host).toBe(newConfig.host);
expect(currentConfig.credentials).toEqual(newConfig.credentials);
});
});
Loading