Skip to content

Commit

Permalink
SSE using @microsoft/fetch-event-source so we can post and control …
Browse files Browse the repository at this point in the history
…more (#193)
  • Loading branch information
samuelcolvin authored Feb 14, 2024
1 parent 9eff25a commit 345ab9f
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 84 deletions.
9 changes: 1 addition & 8 deletions demo/sse.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,11 @@
async def canned_ai_response_generator() -> AsyncIterable[str]:
prompt = '**User:** What is SSE? Please include a javascript code example.\n\n**AI:** '
output = ''
msg = ''
for time, text in chain([(0.5, prompt)], CANNED_RESPONSE):
await asyncio.sleep(time)
output += text
m = FastUI(root=[c.Markdown(text=output)])
msg = f'data: {m.model_dump_json(by_alias=True, exclude_none=True)}\n\n'
yield msg

# avoid the browser reconnecting
while True:
yield msg
await asyncio.sleep(10)
yield f'data: {m.model_dump_json(by_alias=True, exclude_none=True)}\n\n'


@router.get('/sse')
Expand Down
14 changes: 10 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/npm-fastui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"typewatch": "tsc --noEmit --watch"
},
"dependencies": {
"@microsoft/fetch-event-source": "^2.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.1",
Expand Down
62 changes: 46 additions & 16 deletions src/npm-fastui/src/components/ServerLoad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FC, useCallback, useContext, useEffect, useState } from 'react'
import type { ServerLoad, PageEvent, FastProps } from '../models'

import { ErrorContext } from '../hooks/error'
import { useRequest, useSSE } from '../tools'
import { useRequest, useSSE, Method } from '../tools'
import { DefaultNotFound, DefaultTransition } from '../Defaults'
import { ConfigContext } from '../hooks/config'
import { usePageEventListen } from '../events'
Expand All @@ -14,36 +14,60 @@ import { AnyCompList } from './index'

import { SpinnerComp } from './spinner'

export const ServerLoadComp: FC<ServerLoad> = ({ path, components, loadTrigger, sse }) => {
export const ServerLoadComp: FC<ServerLoad> = (props) => {
const { path, components, loadTrigger, sse, method, sseRetry } = props
if (components) {
return <ServerLoadDefer path={path} components={components} loadTrigger={loadTrigger} sse={sse} />
return (
<ServerLoadDefer
path={path}
components={components}
loadTrigger={loadTrigger}
sse={sse}
method={method}
sseRetry={sseRetry}
/>
)
} else if (sse) {
return <ServerLoadSSE path={path} />
return <ServerLoadSSE path={path} method={method} sseRetry={sseRetry} />
} else {
return <ServerLoadFetch path={path} />
}
}

const ServerLoadDefer: FC<{ path: string; components: FastProps[]; loadTrigger?: PageEvent; sse?: boolean }> = ({
components,
path,
loadTrigger,
sse,
}) => {
interface DeferProps {
path: string
components: FastProps[]
loadTrigger?: PageEvent
sse?: boolean
method?: Method
sseRetry?: number
}

const ServerLoadDefer: FC<DeferProps> = ({ components, path, loadTrigger, sse, method, sseRetry }) => {
const { eventContext } = usePageEventListen(loadTrigger)

if (eventContext) {
return (
<EventContextProvider context={eventContext}>
{sse ? <ServerLoadSSE path={path} /> : <ServerLoadFetch path={path} />}
{sse ? (
<ServerLoadSSE path={path} method={method} sseRetry={sseRetry} />
) : (
<ServerLoadFetch path={path} method={method} />
)}
</EventContextProvider>
)
} else {
return <AnyCompList propsList={components} />
}
}

export const ServerLoadFetch: FC<{ path: string; devReload?: number }> = ({ path, devReload }) => {
interface FetchProps {
path: string
devReload?: number
method?: Method
}

export const ServerLoadFetch: FC<FetchProps> = ({ path, devReload, method }) => {
const [transitioning, setTransitioning] = useState<boolean>(false)
const [componentProps, setComponentProps] = useState<FastProps[] | null>(null)
const [notFoundUrl, setNotFoundUrl] = useState<string | undefined>(undefined)
Expand All @@ -55,7 +79,7 @@ export const ServerLoadFetch: FC<{ path: string; devReload?: number }> = ({ path
useEffect(() => {
setTransitioning(true)
let componentLoaded = true
request({ url, expectedStatus: [200, 345, 404] }).then(([status, data]) => {
request({ url, method, expectedStatus: [200, 345, 404] }).then(([status, data]) => {
if (componentLoaded) {
// 345 is treat the same as 200 - the server is expected to return valid FastUI components
if (status === 200 || status === 345) {
Expand All @@ -80,7 +104,7 @@ export const ServerLoadFetch: FC<{ path: string; devReload?: number }> = ({ path
return () => {
componentLoaded = false
}
}, [url, path, request, devReload])
}, [url, path, request, devReload, method])

useEffect(() => {
setNotFoundUrl(undefined)
Expand All @@ -89,12 +113,18 @@ export const ServerLoadFetch: FC<{ path: string; devReload?: number }> = ({ path
return <Render propsList={componentProps} notFoundUrl={notFoundUrl} transitioning={transitioning} />
}

export const ServerLoadSSE: FC<{ path: string }> = ({ path }) => {
interface SSEProps {
path: string
method?: Method
sseRetry?: number
}

export const ServerLoadSSE: FC<SSEProps> = ({ path, method, sseRetry }) => {
const [componentProps, setComponentProps] = useState<FastProps[] | null>(null)

const url = useServerUrl(path)
const onMessage = useCallback((data: any) => setComponentProps(data as FastProps[]), [])
useSSE(url, onMessage)
useSSE(url, onMessage, method, sseRetry)

return <Render propsList={componentProps} transitioning={false} />
}
Expand Down
2 changes: 2 additions & 0 deletions src/npm-fastui/src/models.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ export interface ServerLoad {
loadTrigger?: PageEvent
components?: FastProps[]
sse?: boolean
sseRetry?: number
method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'
type: 'ServerLoad'
}
export interface Image {
Expand Down
Loading

0 comments on commit 345ab9f

Please sign in to comment.