mirror of
https://github.com/furyhawk/home_stack.git
synced 2026-07-20 17:56:37 +00:00
feat: Generate TypeScript client for Ollama API using OpenAPI specification
- Added a new script `generate-ollama-client-simple.sh` for generating a simple TypeScript client using the existing OpenAPI tools. - Created a comprehensive `generate-ollama-client.sh` script to generate a FastAPI client from the Ollama OpenAPI specification with support for axios. - Introduced `generate_fastapi_client.py` to support generating FastAPI clients from OpenAPI specifications in multiple languages, including TypeScript and Python. - Generated TypeScript types for the Ollama API endpoints in `types.gen.ts`. - Included example usage files for both Python and TypeScript clients to demonstrate how to interact with the Ollama API.
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import type { ApiRequestOptions } from "./ApiRequestOptions"
|
||||
import type { ApiResult } from "./ApiResult"
|
||||
|
||||
export class ApiError extends Error {
|
||||
public readonly url: string
|
||||
public readonly status: number
|
||||
public readonly statusText: string
|
||||
public readonly body: unknown
|
||||
public readonly request: ApiRequestOptions
|
||||
|
||||
constructor(
|
||||
request: ApiRequestOptions,
|
||||
response: ApiResult,
|
||||
message: string,
|
||||
) {
|
||||
super(message)
|
||||
|
||||
this.name = "ApiError"
|
||||
this.url = response.url
|
||||
this.status = response.status
|
||||
this.statusText = response.statusText
|
||||
this.body = response.body
|
||||
this.request = request
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export type ApiRequestOptions<T = unknown> = {
|
||||
readonly body?: any
|
||||
readonly cookies?: Record<string, unknown>
|
||||
readonly errors?: Record<number | string, string>
|
||||
readonly formData?: Record<string, unknown> | any[] | Blob | File
|
||||
readonly headers?: Record<string, unknown>
|
||||
readonly mediaType?: string
|
||||
readonly method:
|
||||
| "DELETE"
|
||||
| "GET"
|
||||
| "HEAD"
|
||||
| "OPTIONS"
|
||||
| "PATCH"
|
||||
| "POST"
|
||||
| "PUT"
|
||||
readonly path?: Record<string, unknown>
|
||||
readonly query?: Record<string, unknown>
|
||||
readonly responseHeader?: string
|
||||
readonly responseTransformer?: (data: unknown) => Promise<T>
|
||||
readonly url: string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export type ApiResult<TData = any> = {
|
||||
readonly body: TData
|
||||
readonly ok: boolean
|
||||
readonly status: number
|
||||
readonly statusText: string
|
||||
readonly url: string
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
export class CancelError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
this.name = "CancelError"
|
||||
}
|
||||
|
||||
public get isCancelled(): boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export interface OnCancel {
|
||||
readonly isResolved: boolean
|
||||
readonly isRejected: boolean
|
||||
readonly isCancelled: boolean
|
||||
|
||||
(cancelHandler: () => void): void
|
||||
}
|
||||
|
||||
export class CancelablePromise<T> implements Promise<T> {
|
||||
private _isResolved: boolean
|
||||
private _isRejected: boolean
|
||||
private _isCancelled: boolean
|
||||
readonly cancelHandlers: (() => void)[]
|
||||
readonly promise: Promise<T>
|
||||
private _resolve?: (value: T | PromiseLike<T>) => void
|
||||
private _reject?: (reason?: unknown) => void
|
||||
|
||||
constructor(
|
||||
executor: (
|
||||
resolve: (value: T | PromiseLike<T>) => void,
|
||||
reject: (reason?: unknown) => void,
|
||||
onCancel: OnCancel,
|
||||
) => void,
|
||||
) {
|
||||
this._isResolved = false
|
||||
this._isRejected = false
|
||||
this._isCancelled = false
|
||||
this.cancelHandlers = []
|
||||
this.promise = new Promise<T>((resolve, reject) => {
|
||||
this._resolve = resolve
|
||||
this._reject = reject
|
||||
|
||||
const onResolve = (value: T | PromiseLike<T>): void => {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return
|
||||
}
|
||||
this._isResolved = true
|
||||
if (this._resolve) this._resolve(value)
|
||||
}
|
||||
|
||||
const onReject = (reason?: unknown): void => {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return
|
||||
}
|
||||
this._isRejected = true
|
||||
if (this._reject) this._reject(reason)
|
||||
}
|
||||
|
||||
const onCancel = (cancelHandler: () => void): void => {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return
|
||||
}
|
||||
this.cancelHandlers.push(cancelHandler)
|
||||
}
|
||||
|
||||
Object.defineProperty(onCancel, "isResolved", {
|
||||
get: (): boolean => this._isResolved,
|
||||
})
|
||||
|
||||
Object.defineProperty(onCancel, "isRejected", {
|
||||
get: (): boolean => this._isRejected,
|
||||
})
|
||||
|
||||
Object.defineProperty(onCancel, "isCancelled", {
|
||||
get: (): boolean => this._isCancelled,
|
||||
})
|
||||
|
||||
return executor(onResolve, onReject, onCancel as OnCancel)
|
||||
})
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return "Cancellable Promise"
|
||||
}
|
||||
|
||||
public then<TResult1 = T, TResult2 = never>(
|
||||
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
||||
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
|
||||
): Promise<TResult1 | TResult2> {
|
||||
return this.promise.then(onFulfilled, onRejected)
|
||||
}
|
||||
|
||||
public catch<TResult = never>(
|
||||
onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null,
|
||||
): Promise<T | TResult> {
|
||||
return this.promise.catch(onRejected)
|
||||
}
|
||||
|
||||
public finally(onFinally?: (() => void) | null): Promise<T> {
|
||||
return this.promise.finally(onFinally)
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return
|
||||
}
|
||||
this._isCancelled = true
|
||||
if (this.cancelHandlers.length) {
|
||||
try {
|
||||
for (const cancelHandler of this.cancelHandlers) {
|
||||
cancelHandler()
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Cancellation threw an error", error)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.cancelHandlers.length = 0
|
||||
if (this._reject) this._reject(new CancelError("Request aborted"))
|
||||
}
|
||||
|
||||
public get isCancelled(): boolean {
|
||||
return this._isCancelled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { AxiosRequestConfig, AxiosResponse } from "axios"
|
||||
import type { ApiRequestOptions } from "./ApiRequestOptions"
|
||||
|
||||
type Headers = Record<string, string>
|
||||
type Middleware<T> = (value: T) => T | Promise<T>
|
||||
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>
|
||||
|
||||
export class Interceptors<T> {
|
||||
_fns: Middleware<T>[]
|
||||
|
||||
constructor() {
|
||||
this._fns = []
|
||||
}
|
||||
|
||||
eject(fn: Middleware<T>): void {
|
||||
const index = this._fns.indexOf(fn)
|
||||
if (index !== -1) {
|
||||
this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]
|
||||
}
|
||||
}
|
||||
|
||||
use(fn: Middleware<T>): void {
|
||||
this._fns = [...this._fns, fn]
|
||||
}
|
||||
}
|
||||
|
||||
export type OpenAPIConfig = {
|
||||
BASE: string
|
||||
CREDENTIALS: "include" | "omit" | "same-origin"
|
||||
ENCODE_PATH?: ((path: string) => string) | undefined
|
||||
HEADERS?: Headers | Resolver<Headers> | undefined
|
||||
PASSWORD?: string | Resolver<string> | undefined
|
||||
TOKEN?: string | Resolver<string> | undefined
|
||||
USERNAME?: string | Resolver<string> | undefined
|
||||
VERSION: string
|
||||
WITH_CREDENTIALS: boolean
|
||||
interceptors: {
|
||||
request: Interceptors<AxiosRequestConfig>
|
||||
response: Interceptors<AxiosResponse>
|
||||
}
|
||||
}
|
||||
|
||||
export const OpenAPI: OpenAPIConfig = {
|
||||
BASE: "",
|
||||
CREDENTIALS: "include",
|
||||
ENCODE_PATH: undefined,
|
||||
HEADERS: undefined,
|
||||
PASSWORD: undefined,
|
||||
TOKEN: undefined,
|
||||
USERNAME: undefined,
|
||||
VERSION: "0.1.0",
|
||||
WITH_CREDENTIALS: false,
|
||||
interceptors: {
|
||||
request: new Interceptors(),
|
||||
response: new Interceptors(),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
import axios from "axios"
|
||||
import type {
|
||||
AxiosError,
|
||||
AxiosRequestConfig,
|
||||
AxiosResponse,
|
||||
AxiosInstance,
|
||||
} from "axios"
|
||||
|
||||
import { ApiError } from "./ApiError"
|
||||
import type { ApiRequestOptions } from "./ApiRequestOptions"
|
||||
import type { ApiResult } from "./ApiResult"
|
||||
import { CancelablePromise } from "./CancelablePromise"
|
||||
import type { OnCancel } from "./CancelablePromise"
|
||||
import type { OpenAPIConfig } from "./OpenAPI"
|
||||
|
||||
export const isString = (value: unknown): value is string => {
|
||||
return typeof value === "string"
|
||||
}
|
||||
|
||||
export const isStringWithValue = (value: unknown): value is string => {
|
||||
return isString(value) && value !== ""
|
||||
}
|
||||
|
||||
export const isBlob = (value: any): value is Blob => {
|
||||
return value instanceof Blob
|
||||
}
|
||||
|
||||
export const isFormData = (value: unknown): value is FormData => {
|
||||
return value instanceof FormData
|
||||
}
|
||||
|
||||
export const isSuccess = (status: number): boolean => {
|
||||
return status >= 200 && status < 300
|
||||
}
|
||||
|
||||
export const base64 = (str: string): string => {
|
||||
try {
|
||||
return btoa(str)
|
||||
} catch (err) {
|
||||
// @ts-ignore
|
||||
return Buffer.from(str).toString("base64")
|
||||
}
|
||||
}
|
||||
|
||||
export const getQueryString = (params: Record<string, unknown>): string => {
|
||||
const qs: string[] = []
|
||||
|
||||
const append = (key: string, value: unknown) => {
|
||||
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
|
||||
}
|
||||
|
||||
const encodePair = (key: string, value: unknown) => {
|
||||
if (value === undefined || value === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
append(key, value.toISOString())
|
||||
} else if (Array.isArray(value)) {
|
||||
value.forEach((v) => encodePair(key, v))
|
||||
} else if (typeof value === "object") {
|
||||
Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v))
|
||||
} else {
|
||||
append(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
Object.entries(params).forEach(([key, value]) => encodePair(key, value))
|
||||
|
||||
return qs.length ? `?${qs.join("&")}` : ""
|
||||
}
|
||||
|
||||
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
|
||||
const encoder = config.ENCODE_PATH || encodeURI
|
||||
|
||||
const path = options.url
|
||||
.replace("{api-version}", config.VERSION)
|
||||
.replace(/{(.*?)}/g, (substring: string, group: string) => {
|
||||
if (options.path?.hasOwnProperty(group)) {
|
||||
return encoder(String(options.path[group]))
|
||||
}
|
||||
return substring
|
||||
})
|
||||
|
||||
const url = config.BASE + path
|
||||
return options.query ? url + getQueryString(options.query) : url
|
||||
}
|
||||
|
||||
export const getFormData = (
|
||||
options: ApiRequestOptions,
|
||||
): FormData | undefined => {
|
||||
if (options.formData) {
|
||||
const formData = new FormData()
|
||||
|
||||
const process = (key: string, value: unknown) => {
|
||||
if (isString(value) || isBlob(value)) {
|
||||
formData.append(key, value)
|
||||
} else {
|
||||
formData.append(key, JSON.stringify(value))
|
||||
}
|
||||
}
|
||||
|
||||
Object.entries(options.formData)
|
||||
.filter(([, value]) => value !== undefined && value !== null)
|
||||
.forEach(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((v) => process(key, v))
|
||||
} else {
|
||||
process(key, value)
|
||||
}
|
||||
})
|
||||
|
||||
return formData
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>
|
||||
|
||||
export const resolve = async <T>(
|
||||
options: ApiRequestOptions<T>,
|
||||
resolver?: T | Resolver<T>,
|
||||
): Promise<T | undefined> => {
|
||||
if (typeof resolver === "function") {
|
||||
return (resolver as Resolver<T>)(options)
|
||||
}
|
||||
return resolver
|
||||
}
|
||||
|
||||
export const getHeaders = async <T>(
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions<T>,
|
||||
): Promise<Record<string, string>> => {
|
||||
const [token, username, password, additionalHeaders] = await Promise.all([
|
||||
// @ts-ignore
|
||||
resolve(options, config.TOKEN),
|
||||
// @ts-ignore
|
||||
resolve(options, config.USERNAME),
|
||||
// @ts-ignore
|
||||
resolve(options, config.PASSWORD),
|
||||
// @ts-ignore
|
||||
resolve(options, config.HEADERS),
|
||||
])
|
||||
|
||||
const headers = Object.entries({
|
||||
Accept: "application/json",
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
})
|
||||
.filter(([, value]) => value !== undefined && value !== null)
|
||||
.reduce(
|
||||
(headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: String(value),
|
||||
}),
|
||||
{} as Record<string, string>,
|
||||
)
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers["Authorization"] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = base64(`${username}:${password}`)
|
||||
headers["Authorization"] = `Basic ${credentials}`
|
||||
}
|
||||
|
||||
if (options.body !== undefined) {
|
||||
if (options.mediaType) {
|
||||
headers["Content-Type"] = options.mediaType
|
||||
} else if (isBlob(options.body)) {
|
||||
headers["Content-Type"] = options.body.type || "application/octet-stream"
|
||||
} else if (isString(options.body)) {
|
||||
headers["Content-Type"] = "text/plain"
|
||||
} else if (!isFormData(options.body)) {
|
||||
headers["Content-Type"] = "application/json"
|
||||
}
|
||||
} else if (options.formData !== undefined) {
|
||||
if (options.mediaType) {
|
||||
headers["Content-Type"] = options.mediaType
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
export const getRequestBody = (options: ApiRequestOptions): unknown => {
|
||||
if (options.body) {
|
||||
return options.body
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const sendRequest = async <T>(
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions<T>,
|
||||
url: string,
|
||||
body: unknown,
|
||||
formData: FormData | undefined,
|
||||
headers: Record<string, string>,
|
||||
onCancel: OnCancel,
|
||||
axiosClient: AxiosInstance,
|
||||
): Promise<AxiosResponse<T>> => {
|
||||
const controller = new AbortController()
|
||||
|
||||
let requestConfig: AxiosRequestConfig = {
|
||||
data: body ?? formData,
|
||||
headers,
|
||||
method: options.method,
|
||||
signal: controller.signal,
|
||||
url,
|
||||
withCredentials: config.WITH_CREDENTIALS,
|
||||
}
|
||||
|
||||
onCancel(() => controller.abort())
|
||||
|
||||
for (const fn of config.interceptors.request._fns) {
|
||||
requestConfig = await fn(requestConfig)
|
||||
}
|
||||
|
||||
try {
|
||||
return await axiosClient.request(requestConfig)
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError<T>
|
||||
if (axiosError.response) {
|
||||
return axiosError.response
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export const getResponseHeader = (
|
||||
response: AxiosResponse<unknown>,
|
||||
responseHeader?: string,
|
||||
): string | undefined => {
|
||||
if (responseHeader) {
|
||||
const content = response.headers[responseHeader]
|
||||
if (isString(content)) {
|
||||
return content
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const getResponseBody = (response: AxiosResponse<unknown>): unknown => {
|
||||
if (response.status !== 204) {
|
||||
return response.data
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const catchErrorCodes = (
|
||||
options: ApiRequestOptions,
|
||||
result: ApiResult,
|
||||
): void => {
|
||||
const errors: Record<number, string> = {
|
||||
400: "Bad Request",
|
||||
401: "Unauthorized",
|
||||
402: "Payment Required",
|
||||
403: "Forbidden",
|
||||
404: "Not Found",
|
||||
405: "Method Not Allowed",
|
||||
406: "Not Acceptable",
|
||||
407: "Proxy Authentication Required",
|
||||
408: "Request Timeout",
|
||||
409: "Conflict",
|
||||
410: "Gone",
|
||||
411: "Length Required",
|
||||
412: "Precondition Failed",
|
||||
413: "Payload Too Large",
|
||||
414: "URI Too Long",
|
||||
415: "Unsupported Media Type",
|
||||
416: "Range Not Satisfiable",
|
||||
417: "Expectation Failed",
|
||||
418: "Im a teapot",
|
||||
421: "Misdirected Request",
|
||||
422: "Unprocessable Content",
|
||||
423: "Locked",
|
||||
424: "Failed Dependency",
|
||||
425: "Too Early",
|
||||
426: "Upgrade Required",
|
||||
428: "Precondition Required",
|
||||
429: "Too Many Requests",
|
||||
431: "Request Header Fields Too Large",
|
||||
451: "Unavailable For Legal Reasons",
|
||||
500: "Internal Server Error",
|
||||
501: "Not Implemented",
|
||||
502: "Bad Gateway",
|
||||
503: "Service Unavailable",
|
||||
504: "Gateway Timeout",
|
||||
505: "HTTP Version Not Supported",
|
||||
506: "Variant Also Negotiates",
|
||||
507: "Insufficient Storage",
|
||||
508: "Loop Detected",
|
||||
510: "Not Extended",
|
||||
511: "Network Authentication Required",
|
||||
...options.errors,
|
||||
}
|
||||
|
||||
const error = errors[result.status]
|
||||
if (error) {
|
||||
throw new ApiError(options, result, error)
|
||||
}
|
||||
|
||||
if (!result.ok) {
|
||||
const errorStatus = result.status ?? "unknown"
|
||||
const errorStatusText = result.statusText ?? "unknown"
|
||||
const errorBody = (() => {
|
||||
try {
|
||||
return JSON.stringify(result.body, null, 2)
|
||||
} catch (e) {
|
||||
return undefined
|
||||
}
|
||||
})()
|
||||
|
||||
throw new ApiError(
|
||||
options,
|
||||
result,
|
||||
`Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request method
|
||||
* @param config The OpenAPI configuration object
|
||||
* @param options The request options from the service
|
||||
* @param axiosClient The axios client instance to use
|
||||
* @returns CancelablePromise<T>
|
||||
* @throws ApiError
|
||||
*/
|
||||
export const request = <T>(
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions<T>,
|
||||
axiosClient: AxiosInstance = axios,
|
||||
): CancelablePromise<T> => {
|
||||
return new CancelablePromise(async (resolve, reject, onCancel) => {
|
||||
try {
|
||||
const url = getUrl(config, options)
|
||||
const formData = getFormData(options)
|
||||
const body = getRequestBody(options)
|
||||
const headers = await getHeaders(config, options)
|
||||
|
||||
if (!onCancel.isCancelled) {
|
||||
let response = await sendRequest<T>(
|
||||
config,
|
||||
options,
|
||||
url,
|
||||
body,
|
||||
formData,
|
||||
headers,
|
||||
onCancel,
|
||||
axiosClient,
|
||||
)
|
||||
|
||||
for (const fn of config.interceptors.response._fns) {
|
||||
response = await fn(response)
|
||||
}
|
||||
|
||||
const responseBody = getResponseBody(response)
|
||||
const responseHeader = getResponseHeader(
|
||||
response,
|
||||
options.responseHeader,
|
||||
)
|
||||
|
||||
let transformedBody = responseBody
|
||||
if (options.responseTransformer && isSuccess(response.status)) {
|
||||
transformedBody = await options.responseTransformer(responseBody)
|
||||
}
|
||||
|
||||
const result: ApiResult = {
|
||||
url,
|
||||
ok: isSuccess(response.status),
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: responseHeader ?? transformedBody,
|
||||
}
|
||||
|
||||
catchErrorCodes(options, result)
|
||||
|
||||
resolve(result.body)
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Example usage of the generated Ollama FastAPI client
|
||||
*/
|
||||
import {
|
||||
OpenAPI,
|
||||
ModelsService,
|
||||
DefaultService,
|
||||
AutomaticSpeechRecognitionService,
|
||||
SpeechToTextService
|
||||
} from './index';
|
||||
|
||||
// Configure the base URL for your Ollama API
|
||||
OpenAPI.BASE = 'http://localhost:11434'; // Default Ollama port
|
||||
|
||||
// Example: List available models
|
||||
async function listModels() {
|
||||
try {
|
||||
const response = await ModelsService.listLocalModelsV1ModelsGet({});
|
||||
console.log('Available models:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error listing models:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Example: Get a specific model
|
||||
async function getModel(modelId: string) {
|
||||
try {
|
||||
const response = await ModelsService.getLocalModelV1ModelsModelIdGet({
|
||||
path: { model_id: modelId }
|
||||
});
|
||||
console.log('Model details:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error getting model:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Example: Chat completion
|
||||
async function chatCompletion() {
|
||||
try {
|
||||
const response = await DefaultService.handleCompletionsV1ChatCompletionsPost({
|
||||
body: {
|
||||
model: 'llama2', // Replace with your model
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Hello, how are you?'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
console.log('Chat response:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error in chat completion:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Example: Transcribe audio
|
||||
async function transcribeAudio(audioFile: File) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', audioFile);
|
||||
formData.append('model', 'whisper-1');
|
||||
|
||||
const response = await AutomaticSpeechRecognitionService.transcribeFileV1AudioTranscriptionsPost({
|
||||
formData
|
||||
});
|
||||
console.log('Transcription:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error transcribing audio:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Example: Text-to-speech synthesis
|
||||
async function synthesizeSpeech(text: string, voice: string = 'default') {
|
||||
try {
|
||||
const response = await SpeechToTextService.synthesizeV1AudioSpeechPost({
|
||||
body: {
|
||||
model: 'tts-1',
|
||||
input: text,
|
||||
voice: voice
|
||||
}
|
||||
});
|
||||
console.log('Speech synthesis response:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error synthesizing speech:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Export the functions for use in your application
|
||||
export {
|
||||
listModels,
|
||||
getModel,
|
||||
chatCompletion,
|
||||
transcribeAudio,
|
||||
synthesizeSpeech
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
export { ApiError } from "./core/ApiError"
|
||||
export { CancelablePromise, CancelError } from "./core/CancelablePromise"
|
||||
export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI"
|
||||
export * from "./sdk.gen"
|
||||
export * from "./types.gen"
|
||||
@@ -0,0 +1,356 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type { CancelablePromise } from "./core/CancelablePromise"
|
||||
import { OpenAPI } from "./core/OpenAPI"
|
||||
import { request as __request } from "./core/request"
|
||||
import type {
|
||||
TranslateFileV1AudioTranslationsPostData,
|
||||
TranslateFileV1AudioTranslationsPostResponse,
|
||||
TranscribeFileV1AudioTranscriptionsPostData,
|
||||
TranscribeFileV1AudioTranscriptionsPostResponse,
|
||||
HandleCompletionsV1ChatCompletionsPostData,
|
||||
HandleCompletionsV1ChatCompletionsPostResponse,
|
||||
HealthHealthGetResponse,
|
||||
GetRunningModelsApiPsGetResponse,
|
||||
LoadModelRouteApiPsModelIdPostData,
|
||||
LoadModelRouteApiPsModelIdPostResponse,
|
||||
StopRunningModelApiPsModelIdDeleteData,
|
||||
StopRunningModelApiPsModelIdDeleteResponse,
|
||||
ListLocalModelsV1ModelsGetData,
|
||||
ListLocalModelsV1ModelsGetResponse,
|
||||
GetLocalModelV1ModelsModelIdGetData,
|
||||
GetLocalModelV1ModelsModelIdGetResponse,
|
||||
DownloadRemoteModelV1ModelsModelIdPostData,
|
||||
DownloadRemoteModelV1ModelsModelIdPostResponse,
|
||||
DeleteModelV1ModelsModelIdDeleteData,
|
||||
DeleteModelV1ModelsModelIdDeleteResponse,
|
||||
GetRemoteModelsV1RegistryGetData,
|
||||
GetRemoteModelsV1RegistryGetResponse,
|
||||
RealtimeWebrtcV1RealtimePostData,
|
||||
RealtimeWebrtcV1RealtimePostResponse,
|
||||
SynthesizeV1AudioSpeechPostData,
|
||||
SynthesizeV1AudioSpeechPostResponse,
|
||||
DetectSpeechTimestampsV1AudioSpeechTimestampsPostData,
|
||||
DetectSpeechTimestampsV1AudioSpeechTimestampsPostResponse,
|
||||
} from "./types.gen"
|
||||
|
||||
export class AutomaticSpeechRecognitionService {
|
||||
/**
|
||||
* Translate File
|
||||
* @param data The data for the request.
|
||||
* @param data.formData
|
||||
* @returns unknown Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static translateFileV1AudioTranslationsPost(
|
||||
data: TranslateFileV1AudioTranslationsPostData,
|
||||
): CancelablePromise<TranslateFileV1AudioTranslationsPostResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "POST",
|
||||
url: "/v1/audio/translations",
|
||||
formData: data.formData,
|
||||
mediaType: "application/x-www-form-urlencoded",
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Transcribe File
|
||||
* @param data The data for the request.
|
||||
* @param data.formData
|
||||
* @returns unknown Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static transcribeFileV1AudioTranscriptionsPost(
|
||||
data: TranscribeFileV1AudioTranscriptionsPostData,
|
||||
): CancelablePromise<TranscribeFileV1AudioTranscriptionsPostResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "POST",
|
||||
url: "/v1/audio/transcriptions",
|
||||
formData: data.formData,
|
||||
mediaType: "application/x-www-form-urlencoded",
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultService {
|
||||
/**
|
||||
* Handle Completions
|
||||
* @param data The data for the request.
|
||||
* @param data.requestBody
|
||||
* @returns unknown Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static handleCompletionsV1ChatCompletionsPost(
|
||||
data: HandleCompletionsV1ChatCompletionsPostData,
|
||||
): CancelablePromise<HandleCompletionsV1ChatCompletionsPostResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "POST",
|
||||
url: "/v1/chat/completions",
|
||||
body: data.requestBody,
|
||||
mediaType: "application/json",
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class DiagnosticService {
|
||||
/**
|
||||
* Health
|
||||
* @returns unknown Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static healthHealthGet(): CancelablePromise<HealthHealthGetResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/health",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class ExperimentalService {
|
||||
/**
|
||||
* Get a list of loaded models.
|
||||
* @returns string Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getRunningModelsApiPsGet(): CancelablePromise<GetRunningModelsApiPsGetResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/ps",
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a model into memory.
|
||||
* @param data The data for the request.
|
||||
* @param data.modelId
|
||||
* @returns unknown Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static loadModelRouteApiPsModelIdPost(
|
||||
data: LoadModelRouteApiPsModelIdPostData,
|
||||
): CancelablePromise<LoadModelRouteApiPsModelIdPostResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "POST",
|
||||
url: "/api/ps/{model_id}",
|
||||
path: {
|
||||
model_id: data.modelId,
|
||||
},
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload a model from memory.
|
||||
* @param data The data for the request.
|
||||
* @param data.modelId
|
||||
* @returns unknown Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static stopRunningModelApiPsModelIdDelete(
|
||||
data: StopRunningModelApiPsModelIdDeleteData,
|
||||
): CancelablePromise<StopRunningModelApiPsModelIdDeleteResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "DELETE",
|
||||
url: "/api/ps/{model_id}",
|
||||
path: {
|
||||
model_id: data.modelId,
|
||||
},
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class ModelsService {
|
||||
/**
|
||||
* List Local Models
|
||||
* @param data The data for the request.
|
||||
* @param data.task
|
||||
* @returns ListModelsResponse Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static listLocalModelsV1ModelsGet(
|
||||
data: ListLocalModelsV1ModelsGetData = {},
|
||||
): CancelablePromise<ListLocalModelsV1ModelsGetResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/v1/models",
|
||||
query: {
|
||||
task: data.task,
|
||||
},
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Local Model
|
||||
* @param data The data for the request.
|
||||
* @param data.modelId
|
||||
* @returns Model Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getLocalModelV1ModelsModelIdGet(
|
||||
data: GetLocalModelV1ModelsModelIdGetData,
|
||||
): CancelablePromise<GetLocalModelV1ModelsModelIdGetResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/v1/models/{model_id}",
|
||||
path: {
|
||||
model_id: data.modelId,
|
||||
},
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Download Remote Model
|
||||
* @param data The data for the request.
|
||||
* @param data.modelId
|
||||
* @returns unknown Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static downloadRemoteModelV1ModelsModelIdPost(
|
||||
data: DownloadRemoteModelV1ModelsModelIdPostData,
|
||||
): CancelablePromise<DownloadRemoteModelV1ModelsModelIdPostResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "POST",
|
||||
url: "/v1/models/{model_id}",
|
||||
path: {
|
||||
model_id: data.modelId,
|
||||
},
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Model
|
||||
* @param data The data for the request.
|
||||
* @param data.modelId
|
||||
* @returns unknown Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static deleteModelV1ModelsModelIdDelete(
|
||||
data: DeleteModelV1ModelsModelIdDeleteData,
|
||||
): CancelablePromise<DeleteModelV1ModelsModelIdDeleteResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "DELETE",
|
||||
url: "/v1/models/{model_id}",
|
||||
path: {
|
||||
model_id: data.modelId,
|
||||
},
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Remote Models
|
||||
* @param data The data for the request.
|
||||
* @param data.task
|
||||
* @returns ListModelsResponse Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getRemoteModelsV1RegistryGet(
|
||||
data: GetRemoteModelsV1RegistryGetData = {},
|
||||
): CancelablePromise<GetRemoteModelsV1RegistryGetResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/v1/registry",
|
||||
query: {
|
||||
task: data.task,
|
||||
},
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class RealtimeService {
|
||||
/**
|
||||
* Realtime Webrtc
|
||||
* @param data The data for the request.
|
||||
* @param data.model
|
||||
* @returns unknown Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static webrtcV1RealtimePost(
|
||||
data: RealtimeWebrtcV1RealtimePostData,
|
||||
): CancelablePromise<RealtimeWebrtcV1RealtimePostResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "POST",
|
||||
url: "/v1/realtime",
|
||||
query: {
|
||||
model: data.model,
|
||||
},
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class SpeechToTextService {
|
||||
/**
|
||||
* Synthesize
|
||||
* @param data The data for the request.
|
||||
* @param data.requestBody
|
||||
* @returns unknown Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static synthesizeV1AudioSpeechPost(
|
||||
data: SynthesizeV1AudioSpeechPostData,
|
||||
): CancelablePromise<SynthesizeV1AudioSpeechPostResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "POST",
|
||||
url: "/v1/audio/speech",
|
||||
body: data.requestBody,
|
||||
mediaType: "application/json",
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class VoiceActivityDetectionService {
|
||||
/**
|
||||
* Detect Speech Timestamps
|
||||
* @param data The data for the request.
|
||||
* @param data.formData
|
||||
* @returns SpeechTimestamp Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static detectSpeechTimestampsV1AudioSpeechTimestampsPost(
|
||||
data: DetectSpeechTimestampsV1AudioSpeechTimestampsPostData,
|
||||
): CancelablePromise<DetectSpeechTimestampsV1AudioSpeechTimestampsPostResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "POST",
|
||||
url: "/v1/audio/speech/timestamps",
|
||||
formData: data.formData,
|
||||
mediaType: "application/x-www-form-urlencoded",
|
||||
errors: {
|
||||
422: "Validation Error",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,702 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
export type Audio = {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type Body_detect_speech_timestamps_v1_audio_speech_timestamps_post = {
|
||||
model?: string
|
||||
/**
|
||||
* Speech threshold. Silero VAD outputs speech probabilities for each audio chunk, probabilities ABOVE this value are considered as SPEECH. It is better to tune this parameter for each dataset separately, but "lazy" 0.5 is pretty good for most datasets.
|
||||
*/
|
||||
threshold?: number
|
||||
/**
|
||||
* Silence threshold for determining the end of speech. If a probability is lower
|
||||
* than neg_threshold, it is always considered silence. Values higher than neg_threshold
|
||||
* are only considered speech if the previous sample was classified as speech; otherwise,
|
||||
* they are treated as silence. This parameter helps refine the detection of speech
|
||||
* transitions, ensuring smoother segment boundaries.
|
||||
*/
|
||||
neg_threshold?: number | null
|
||||
/**
|
||||
* Final speech chunks shorter min_speech_duration_ms are thrown out.
|
||||
*/
|
||||
min_speech_duration_ms?: number
|
||||
/**
|
||||
* Maximum duration of speech chunks in seconds. Chunks longer
|
||||
* than max_speech_duration_s will be split at the timestamp of the last silence that
|
||||
* lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, they will be
|
||||
* split aggressively just before max_speech_duration_s.
|
||||
*/
|
||||
max_speech_duration_s?: number
|
||||
/**
|
||||
* In the end of each speech chunk wait for min_silence_duration_ms
|
||||
* before separating it
|
||||
*/
|
||||
min_silence_duration_ms?: number
|
||||
/**
|
||||
* Final speech chunks are padded by speech_pad_ms each side
|
||||
*/
|
||||
speech_pad_ms?: number
|
||||
file: Blob | File
|
||||
}
|
||||
|
||||
export type Body_transcribe_file_v1_audio_transcriptions_post = {
|
||||
model: string
|
||||
language?: string | null
|
||||
prompt?: string | null
|
||||
response_format?: speaches__routers__stt__ResponseFormat
|
||||
temperature?: number
|
||||
timestamp_granularities?: Array<"segment" | "word">
|
||||
stream?: boolean
|
||||
hotwords?: string | null
|
||||
vad_filter?: boolean
|
||||
file: Blob | File
|
||||
}
|
||||
|
||||
export type Body_translate_file_v1_audio_translations_post = {
|
||||
model: string
|
||||
prompt?: string | null
|
||||
response_format?: speaches__routers__stt__ResponseFormat
|
||||
temperature?: number
|
||||
stream?: boolean
|
||||
vad_filter?: boolean
|
||||
file: Blob | File
|
||||
}
|
||||
|
||||
export type ChatCompletion = {
|
||||
id: string
|
||||
choices: Array<openai__types__chat__chat_completion__Choice>
|
||||
created: number
|
||||
model: string
|
||||
object: "chat.completion"
|
||||
service_tier?: "scale" | "default" | null
|
||||
system_fingerprint?: string | null
|
||||
usage?: CompletionUsage | null
|
||||
[key: string]:
|
||||
| unknown
|
||||
| string
|
||||
| openai__types__chat__chat_completion__Choice
|
||||
| number
|
||||
| "chat.completion"
|
||||
}
|
||||
|
||||
export type ChatCompletionAssistantMessageParam = {
|
||||
role: "assistant"
|
||||
audio?: Audio | null
|
||||
content?:
|
||||
| string
|
||||
| Array<
|
||||
| ChatCompletionContentPartTextParam
|
||||
| ChatCompletionContentPartRefusalParam
|
||||
>
|
||||
| null
|
||||
function_call?: FunctionCall_Input | null
|
||||
name?: string | null
|
||||
refusal?: string | null
|
||||
tool_calls?: Array<ChatCompletionMessageToolCallParam> | null
|
||||
}
|
||||
|
||||
export type ChatCompletionAudio = {
|
||||
id: string
|
||||
data: string
|
||||
expires_at: number
|
||||
transcript: string
|
||||
[key: string]: unknown | string | number
|
||||
}
|
||||
|
||||
export type ChatCompletionAudioParam = {
|
||||
format: "wav" | "mp3" | "flac" | "opus" | "pcm16"
|
||||
voice: string
|
||||
}
|
||||
|
||||
export type format = "wav" | "mp3" | "flac" | "opus" | "pcm16"
|
||||
|
||||
export type ChatCompletionChunk = {
|
||||
id: string
|
||||
choices: Array<openai__types__chat__chat_completion_chunk__Choice>
|
||||
created: number
|
||||
model: string
|
||||
object: "chat.completion.chunk"
|
||||
service_tier?: "scale" | "default" | null
|
||||
system_fingerprint?: string | null
|
||||
usage?: CompletionUsage | null
|
||||
[key: string]:
|
||||
| unknown
|
||||
| string
|
||||
| openai__types__chat__chat_completion_chunk__Choice
|
||||
| number
|
||||
| "chat.completion.chunk"
|
||||
}
|
||||
|
||||
export type ChatCompletionContentPartImageParam = {
|
||||
image_url: ImageURL
|
||||
type: "image_url"
|
||||
}
|
||||
|
||||
export type ChatCompletionContentPartInputAudioParam = {
|
||||
input_audio: InputAudio
|
||||
type: "input_audio"
|
||||
}
|
||||
|
||||
export type ChatCompletionContentPartRefusalParam = {
|
||||
refusal: string
|
||||
type: "refusal"
|
||||
}
|
||||
|
||||
export type ChatCompletionContentPartTextParam = {
|
||||
text: string
|
||||
type: "text"
|
||||
}
|
||||
|
||||
export type ChatCompletionDeveloperMessageParam = {
|
||||
content: string | Array<ChatCompletionContentPartTextParam>
|
||||
role: "developer"
|
||||
name?: string | null
|
||||
}
|
||||
|
||||
export type ChatCompletionFunctionCallOptionParam = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export type ChatCompletionFunctionMessageParam = {
|
||||
content: string | null
|
||||
name: string
|
||||
role: "function"
|
||||
}
|
||||
|
||||
export type ChatCompletionMessage = {
|
||||
content?: string | null
|
||||
refusal?: string | null
|
||||
role: "assistant"
|
||||
audio?: ChatCompletionAudio | null
|
||||
function_call?: FunctionCall_Output | null
|
||||
tool_calls?: Array<ChatCompletionMessageToolCall> | null
|
||||
[key: string]: unknown | "assistant"
|
||||
}
|
||||
|
||||
export type ChatCompletionMessageToolCall = {
|
||||
id: string
|
||||
function: Function
|
||||
type: "function"
|
||||
[key: string]: unknown | string | Function | "function"
|
||||
}
|
||||
|
||||
export type ChatCompletionMessageToolCallParam = {
|
||||
id: string
|
||||
function: OpenaiTypesChatChatCompletionMessageToolCallParamFunction
|
||||
type: "function"
|
||||
}
|
||||
|
||||
export type ChatCompletionNamedToolChoiceParam = {
|
||||
function: OpenaiTypesChatChatCompletionNamedToolChoiceParamFunction
|
||||
type: "function"
|
||||
}
|
||||
|
||||
export type ChatCompletionPredictionContentParam = {
|
||||
content: string | Array<ChatCompletionContentPartTextParam>
|
||||
type: "content"
|
||||
}
|
||||
|
||||
export type ChatCompletionStreamOptionsParam = {
|
||||
include_usage?: boolean | null
|
||||
}
|
||||
|
||||
export type ChatCompletionSystemMessageParam = {
|
||||
content: string | Array<ChatCompletionContentPartTextParam>
|
||||
role: "system"
|
||||
name?: string | null
|
||||
}
|
||||
|
||||
export type ChatCompletionTokenLogprob = {
|
||||
token: string
|
||||
bytes?: Array<number> | null
|
||||
logprob: number
|
||||
top_logprobs: Array<TopLogprob>
|
||||
[key: string]: unknown | string | number | TopLogprob
|
||||
}
|
||||
|
||||
export type ChatCompletionToolMessageParam = {
|
||||
content: string | Array<ChatCompletionContentPartTextParam>
|
||||
role: "tool"
|
||||
tool_call_id: string
|
||||
}
|
||||
|
||||
export type ChatCompletionToolParam = {
|
||||
function: FunctionDefinition
|
||||
type: "function"
|
||||
}
|
||||
|
||||
export type ChatCompletionUserMessageParam = {
|
||||
content:
|
||||
| string
|
||||
| Array<
|
||||
| ChatCompletionContentPartTextParam
|
||||
| ChatCompletionContentPartImageParam
|
||||
| ChatCompletionContentPartInputAudioParam
|
||||
>
|
||||
role: "user"
|
||||
name?: string | null
|
||||
}
|
||||
|
||||
export type ChoiceDelta = {
|
||||
content?: string | null
|
||||
function_call?: ChoiceDeltaFunctionCall | null
|
||||
refusal?: string | null
|
||||
role?: "developer" | "system" | "user" | "assistant" | "tool" | null
|
||||
tool_calls?: Array<ChoiceDeltaToolCall> | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ChoiceDeltaFunctionCall = {
|
||||
arguments?: string | null
|
||||
name?: string | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ChoiceDeltaToolCall = {
|
||||
index: number
|
||||
id?: string | null
|
||||
function?: ChoiceDeltaToolCallFunction | null
|
||||
type?: "function" | null
|
||||
[key: string]: unknown | number
|
||||
}
|
||||
|
||||
export type ChoiceDeltaToolCallFunction = {
|
||||
arguments?: string | null
|
||||
name?: string | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ChoiceLogprobs = {
|
||||
content?: Array<ChatCompletionTokenLogprob> | null
|
||||
refusal?: Array<ChatCompletionTokenLogprob> | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type CompletionCreateParamsBase = {
|
||||
messages: Array<
|
||||
| ChatCompletionDeveloperMessageParam
|
||||
| ChatCompletionSystemMessageParam
|
||||
| ChatCompletionUserMessageParam
|
||||
| ChatCompletionAssistantMessageParam
|
||||
| ChatCompletionToolMessageParam
|
||||
| ChatCompletionFunctionMessageParam
|
||||
>
|
||||
model:
|
||||
| string
|
||||
| "o1"
|
||||
| "o1-2024-12-17"
|
||||
| "o1-preview"
|
||||
| "o1-preview-2024-09-12"
|
||||
| "o1-mini"
|
||||
| "o1-mini-2024-09-12"
|
||||
| "gpt-4o"
|
||||
| "gpt-4o-2024-11-20"
|
||||
| "gpt-4o-2024-08-06"
|
||||
| "gpt-4o-2024-05-13"
|
||||
| "gpt-4o-audio-preview"
|
||||
| "gpt-4o-audio-preview-2024-10-01"
|
||||
| "gpt-4o-audio-preview-2024-12-17"
|
||||
| "gpt-4o-mini-audio-preview"
|
||||
| "gpt-4o-mini-audio-preview-2024-12-17"
|
||||
| "chatgpt-4o-latest"
|
||||
| "gpt-4o-mini"
|
||||
| "gpt-4o-mini-2024-07-18"
|
||||
| "gpt-4-turbo"
|
||||
| "gpt-4-turbo-2024-04-09"
|
||||
| "gpt-4-0125-preview"
|
||||
| "gpt-4-turbo-preview"
|
||||
| "gpt-4-1106-preview"
|
||||
| "gpt-4-vision-preview"
|
||||
| "gpt-4"
|
||||
| "gpt-4-0314"
|
||||
| "gpt-4-0613"
|
||||
| "gpt-4-32k"
|
||||
| "gpt-4-32k-0314"
|
||||
| "gpt-4-32k-0613"
|
||||
| "gpt-3.5-turbo"
|
||||
| "gpt-3.5-turbo-16k"
|
||||
| "gpt-3.5-turbo-0301"
|
||||
| "gpt-3.5-turbo-0613"
|
||||
| "gpt-3.5-turbo-1106"
|
||||
| "gpt-3.5-turbo-0125"
|
||||
| "gpt-3.5-turbo-16k-0613"
|
||||
audio?: ChatCompletionAudioParam | null
|
||||
frequency_penalty?: number | null
|
||||
function_call?: "none" | "auto" | ChatCompletionFunctionCallOptionParam | null
|
||||
functions?: Array<OpenaiTypesChatCompletionCreateParamsFunction> | null
|
||||
logit_bias?: {
|
||||
[key: string]: number
|
||||
} | null
|
||||
logprobs?: boolean | null
|
||||
max_completion_tokens?: number | null
|
||||
max_tokens?: number | null
|
||||
metadata?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
modalities?: Array<"text" | "audio"> | null
|
||||
n?: number | null
|
||||
parallel_tool_calls?: boolean | null
|
||||
prediction?: ChatCompletionPredictionContentParam | null
|
||||
presence_penalty?: number | null
|
||||
reasoning_effort?: "low" | "medium" | "high" | null
|
||||
response_format?:
|
||||
| ResponseFormatText
|
||||
| ResponseFormatJSONObject
|
||||
| ResponseFormatJSONSchema
|
||||
| null
|
||||
seed?: number | null
|
||||
service_tier?: "auto" | "default" | null
|
||||
stop?: string | Array<string> | null
|
||||
store?: boolean | null
|
||||
stream_options?: ChatCompletionStreamOptionsParam | null
|
||||
temperature?: number | null
|
||||
tool_choice?:
|
||||
| "none"
|
||||
| "auto"
|
||||
| "required"
|
||||
| ChatCompletionNamedToolChoiceParam
|
||||
| null
|
||||
tools?: Array<ChatCompletionToolParam> | null
|
||||
top_logprobs?: number | null
|
||||
top_p?: number | null
|
||||
user?: string | null
|
||||
stream?: boolean
|
||||
trancription_model?: string
|
||||
transcription_extra_body?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
speech_model?: string
|
||||
speech_extra_body?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
}
|
||||
|
||||
export type CompletionTokensDetails = {
|
||||
accepted_prediction_tokens?: number | null
|
||||
audio_tokens?: number | null
|
||||
reasoning_tokens?: number | null
|
||||
rejected_prediction_tokens?: number | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type CompletionUsage = {
|
||||
completion_tokens: number
|
||||
prompt_tokens: number
|
||||
total_tokens: number
|
||||
completion_tokens_details?: CompletionTokensDetails | null
|
||||
prompt_tokens_details?: PromptTokensDetails | null
|
||||
[key: string]: unknown | number
|
||||
}
|
||||
|
||||
export type CreateSpeechRequestBody = {
|
||||
/**
|
||||
* The ID of the model. You can get a list of available models by calling `/v1/models`.
|
||||
*/
|
||||
model: string
|
||||
input: string
|
||||
voice: string
|
||||
response_format?: speaches__routers__speech__ResponseFormat
|
||||
speed?: number
|
||||
sample_rate?: number | null
|
||||
}
|
||||
|
||||
export type CreateTranscriptionResponseJson = {
|
||||
text: string
|
||||
}
|
||||
|
||||
export type CreateTranscriptionResponseVerboseJson = {
|
||||
task?: string
|
||||
language: string
|
||||
duration: number
|
||||
text: string
|
||||
words: Array<TranscriptionWord> | null
|
||||
segments: Array<TranscriptionSegment>
|
||||
}
|
||||
|
||||
export type Function = {
|
||||
arguments: string
|
||||
name: string
|
||||
[key: string]: unknown | string
|
||||
}
|
||||
|
||||
export type FunctionCall_Input = {
|
||||
arguments: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type FunctionCall_Output = {
|
||||
arguments: string
|
||||
name: string
|
||||
[key: string]: unknown | string
|
||||
}
|
||||
|
||||
export type FunctionDefinition = {
|
||||
name: string
|
||||
description?: string | null
|
||||
parameters?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
strict?: boolean | null
|
||||
}
|
||||
|
||||
export type HTTPValidationError = {
|
||||
detail?: Array<ValidationError>
|
||||
}
|
||||
|
||||
export type ImageURL = {
|
||||
url: string
|
||||
detail?: "auto" | "low" | "high" | null
|
||||
}
|
||||
|
||||
export type InputAudio = {
|
||||
data: string
|
||||
format: "wav" | "mp3"
|
||||
}
|
||||
|
||||
export type format2 = "wav" | "mp3"
|
||||
|
||||
export type JSONSchema = {
|
||||
name: string
|
||||
description?: string | null
|
||||
schema?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
strict?: boolean | null
|
||||
}
|
||||
|
||||
export type ListModelsResponse = {
|
||||
data: Array<Model>
|
||||
object?: "list"
|
||||
}
|
||||
|
||||
/**
|
||||
* There may be additional fields in the response that are specific to the model type.
|
||||
*/
|
||||
export type Model = {
|
||||
id: string
|
||||
created?: number
|
||||
object?: "model"
|
||||
owned_by: string
|
||||
language?: Array<string> | null
|
||||
task: "automatic-speech-recognition" | "text-to-speech"
|
||||
[key: string]: unknown | string | number | "model"
|
||||
}
|
||||
|
||||
export type task = "automatic-speech-recognition" | "text-to-speech"
|
||||
|
||||
export type openai__types__chat__chat_completion__Choice = {
|
||||
finish_reason:
|
||||
| "stop"
|
||||
| "length"
|
||||
| "tool_calls"
|
||||
| "content_filter"
|
||||
| "function_call"
|
||||
index: number
|
||||
logprobs?: ChoiceLogprobs | null
|
||||
message: ChatCompletionMessage
|
||||
[key: string]: unknown | string | number | ChatCompletionMessage
|
||||
}
|
||||
|
||||
export type finish_reason =
|
||||
| "stop"
|
||||
| "length"
|
||||
| "tool_calls"
|
||||
| "content_filter"
|
||||
| "function_call"
|
||||
|
||||
export type openai__types__chat__chat_completion_chunk__Choice = {
|
||||
delta: ChoiceDelta
|
||||
finish_reason?:
|
||||
| "stop"
|
||||
| "length"
|
||||
| "tool_calls"
|
||||
| "content_filter"
|
||||
| "function_call"
|
||||
| null
|
||||
index: number
|
||||
logprobs?: ChoiceLogprobs | null
|
||||
[key: string]: unknown | ChoiceDelta | number
|
||||
}
|
||||
|
||||
export type OpenaiTypesChatChatCompletionMessageToolCallParamFunction = {
|
||||
arguments: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type OpenaiTypesChatChatCompletionNamedToolChoiceParamFunction = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export type OpenaiTypesChatCompletionCreateParamsFunction = {
|
||||
name: string
|
||||
description?: string | null
|
||||
parameters?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
}
|
||||
|
||||
export type PromptTokensDetails = {
|
||||
audio_tokens?: number | null
|
||||
cached_tokens?: number | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ResponseFormatJSONObject = {
|
||||
type: "json_object"
|
||||
}
|
||||
|
||||
export type ResponseFormatJSONSchema = {
|
||||
json_schema: JSONSchema
|
||||
type: "json_schema"
|
||||
}
|
||||
|
||||
export type ResponseFormatText = {
|
||||
type: "text"
|
||||
}
|
||||
|
||||
export type speaches__routers__speech__ResponseFormat =
|
||||
| "mp3"
|
||||
| "flac"
|
||||
| "wav"
|
||||
| "pcm"
|
||||
|
||||
export type speaches__routers__stt__ResponseFormat =
|
||||
| "text"
|
||||
| "json"
|
||||
| "verbose_json"
|
||||
| "srt"
|
||||
| "vtt"
|
||||
|
||||
export type SpeechTimestamp = {
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
export type TopLogprob = {
|
||||
token: string
|
||||
bytes?: Array<number> | null
|
||||
logprob: number
|
||||
[key: string]: unknown | string | number
|
||||
}
|
||||
|
||||
export type TranscriptionSegment = {
|
||||
id: number
|
||||
seek: number
|
||||
start: number
|
||||
end: number
|
||||
text: string
|
||||
tokens: Array<number>
|
||||
temperature: number
|
||||
avg_logprob: number
|
||||
compression_ratio: number
|
||||
no_speech_prob: number
|
||||
words: Array<TranscriptionWord> | null
|
||||
}
|
||||
|
||||
export type TranscriptionWord = {
|
||||
start: number
|
||||
end: number
|
||||
word: string
|
||||
probability: number
|
||||
}
|
||||
|
||||
export type ValidationError = {
|
||||
loc: Array<string | number>
|
||||
msg: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export type TranslateFileV1AudioTranslationsPostData = {
|
||||
formData: Body_translate_file_v1_audio_translations_post
|
||||
}
|
||||
|
||||
export type TranslateFileV1AudioTranslationsPostResponse =
|
||||
| string
|
||||
| CreateTranscriptionResponseJson
|
||||
| CreateTranscriptionResponseVerboseJson
|
||||
|
||||
export type TranscribeFileV1AudioTranscriptionsPostData = {
|
||||
formData: Body_transcribe_file_v1_audio_transcriptions_post
|
||||
}
|
||||
|
||||
export type TranscribeFileV1AudioTranscriptionsPostResponse =
|
||||
| string
|
||||
| CreateTranscriptionResponseJson
|
||||
| CreateTranscriptionResponseVerboseJson
|
||||
|
||||
export type HandleCompletionsV1ChatCompletionsPostData = {
|
||||
requestBody: CompletionCreateParamsBase
|
||||
}
|
||||
|
||||
export type HandleCompletionsV1ChatCompletionsPostResponse =
|
||||
| ChatCompletion
|
||||
| ChatCompletionChunk
|
||||
|
||||
export type HealthHealthGetResponse = unknown
|
||||
|
||||
export type GetRunningModelsApiPsGetResponse = {
|
||||
[key: string]: Array<string>
|
||||
}
|
||||
|
||||
export type LoadModelRouteApiPsModelIdPostData = {
|
||||
modelId: string
|
||||
}
|
||||
|
||||
export type LoadModelRouteApiPsModelIdPostResponse = unknown
|
||||
|
||||
export type StopRunningModelApiPsModelIdDeleteData = {
|
||||
modelId: string
|
||||
}
|
||||
|
||||
export type StopRunningModelApiPsModelIdDeleteResponse = unknown
|
||||
|
||||
export type ListLocalModelsV1ModelsGetData = {
|
||||
task?: "automatic-speech-recognition" | "text-to-speech" | null
|
||||
}
|
||||
|
||||
export type ListLocalModelsV1ModelsGetResponse = ListModelsResponse
|
||||
|
||||
export type GetLocalModelV1ModelsModelIdGetData = {
|
||||
modelId: string
|
||||
}
|
||||
|
||||
export type GetLocalModelV1ModelsModelIdGetResponse = Model
|
||||
|
||||
export type DownloadRemoteModelV1ModelsModelIdPostData = {
|
||||
modelId: string
|
||||
}
|
||||
|
||||
export type DownloadRemoteModelV1ModelsModelIdPostResponse = unknown
|
||||
|
||||
export type DeleteModelV1ModelsModelIdDeleteData = {
|
||||
modelId: string
|
||||
}
|
||||
|
||||
export type DeleteModelV1ModelsModelIdDeleteResponse = unknown
|
||||
|
||||
export type GetRemoteModelsV1RegistryGetData = {
|
||||
task?: "automatic-speech-recognition" | "text-to-speech" | null
|
||||
}
|
||||
|
||||
export type GetRemoteModelsV1RegistryGetResponse = ListModelsResponse
|
||||
|
||||
export type RealtimeWebrtcV1RealtimePostData = {
|
||||
model: string
|
||||
}
|
||||
|
||||
export type RealtimeWebrtcV1RealtimePostResponse = unknown
|
||||
|
||||
export type SynthesizeV1AudioSpeechPostData = {
|
||||
requestBody: CreateSpeechRequestBody
|
||||
}
|
||||
|
||||
export type SynthesizeV1AudioSpeechPostResponse = unknown
|
||||
|
||||
export type DetectSpeechTimestampsV1AudioSpeechTimestampsPostData = {
|
||||
formData: Body_detect_speech_timestamps_v1_audio_speech_timestamps_post
|
||||
}
|
||||
|
||||
export type DetectSpeechTimestampsV1AudioSpeechTimestampsPostResponse =
|
||||
Array<SpeechTimestamp>
|
||||
Executable
+142
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Simple script to generate TypeScript client using the project's existing tools
|
||||
# This uses @hey-api/openapi-ts which is already configured in the project
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Get script directory and project root
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
OLLAMA_OPENAPI_PATH="$PROJECT_ROOT/ai_stack/ollama/openapi.json"
|
||||
OUTPUT_DIR="$PROJECT_ROOT/frontend/src/ollama-client"
|
||||
|
||||
echo -e "${GREEN}🚀 Generating Ollama TypeScript Client${NC}"
|
||||
|
||||
# Verify the OpenAPI file exists
|
||||
if [ ! -f "$OLLAMA_OPENAPI_PATH" ]; then
|
||||
echo -e "${RED}❌ Error: OpenAPI file not found at $OLLAMA_OPENAPI_PATH${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create output directory
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Change to frontend directory
|
||||
cd "$PROJECT_ROOT/frontend"
|
||||
|
||||
echo -e "${GREEN}📋 Using OpenAPI spec: $OLLAMA_OPENAPI_PATH${NC}"
|
||||
echo -e "${GREEN}📁 Output directory: $OUTPUT_DIR${NC}"
|
||||
|
||||
# Generate client using the project's existing openapi-ts setup
|
||||
echo -e "${GREEN}⚙️ Generating TypeScript client...${NC}"
|
||||
|
||||
npx @hey-api/openapi-ts \
|
||||
--input "$OLLAMA_OPENAPI_PATH" \
|
||||
--output "$OUTPUT_DIR" \
|
||||
--client "legacy/axios"
|
||||
|
||||
# Format the generated code with biome
|
||||
if [ -f "biome.json" ]; then
|
||||
echo -e "${GREEN}🎨 Formatting generated code...${NC}"
|
||||
npx biome format --write "$OUTPUT_DIR"
|
||||
fi
|
||||
|
||||
# Create a simple usage example
|
||||
EXAMPLE_FILE="$OUTPUT_DIR/example.ts"
|
||||
cat > "$EXAMPLE_FILE" << 'EOF'
|
||||
/**
|
||||
* Example usage of the generated Ollama FastAPI client
|
||||
*/
|
||||
import { client, OllamaService } from './index';
|
||||
|
||||
// Configure the base URL for your Ollama API
|
||||
client.setConfig({
|
||||
baseUrl: 'http://localhost:11434', // Default Ollama port
|
||||
});
|
||||
|
||||
// Example: List available models
|
||||
async function listModels() {
|
||||
try {
|
||||
const response = await OllamaService.listLocalModelsV1ModelsGet();
|
||||
console.log('Available models:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error listing models:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Example: Get a specific model
|
||||
async function getModel(modelId: string) {
|
||||
try {
|
||||
const response = await OllamaService.getLocalModelV1ModelsModelIdGet({
|
||||
path: { model_id: modelId }
|
||||
});
|
||||
console.log('Model details:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error getting model:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Example: Chat completion
|
||||
async function chatCompletion() {
|
||||
try {
|
||||
const response = await OllamaService.handleCompletionsV1ChatCompletionsPost({
|
||||
body: {
|
||||
model: 'llama2', // Replace with your model
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Hello, how are you?'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
console.log('Chat response:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error in chat completion:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Example: Transcribe audio
|
||||
async function transcribeAudio(audioFile: File) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', audioFile);
|
||||
formData.append('model', 'whisper-1');
|
||||
|
||||
const response = await OllamaService.transcribeFileV1AudioTranscriptionsPost({
|
||||
body: formData
|
||||
});
|
||||
console.log('Transcription:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error transcribing audio:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Export the functions for use in your application
|
||||
export {
|
||||
listModels,
|
||||
getModel,
|
||||
chatCompletion,
|
||||
transcribeAudio
|
||||
};
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✅ Ollama TypeScript client generated successfully!${NC}"
|
||||
echo -e "${GREEN}📍 Client available at: $OUTPUT_DIR${NC}"
|
||||
echo -e "${GREEN}📝 Usage example: $EXAMPLE_FILE${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}💡 Quick start:${NC}"
|
||||
echo -e "${YELLOW}import { listModels, chatCompletion } from './ollama-client/example';${NC}"
|
||||
echo -e "${YELLOW}const models = await listModels();${NC}"
|
||||
echo -e "${YELLOW}const response = await chatCompletion();${NC}"
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# Script to generate FastAPI client from Ollama OpenAPI specification
|
||||
# This generates a TypeScript client for the Ollama API endpoints
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}🚀 Generating Ollama FastAPI Client${NC}"
|
||||
|
||||
# Check if openapi-ts is available
|
||||
if ! command -v openapi-ts &> /dev/null; then
|
||||
echo -e "${YELLOW}⚠️ openapi-ts not found globally, using npx...${NC}"
|
||||
OPENAPI_CMD="npx @hey-api/openapi-ts"
|
||||
else
|
||||
OPENAPI_CMD="openapi-ts"
|
||||
fi
|
||||
|
||||
# Set paths
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
OLLAMA_OPENAPI_PATH="$PROJECT_ROOT/ai_stack/ollama/openapi.json"
|
||||
OUTPUT_DIR="$PROJECT_ROOT/frontend/src/ollama-client"
|
||||
|
||||
# Verify the OpenAPI file exists
|
||||
if [ ! -f "$OLLAMA_OPENAPI_PATH" ]; then
|
||||
echo -e "${RED}❌ Error: OpenAPI file not found at $OLLAMA_OPENAPI_PATH${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}📋 Using OpenAPI spec: $OLLAMA_OPENAPI_PATH${NC}"
|
||||
echo -e "${GREEN}📁 Output directory: $OUTPUT_DIR${NC}"
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Generate the client
|
||||
echo -e "${GREEN}⚙️ Generating TypeScript client...${NC}"
|
||||
|
||||
cd "$PROJECT_ROOT/frontend"
|
||||
|
||||
# Generate client using openapi-ts
|
||||
$OPENAPI_CMD \
|
||||
--input "$OLLAMA_OPENAPI_PATH" \
|
||||
--output "$OUTPUT_DIR" \
|
||||
--client "axios" \
|
||||
--useOptions
|
||||
|
||||
# Format the generated code
|
||||
if command -v biome &> /dev/null; then
|
||||
echo -e "${GREEN}🎨 Formatting generated code...${NC}"
|
||||
npx biome format --write "$OUTPUT_DIR"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Biome not available, skipping formatting${NC}"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Ollama FastAPI client generated successfully!${NC}"
|
||||
echo -e "${GREEN}📍 Client available at: $OUTPUT_DIR${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}💡 Usage example:${NC}"
|
||||
echo -e "${YELLOW}import { DefaultApi } from './ollama-client';${NC}"
|
||||
echo -e "${YELLOW}const api = new DefaultApi();${NC}"
|
||||
echo -e "${YELLOW}const models = await api.listLocalModelsV1ModelsGet();${NC}"
|
||||
Executable
+258
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to generate FastAPI clients from OpenAPI specifications.
|
||||
Supports multiple output formats including Python, TypeScript, and more.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
class ClientGenerator:
|
||||
"""Generator for FastAPI clients from OpenAPI specifications."""
|
||||
|
||||
SUPPORTED_GENERATORS = {
|
||||
'python': 'python',
|
||||
'typescript': 'typescript-axios',
|
||||
'typescript-fetch': 'typescript-fetch',
|
||||
'javascript': 'javascript',
|
||||
'java': 'java',
|
||||
'go': 'go',
|
||||
'rust': 'rust',
|
||||
'php': 'php',
|
||||
'csharp': 'csharp'
|
||||
}
|
||||
|
||||
def __init__(self, openapi_path: str, output_dir: str, generator: str = 'python'):
|
||||
self.openapi_path = Path(openapi_path)
|
||||
self.output_dir = Path(output_dir)
|
||||
self.generator = generator
|
||||
|
||||
if not self.openapi_path.exists():
|
||||
raise FileNotFoundError(f"OpenAPI file not found: {openapi_path}")
|
||||
|
||||
if generator not in self.SUPPORTED_GENERATORS:
|
||||
raise ValueError(f"Unsupported generator: {generator}. "
|
||||
f"Supported: {list(self.SUPPORTED_GENERATORS.keys())}")
|
||||
|
||||
def _check_dependencies(self) -> bool:
|
||||
"""Check if required dependencies are available."""
|
||||
try:
|
||||
result = subprocess.run(['openapi-generator-cli', 'version'],
|
||||
capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
|
||||
def _install_openapi_generator(self) -> bool:
|
||||
"""Install OpenAPI Generator CLI via npm."""
|
||||
try:
|
||||
print("📦 Installing OpenAPI Generator CLI...")
|
||||
subprocess.run(['npm', 'install', '-g', '@openapitools/openapi-generator-cli'],
|
||||
check=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
print("❌ Failed to install OpenAPI Generator CLI")
|
||||
return False
|
||||
|
||||
def _validate_openapi_spec(self) -> Dict:
|
||||
"""Validate and load the OpenAPI specification."""
|
||||
try:
|
||||
with open(self.openapi_path, 'r') as f:
|
||||
spec = json.load(f)
|
||||
|
||||
# Basic validation
|
||||
if 'openapi' not in spec and 'swagger' not in spec:
|
||||
raise ValueError("Invalid OpenAPI specification")
|
||||
|
||||
return spec
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Invalid JSON in OpenAPI file: {e}")
|
||||
|
||||
def generate_client(self, package_name: Optional[str] = None) -> bool:
|
||||
"""Generate the client code."""
|
||||
print(f"🚀 Generating {self.generator} client from {self.openapi_path}")
|
||||
|
||||
# Validate OpenAPI spec
|
||||
spec = self._validate_openapi_spec()
|
||||
print(f"✅ Valid OpenAPI {spec.get('openapi', spec.get('swagger', 'unknown'))} specification")
|
||||
|
||||
# Check dependencies
|
||||
if not self._check_dependencies():
|
||||
print("⚠️ OpenAPI Generator CLI not found, attempting to install...")
|
||||
if not self._install_openapi_generator():
|
||||
print("❌ Failed to install OpenAPI Generator CLI")
|
||||
print("💡 Please install manually: npm install -g @openapitools/openapi-generator-cli")
|
||||
return False
|
||||
|
||||
# Create output directory
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Prepare generator command
|
||||
generator_name = self.SUPPORTED_GENERATORS[self.generator]
|
||||
cmd = [
|
||||
'openapi-generator-cli', 'generate',
|
||||
'-i', str(self.openapi_path),
|
||||
'-g', generator_name,
|
||||
'-o', str(self.output_dir),
|
||||
]
|
||||
|
||||
# Add additional options based on generator type
|
||||
if self.generator == 'python':
|
||||
cmd.extend([
|
||||
'--additional-properties',
|
||||
f'packageName={package_name or "ollama_client"}',
|
||||
'--additional-properties',
|
||||
'projectName=ollama-fastapi-client',
|
||||
'--additional-properties',
|
||||
'packageVersion=1.0.0'
|
||||
])
|
||||
elif self.generator in ['typescript', 'typescript-fetch']:
|
||||
cmd.extend([
|
||||
'--additional-properties',
|
||||
'npmName=ollama-fastapi-client',
|
||||
'--additional-properties',
|
||||
'npmVersion=1.0.0'
|
||||
])
|
||||
|
||||
try:
|
||||
print(f"🔧 Running: {' '.join(cmd)}")
|
||||
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
||||
print("✅ Client generated successfully!")
|
||||
|
||||
# Post-processing based on generator type
|
||||
self._post_process()
|
||||
|
||||
return True
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"❌ Error generating client: {e}")
|
||||
if e.stdout:
|
||||
print(f"stdout: {e.stdout}")
|
||||
if e.stderr:
|
||||
print(f"stderr: {e.stderr}")
|
||||
return False
|
||||
|
||||
def _post_process(self):
|
||||
"""Post-process the generated client."""
|
||||
if self.generator == 'python':
|
||||
self._post_process_python()
|
||||
elif self.generator in ['typescript', 'typescript-fetch']:
|
||||
self._post_process_typescript()
|
||||
|
||||
def _post_process_python(self):
|
||||
"""Post-process Python client."""
|
||||
# Create a simple usage example
|
||||
example_file = self.output_dir / 'example_usage.py'
|
||||
example_content = '''#!/usr/bin/env python3
|
||||
"""
|
||||
Example usage of the generated Ollama FastAPI client.
|
||||
"""
|
||||
|
||||
from ollama_client import ApiClient, Configuration
|
||||
from ollama_client.api.default_api import DefaultApi
|
||||
|
||||
# Configure the client
|
||||
configuration = Configuration(
|
||||
host="http://localhost:8000" # Adjust to your Ollama API endpoint
|
||||
)
|
||||
|
||||
# Create API client
|
||||
with ApiClient(configuration) as api_client:
|
||||
api = DefaultApi(api_client)
|
||||
|
||||
try:
|
||||
# List available models
|
||||
models = api.list_local_models_v1_models_get()
|
||||
print("Available models:")
|
||||
for model in models.data:
|
||||
print(f" - {model.id}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
'''
|
||||
with open(example_file, 'w') as f:
|
||||
f.write(example_content)
|
||||
print(f"📝 Created usage example: {example_file}")
|
||||
|
||||
def _post_process_typescript(self):
|
||||
"""Post-process TypeScript client."""
|
||||
# Create a simple usage example
|
||||
example_file = self.output_dir / 'example-usage.ts'
|
||||
example_content = '''/**
|
||||
* Example usage of the generated Ollama FastAPI client.
|
||||
*/
|
||||
|
||||
import { Configuration, DefaultApi } from './';
|
||||
|
||||
// Configure the client
|
||||
const configuration = new Configuration({
|
||||
basePath: 'http://localhost:8000', // Adjust to your Ollama API endpoint
|
||||
});
|
||||
|
||||
// Create API client
|
||||
const api = new DefaultApi(configuration);
|
||||
|
||||
async function listModels() {
|
||||
try {
|
||||
const response = await api.listLocalModelsV1ModelsGet();
|
||||
console.log('Available models:');
|
||||
response.data.data.forEach(model => {
|
||||
console.log(` - ${model.id}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the example
|
||||
listModels();
|
||||
'''
|
||||
with open(example_file, 'w') as f:
|
||||
f.write(example_content)
|
||||
print(f"📝 Created usage example: {example_file}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Generate FastAPI clients from OpenAPI specifications')
|
||||
parser.add_argument('openapi_path', help='Path to the OpenAPI JSON/YAML file')
|
||||
parser.add_argument('-o', '--output', required=True, help='Output directory for generated client')
|
||||
parser.add_argument('-g', '--generator',
|
||||
choices=list(ClientGenerator.SUPPORTED_GENERATORS.keys()),
|
||||
default='python',
|
||||
help='Client generator type (default: python)')
|
||||
parser.add_argument('-p', '--package-name', help='Package name for generated client')
|
||||
parser.add_argument('--list-generators', action='store_true',
|
||||
help='List all supported generators')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list_generators:
|
||||
print("Supported generators:")
|
||||
for name, generator in ClientGenerator.SUPPORTED_GENERATORS.items():
|
||||
print(f" {name:20} -> {generator}")
|
||||
return
|
||||
|
||||
try:
|
||||
generator = ClientGenerator(args.openapi_path, args.output, args.generator)
|
||||
success = generator.generate_client(args.package_name)
|
||||
|
||||
if success:
|
||||
print(f"\n🎉 Client generated successfully!")
|
||||
print(f"📁 Output directory: {args.output}")
|
||||
print(f"🔧 Generator used: {args.generator}")
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user