@@ -1,5 +1,6 @@ .markdown { max-width: 100%; + margin-top: 12px; :global(.ds-markdown) { background-color: transparent !important; @@ -0,0 +1,413 @@ +export interface StreamingCodeBlock { + id: string + title: string + code: string + language: 'python' | 'bash' +} + +interface StreamingBlocksParams { + modelsType: string + modelSlug: string + currentVersion: string + formattedModelParams: string + showFileExample: boolean + prefix: string +} + +const SSE_LOOP_HTTPX = ` buffer = "" + for chunk in response.iter_bytes(): + buffer += chunk.decode() + while "\\n\\n" in buffer: + message, buffer = buffer.split("\\n\\n", 1) + event = data = None + for line in message.splitlines(): + if line.startswith("event:"): + event = line[6:].strip() + elif line.startswith("data:"): + data = json.loads(line[5:].strip()) + if event == "token": + print(data["content"], end="", flush=True) + elif event == "error": + print(data["detail"])` + +const SSE_LOOP_REQUESTS = ` buffer = "" + for chunk in response.iter_content(chunk_size=1024): + buffer += chunk.decode() + while "\\n\\n" in buffer: + message, buffer = buffer.split("\\n\\n", 1) + event = data = None + for line in message.splitlines(): + if line.startswith("event:"): + event = line[6:].strip() + elif line.startswith("data:"): + data = json.loads(line[5:].strip()) + if event == "token": + print(data["content"], end="", flush=True) + elif event == "error": + print(data["detail"])` + +function toCompactInfoJson(formattedModelParams: string) { + const body = formattedModelParams + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + .join('') + + return `{${body}}` +} + +function indentCode(code: string, spaces: number) { + const padding = ' '.repeat(spaces) + return code + .split('\n') + .map((line) => (line ? `${padding}${line}` : line)) + .join('\n') +} + +function getReconnectTitle(showFileExample: boolean) { + return showFileExample ? '3. Переподключение к стриму' : '2. Переподключение к стриму' +} + +export function getHttpxStreamingBlocks({ + modelsType, + modelSlug, + formattedModelParams, + showFileExample, + prefix, +}: StreamingBlocksParams): StreamingCodeBlock[] { + const url = `https://api.air.fail/public/${modelsType}/${modelSlug}/stream` + + const streamCode = `import httpx +import json + +url = "${url}" +api_key = "" +idempotency_key = "stream-example-1" # уникальный ID запроса; необходим для переподключения к стриму + +form_data = { + "content": "Привет! Как дела?", + "info": json.dumps({ + ${formattedModelParams} + }), +} +headers = { + "Authorization": api_key, + "Idempotency-Key": idempotency_key, +} + +with httpx.stream("POST", url, json=form_data, headers=headers, timeout=120) as response: +${SSE_LOOP_HTTPX}` + + const streamFileCode = `import httpx +import json + +url = "${url}" +api_key = "" +idempotency_key = "stream-example-1" # уникальный ID запроса; необходим для переподключения к стриму +filename = "example.png" + +form_data = { + "content": "Привет, что на картинке?", + "info": json.dumps({ + ${formattedModelParams} + }), +} +headers = { + "Authorization": api_key, + "Idempotency-Key": idempotency_key, +} + +with open(filename, "rb") as f: + files = {"file": (filename, f)} + with httpx.stream("POST", url, data=form_data, files=files, headers=headers, timeout=120) as response: +${indentCode(SSE_LOOP_HTTPX, 4)}` + + const reconnectCode = `import httpx +import json + +url = "${url}" +api_key = "" +idempotency_key = "stream-example-1" # тот же ключ, что при POST; необходим для переподключения +offset = 10 # номер последнего полученного event; стрим продолжится с этого места + +headers = { + "Authorization": api_key, + "Idempotency-Key": idempotency_key, +} + +with httpx.stream("GET", url, params={"offset": offset}, headers=headers, timeout=120) as response: +${SSE_LOOP_HTTPX}` + + const blocks: StreamingCodeBlock[] = [ + { id: `${prefix}-stream-1`, title: '1. Обычный стриминг', code: streamCode, language: 'python' }, + ] + + if (showFileExample) { + blocks.push({ + id: `${prefix}-stream-2`, + title: '2. Стриминг с прикреплённым файлом', + code: streamFileCode, + language: 'python', + }) + } + + blocks.push({ + id: `${prefix}-stream-3`, + title: getReconnectTitle(showFileExample), + code: reconnectCode, + language: 'python', + }) + + return blocks +} + +export function getRequestsStreamingBlocks({ + modelsType, + modelSlug, + formattedModelParams, + showFileExample, + prefix, +}: StreamingBlocksParams): StreamingCodeBlock[] { + const url = `https://api.air.fail/public/${modelsType}/${modelSlug}/stream` + + const streamCode = `import requests +import json + +url = "${url}" +api_key = "" +idempotency_key = "stream-example-1" # уникальный ID запроса; необходим для переподключения к стриму + +form_data = { + "content": "Привет! Как дела?", + "info": json.dumps({ + ${formattedModelParams} + }), +} +headers = { + "Authorization": api_key, + "Idempotency-Key": idempotency_key, +} + +with requests.post(url, json=form_data, headers=headers, stream=True, timeout=120) as response: +${SSE_LOOP_REQUESTS}` + + const streamFileCode = `import requests +import json + +url = "${url}" +api_key = "" +idempotency_key = "stream-example-1" # уникальный ID запроса; необходим для переподключения к стриму +filename = "example.png" + +form_data = { + "content": "Привет, что на картинке?", + "info": json.dumps({ + ${formattedModelParams} + }), +} +headers = { + "Authorization": api_key, + "Idempotency-Key": idempotency_key, +} + +with open(filename, "rb") as f: + files = {"file": (filename, f)} + with requests.post(url, data=form_data, files=files, headers=headers, stream=True, timeout=120) as response: +${indentCode(SSE_LOOP_REQUESTS, 4)}` + + const reconnectCode = `import requests +import json + +url = "${url}" +api_key = "" +idempotency_key = "stream-example-1" # тот же ключ, что при POST; необходим для переподключения +offset = 10 # номер последнего полученного event; стрим продолжится с этого места + +headers = { + "Authorization": api_key, + "Idempotency-Key": idempotency_key, +} + +with requests.get(url, params={"offset": offset}, headers=headers, stream=True, timeout=120) as response: +${SSE_LOOP_REQUESTS}` + + const blocks: StreamingCodeBlock[] = [ + { id: `${prefix}-stream-1`, title: '1. Обычный стриминг', code: streamCode, language: 'python' }, + ] + + if (showFileExample) { + blocks.push({ + id: `${prefix}-stream-2`, + title: '2. Стриминг с прикреплённым файлом', + code: streamFileCode, + language: 'python', + }) + } + + blocks.push({ + id: `${prefix}-stream-3`, + title: getReconnectTitle(showFileExample), + code: reconnectCode, + language: 'python', + }) + + return blocks +} + +export function getOpenAiStreamingBlocks({ + currentVersion, + formattedModelParams, + showFileExample, + prefix, +}: StreamingBlocksParams): StreamingCodeBlock[] { + const streamCode = `from openai import OpenAI + +client = OpenAI( + base_url="https://api.air.fail/public/openai", + api_key="", + default_headers={ + "Idempotency-Key": "stream-example-1", # уникальный ID запроса; необходим для переподключения к стриму + }, +) + +for event in client.responses.create( + model="${currentVersion}", + instructions="Ты умный ассистент", + input="Напиши короткий тост на день рождения", + stream=True, + metadata={ + ${formattedModelParams} + }, +): + print(event.model_dump_json(warnings=False))` + + const streamFileCode = `import base64 + +from openai import OpenAI + +client = OpenAI( + base_url="https://api.air.fail/public/openai", + api_key="", + default_headers={ + "Idempotency-Key": "stream-example-1", # уникальный ID запроса; необходим для переподключения к стриму + }, +) +filename = "example.png" + +with open(filename, "rb") as f: + image_base64 = base64.b64encode(f.read()).decode("utf-8") + +for event in client.responses.create( + model="${currentVersion}", + input=[ + { + "role": "user", + "content": [ + {"type": "input_text", "text": "Опиши, что изображено на картинке."}, + { + "type": "input_image", + "image_url": f"data:image/png;base64,{image_base64}", + }, + ], + } + ], + stream=True, + metadata={ + ${formattedModelParams} + }, +): + print(event.model_dump_json(warnings=False))` + + const reconnectCode = `from openai import OpenAI + +client = OpenAI( + base_url="https://api.air.fail/public/openai", + api_key="", + default_headers={ + "Idempotency-Key": "stream-example-1", # тот же ключ, что при POST; необходим для переподключения + }, +) + +with client.responses.stream( + response_id="stream-example-1", + starting_after=0, # номер последнего полученного event; стрим продолжится с этого места +) as stream: + for event in stream: + print(event.model_dump_json(warnings=False))` + + const blocks: StreamingCodeBlock[] = [ + { id: `${prefix}-stream-1`, title: '1. Обычный стриминг', code: streamCode, language: 'python' }, + ] + + if (showFileExample) { + blocks.push({ + id: `${prefix}-stream-2`, + title: '2. Стриминг с прикреплённым файлом', + code: streamFileCode, + language: 'python', + }) + } + + blocks.push({ + id: `${prefix}-stream-3`, + title: getReconnectTitle(showFileExample), + code: reconnectCode, + language: 'python', + }) + + return blocks +} + +export function getCurlStreamingBlocks({ + modelsType, + modelSlug, + formattedModelParams, + showFileExample, + prefix, +}: StreamingBlocksParams): StreamingCodeBlock[] { + const url = `https://api.air.fail/public/${modelsType}/${modelSlug}/stream` + const infoJson = toCompactInfoJson(formattedModelParams) + + const streamCode = `# Idempotency-Key — уникальный ID запроса; необходим для переподключения +curl -N -X POST "${url}" \\ + -H "Authorization: " \\ + -H "Idempotency-Key: stream-example-1" \\ + -F "content=Привет! Как дела?" \\ + -F 'info=${infoJson}'` + + const streamFileCode = `# Idempotency-Key — уникальный ID запроса; необходим для переподключения +curl -N -X POST "${url}" \\ + -H "Authorization: " \\ + -H "Idempotency-Key: stream-example-1" \\ + -F "content=Привет, что на картинке?" \\ + -F 'info=${infoJson}' \\ + -F "file=@example.png"` + + const reconnectCode = `# Idempotency-Key — тот же ключ, что при POST; необходим для переподключения +# offset — номер последнего полученного event; стрим продолжится с этого места +curl -N -X GET "${url}?offset=10" \\ + -H "Authorization: " \\ + -H "Idempotency-Key: stream-example-1"` + + const blocks: StreamingCodeBlock[] = [ + { id: `${prefix}-stream-1`, title: '1. Обычный стриминг', code: streamCode, language: 'bash' }, + ] + + if (showFileExample) { + blocks.push({ + id: `${prefix}-stream-2`, + title: '2. Стриминг с прикреплённым файлом', + code: streamFileCode, + language: 'bash', + }) + } + + blocks.push({ + id: `${prefix}-stream-3`, + title: getReconnectTitle(showFileExample), + code: reconnectCode, + language: 'bash', + }) + + return blocks +} @@ -6,7 +6,25 @@ import { IComponentProps } from '../types' import { Markdown } from '#/widgets/markdown/markdown' -const CodeBlock = ({ title, code, id }: { title: string; code: string; id: string }) => { +import { + getCurlStreamingBlocks, + getHttpxStreamingBlocks, + getOpenAiStreamingBlocks, + getRequestsStreamingBlocks, + StreamingCodeBlock, +} from './scope-variants-streaming' + +const CodeBlock = ({ + title, + code, + id, + language = 'python', +}: { + title: string + code: string + id: string + language?: 'python' | 'bash' +}) => { const copy = (e: React.MouseEvent) => { e.preventDefault() e.stopPropagation() @@ -86,20 +104,35 @@ const CodeBlock = ({ title, code, id }: { title: string; code: string; id: strin /> - + ) } -export const PythonHTTPX = ({ currentVersion, modelSlug, modelsType, showFileExample, modelParams }: IComponentProps) => { - const formattedModelParams = Object.entries({ +function StreamingExamples({ blocks, sectionId }: { blocks: StreamingCodeBlock[]; sectionId: string }) { + return ( + + + {blocks.map((block) => ( + + ))} + + ) +} + +function formatModelParams(currentVersion: string, modelParams: IComponentProps['modelParams']) { + return Object.entries({ ...(currentVersion && { version: currentVersion }), - ...modelParams + ...modelParams, }) .filter(([_, value]) => value !== undefined) .map(([key, value]) => `"${key}": ${typeof value === 'string' ? `"${value}"` : value}`) .join(',\n ') +} + +export const PythonHTTPX = ({ currentVersion, modelSlug, modelsType, showFileExample, modelParams }: IComponentProps) => { + const formattedModelParams = formatModelParams(currentVersion, modelParams) const code1 = `import httpx import json @@ -143,18 +176,25 @@ print(response.json())` {showFileExample && } + {modelsType === 'text' && ( + + )} ) } export const PythonRequests = ({ currentVersion, modelSlug, modelsType, showFileExample, modelParams }: IComponentProps) => { - const formattedModelParams = Object.entries({ - ...(currentVersion && { version: currentVersion }), - ...modelParams - }) - .filter(([_, value]) => value !== undefined) - .map(([key, value]) => `"${key}": ${typeof value === 'string' ? `"${value}"` : value}`) - .join(',\n ') + const formattedModelParams = formatModelParams(currentVersion, modelParams) const code1 = `import requests import json @@ -196,11 +236,26 @@ print(response.json())` {showFileExample && } + {modelsType === 'text' && ( + + )} ) } export const PythonOpenAISDK = ({ currentVersion, modelSlug, modelsType, showFileExample, modelParams }: IComponentProps) => { + const formattedModelParams = formatModelParams(currentVersion, modelParams) + const code1 = `from openai import OpenAI client = OpenAI( @@ -252,18 +307,25 @@ print(response)` {showFileExample && } + {modelsType === 'text' && ( + + )} ) } export const cURL = ({ currentVersion, modelSlug, modelsType, showFileExample, modelParams }: IComponentProps) => { - const formattedModelParams = Object.entries({ - ...(currentVersion && { version: currentVersion }), - ...modelParams - }) - .filter(([_, value]) => value !== undefined) - .map(([key, value]) => `"${key}": ${typeof value === 'string' ? `"${value}"` : value}`) - .join(',\n ') + const formattedModelParams = formatModelParams(currentVersion, modelParams) const code1 = `curl -X POST "https://api.air.fail/public/${modelsType}/${modelSlug}" -H "Authorization: " @@ -282,8 +344,21 @@ export const cURL = ({ currentVersion, modelSlug, modelsType, showFileExample, m return ( - - {showFileExample && } + + {showFileExample && } + {modelsType === 'text' && ( + + )} ) } \ No newline at end of file