Allow users to dream directly from within Manifold
This commit is contained in:
parent
c2fa9cb4c4
commit
6f65b3854f
3
web/.gitignore
vendored
3
web/.gitignore
vendored
|
@ -2,4 +2,5 @@
|
|||
.next
|
||||
node_modules
|
||||
out
|
||||
tsconfig.tsbuildinfo
|
||||
tsconfig.tsbuildinfo
|
||||
.env*
|
||||
|
|
|
@ -42,6 +42,7 @@ import ItalicIcon from 'web/lib/icons/italic-icon'
|
|||
import LinkIcon from 'web/lib/icons/link-icon'
|
||||
import { getUrl } from 'common/util/parse'
|
||||
import { TiptapSpoiler } from 'common/util/tiptap-spoiler'
|
||||
import { ImageModal } from './editor/image-modal'
|
||||
|
||||
const DisplayImage = Image.configure({
|
||||
HTMLAttributes: {
|
||||
|
@ -232,6 +233,7 @@ export function TextEditor(props: {
|
|||
children?: React.ReactNode // additional toolbar buttons
|
||||
}) {
|
||||
const { editor, upload, children } = props
|
||||
const [imageOpen, setImageOpen] = useState(false)
|
||||
const [iframeOpen, setIframeOpen] = useState(false)
|
||||
const [marketOpen, setMarketOpen] = useState(false)
|
||||
|
||||
|
@ -245,12 +247,19 @@ export function TextEditor(props: {
|
|||
{/* Toolbar, with buttons for images and embeds */}
|
||||
<div className="flex h-9 items-center gap-5 pl-4 pr-1">
|
||||
<Tooltip text="Add image" noTap noFade>
|
||||
<FileUploadButton
|
||||
onFiles={upload.mutate}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setImageOpen(true)}
|
||||
className="-m-2.5 flex h-10 w-10 items-center justify-center rounded-full text-gray-400 hover:text-gray-500"
|
||||
>
|
||||
<ImageModal
|
||||
editor={editor}
|
||||
upload={upload}
|
||||
open={imageOpen}
|
||||
setOpen={setImageOpen}
|
||||
/>
|
||||
<PhotographIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</FileUploadButton>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Add embed" noTap noFade>
|
||||
<button
|
||||
|
|
160
web/components/editor/image-modal.tsx
Normal file
160
web/components/editor/image-modal.tsx
Normal file
|
@ -0,0 +1,160 @@
|
|||
import { UploadIcon } from '@heroicons/react/outline'
|
||||
import { Editor } from '@tiptap/react'
|
||||
import { useState } from 'react'
|
||||
import { AlertBox } from '../alert-box'
|
||||
import { Button } from '../button'
|
||||
import { FileUploadButton } from '../file-upload-button'
|
||||
import { Col } from '../layout/col'
|
||||
import { Modal } from '../layout/modal'
|
||||
import { Row } from '../layout/row'
|
||||
import { Tabs } from '../layout/tabs'
|
||||
|
||||
const MODIFIERS =
|
||||
'8k, beautiful, illustration, trending on art station, picture of the day, epic composition'
|
||||
|
||||
export function ImageModal(props: {
|
||||
editor: Editor | null
|
||||
// TODO: Type this correctly?
|
||||
upload: any
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
}) {
|
||||
const { upload, open, setOpen } = props
|
||||
return (
|
||||
<Modal open={open} setOpen={setOpen}>
|
||||
<Col className="gap-2 rounded bg-white p-6">
|
||||
<Tabs
|
||||
tabs={[
|
||||
{
|
||||
title: 'Upload file',
|
||||
content: (
|
||||
<Col>
|
||||
<FileUploadButton
|
||||
onFiles={(files) => {
|
||||
setOpen(false)
|
||||
upload.mutate(files)
|
||||
}}
|
||||
className="relative block w-full rounded-lg border-2 border-dashed border-gray-300 p-12 text-center hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
<UploadIcon className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<span className="mt-2 block text-sm font-medium text-gray-400">
|
||||
Upload an image file
|
||||
</span>
|
||||
</FileUploadButton>
|
||||
</Col>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Dream',
|
||||
content: <DreamTab {...props} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const API_KEY = process.env.NEXT_PUBLIC_DREAM_KEY
|
||||
|
||||
function DreamTab(props: {
|
||||
editor: Editor | null
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
}) {
|
||||
const { editor, setOpen } = props
|
||||
const [input, setInput] = useState('')
|
||||
const [isDreaming, setIsDreaming] = useState(false)
|
||||
const [imageUrl, setImageUrl] = useState('')
|
||||
const imageCode = `<img src="${imageUrl}" alt="${input}" />`
|
||||
|
||||
if (!API_KEY) {
|
||||
return (
|
||||
<AlertBox
|
||||
title="Missing API Key"
|
||||
text="An API key from https://beta.dreamstudio.ai/ is needed to dream; add it to your web/.env.local"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
async function dream() {
|
||||
setIsDreaming(true)
|
||||
const url = `/api/v0/dream`
|
||||
const data = {
|
||||
prompt: input + ', ' + MODIFIERS,
|
||||
apiKey: API_KEY,
|
||||
}
|
||||
const headers = {
|
||||
'api-key': 'quickstart-QUdJIGlzIGNvbWluZy4uLi4K',
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
const json = await response.json()
|
||||
setImageUrl(json.url)
|
||||
setIsDreaming(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Col className="gap-2">
|
||||
<Row className="gap-2">
|
||||
<input
|
||||
autoFocus
|
||||
type="text"
|
||||
name="embed"
|
||||
id="embed"
|
||||
className="block w-full rounded-md border-gray-300 shadow-sm placeholder:text-gray-300 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="A crane playing poker on a green table"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Button
|
||||
className="whitespace-nowrap"
|
||||
onClick={dream}
|
||||
loading={isDreaming}
|
||||
>
|
||||
Dream
|
||||
{/* TODO: Charge M$5 with ({formatMoney(5)}) */}
|
||||
</Button>
|
||||
</Row>
|
||||
{isDreaming && (
|
||||
<div className="text-sm">This may take ~10 seconds...</div>
|
||||
)}
|
||||
<div className="pt-2 text-sm text-gray-400">Modifiers: {MODIFIERS}</div>
|
||||
|
||||
{/* Show the current imageUrl */}
|
||||
{imageUrl && (
|
||||
<>
|
||||
{' '}
|
||||
<img src={imageUrl} alt="Image" />
|
||||
<Row className="gap-2">
|
||||
<Button
|
||||
disabled={isDreaming}
|
||||
onClick={() => {
|
||||
if (editor) {
|
||||
editor.chain().insertContent(imageCode).run()
|
||||
setInput('')
|
||||
setOpen(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Add image
|
||||
</Button>
|
||||
<Button
|
||||
color="gray"
|
||||
onClick={() => {
|
||||
setInput('')
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</Col>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user