Last active
August 14, 2024 11:19
-
-
Save laginha/7807e3ab32a918e6d352a5981ab250c7 to your computer and use it in GitHub Desktop.
Yet incomplete alternative to dify-client made for fun
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { fetchEventData, IFetchOptions } from 'fetch-sse' | |
// Defines the options for running a workflow, including an optional data handler. | |
export interface FetchEventStreamOptions extends IFetchOptions { | |
onData?: (json: any) => void | |
} | |
// Extends FetchEventStreamOptions to include user-specific and workflow control options. | |
export interface WorkflowOptions extends FetchEventStreamOptions { | |
body: { | |
user: string // User identifier. | |
inputs: any // Inputs for the workflow. | |
} | |
} | |
// Base API URL for DIFY services. | |
const DIFY_API_URL = 'https://api.dify.ai/v1' | |
// Endpoint URL for running workflows. | |
const WORKFLOW_URL = `${DIFY_API_URL}/workflows/run` | |
// Defaults for the fetch request. | |
const DEFAULT_METHOD = 'POST' | |
const DEFAULT_HEADERS = { | |
'Content-Type': 'application/json', | |
Accept: 'application/json', | |
} | |
/** | |
* Initiates an EventSource connection to stream data from a specified URL. | |
* This function sets up a connection using the FetchEventSource API and handles incoming messages. | |
* | |
* @param {string} url - The URL to connect to for streaming data. | |
* @param {FetchEventStreamOptions} options - Configuration options for the event source connection, | |
* including event handlers and fetch initialization options. | |
* @returns {Promise<void>} - A promise that resolves when the event source connection is established. | |
*/ | |
export function fetchEventStream( | |
url: string, | |
options: FetchEventStreamOptions | |
): Promise<void> { | |
const { onData, ...fetchEventDataInit } = options | |
return fetchEventData(url, { | |
/** | |
* Default handler for incoming messages from the event source. | |
* Filters out 'ping' events and processes other messages by attempting to parse them as JSON. | |
* If parsing is successful and an 'ondata' handler is provided, it passes the parsed data to the handler. | |
*/ | |
onMessage(event) { | |
if (event?.data) { | |
try { | |
onData?.(JSON.parse(event.data)) | |
} catch (error) { | |
console.error('Error parsing event data:', error) | |
} | |
} | |
}, | |
...fetchEventDataInit, | |
}) | |
} | |
/** | |
* Base class for clients interacting with the DIFY API. | |
*/ | |
class BaseClient { | |
/** | |
* Default options for fetch requests, including headers and HTTP method. | |
*/ | |
protected defaultOptions: any | |
/** | |
* Constructs a new BaseClient instance with default options initialized. | |
* @param {string} apiKey - The API key used for authorization. | |
*/ | |
constructor(apiKey: string) { | |
this.defaultOptions = { | |
method: DEFAULT_METHOD, // Default HTTP method for requests. | |
headers: { | |
...DEFAULT_HEADERS, | |
Authorization: `Bearer ${apiKey}`, // Authorization header using Bearer token. | |
}, | |
} | |
} | |
} | |
/** | |
* Client for managing blocking workflows via the DIFY API. | |
*/ | |
export class BlockClient extends BaseClient { | |
private responseMode: string = 'blocking' | |
/** | |
* Constructs a new BlockClient instance. | |
* @param {string} apiKey - The API key used for authorization. | |
*/ | |
constructor(apiKey: string) { | |
super(apiKey) | |
} | |
} | |
/** | |
* Client for managing streaming workflows via the DIFY API. | |
*/ | |
export class StreamClient extends BaseClient { | |
private responseMode: string = 'streaming' | |
/** | |
* Constructs a new StreamClient instance. | |
* @param {string} apiKey - The API key used for authorization. | |
*/ | |
constructor(apiKey: string) { | |
super(apiKey) | |
} | |
/** | |
* Executes a workflow with the provided options. | |
* @param {WorkflowOptions} options - The options for the workflow execution. | |
* @returns {Promise<void>} A promise that resolves when the workflow is executed. | |
*/ | |
runWorkflow(options: WorkflowOptions): Promise<void> { | |
const { body, ...runWorkflowOptions } = options | |
return fetchEventStream(WORKFLOW_URL, { | |
data: { | |
...body, | |
response_mode: this.responseMode, // Response mode set to 'streaming'. | |
}, | |
...this.defaultOptions, | |
...runWorkflowOptions, | |
}) | |
} | |
} |
Nextjs' App Route Handler example:
/app/workflow/route.ts
const dify = new StreamClient(process.env.DIFY_SECRET_KEY)
const encoder = new TextEncoder()
const RESPONSE_HEADERS = {
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
}
export async function POST(request: Request) {
const { inputs } = await request.json()
const responseStream = new ReadableStream({
async start(controller) {
await dify.runWorkflow({
body: {
inputs,
user: 'app-route-handler',
},
onMessage(event) {
if (event?.data) {
const chunk = encoder.encode('data: ' + event.data + '\n\n')
controller.enqueue(chunk)
}
},
onError() {
controller.close()
},
onClose() {
controller.close()
},
})
},
})
return new Response(responseStream, {
headers: RESPONSE_HEADERS,
})
}
export const runtime = 'edge'
export const dynamic = 'force-dynamic'
client side
await fetchEventStream('/workflow', {
method: 'POST',
data: {
inputs: { foo: 'bar' }
},
onData(data) {
console.log(data)
},
})
Using dify-client
instead on the route handler:
const client = new CompletionClient(process.env.DIFY_SECRET_KEY)
const RESPONSE_HEADERS = {
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
}
export async function POST(request: Request) {
const { inputs } = await request.json()
// @ts-ignore <-- don't foget this
const res = await client.runWorkflow(inputs, 'app-route-handler', true)
return new Response(res.data as any, { headers: RESPONSE_HEADERS })
}
Client side using fetch-sse
fetchEventData('/protected/workflow', {
method: 'POST',
data: {
inputs: { foo: 'bar' }
},
onMessage(event) {
if (event?.data) {
console.log(JSON.parse(event.data))
}
}
})
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage: