-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for GPT4Free Local API (#54)
The extension now supports integration with the locally hosted API feature of [the gpt4free project](https://github.com/xtekky/gpt4free). You can easily host your own gpt4free API locally, and connect it to the extension. This opens up many possibilities, allowing for a great variety of up-to-date providers. Read more here: - https://github.com/xtekky/gpt4free/blob/main/docs/interference.md - Help page: https://github.com/XInTheDark/raycast-g4f/wiki/Help-page:-GPT4Free-Local-API
- Loading branch information
1 parent
3fa12b0
commit 37472ca
Showing
13 changed files
with
239 additions
and
17 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// This module allows communication and requests to the local G4F API. | ||
// Read more here: https://github.com/xtekky/gpt4free/blob/main/docs/interference.md | ||
|
||
import { exec } from "child_process"; | ||
import fetch from "node-fetch"; | ||
|
||
import { Storage } from "../storage"; | ||
import { messages_to_json } from "../../classes/message"; | ||
|
||
import { environment, Form } from "@raycast/api"; | ||
|
||
// constants | ||
const DEFAULT_MODEL = "meta-ai"; | ||
export const DEFAULT_TIMEOUT = "900"; | ||
|
||
const BASE_URL = "http://localhost:1337/v1"; | ||
const API_URL = "http://localhost:1337/v1/chat/completions"; | ||
const MODELS_URL = "http://localhost:1337/v1/models"; | ||
|
||
// main function | ||
export const G4FLocalProvider = "G4FLocalProvider"; | ||
export const getG4FLocalResponse = async function* (chat, options) { | ||
if (!(await isG4FRunning())) { | ||
await startG4F(); | ||
} | ||
|
||
chat = messages_to_json(chat); | ||
const model = await getSelectedG4FModel(); | ||
|
||
const response = await fetch(API_URL, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ | ||
model: model, | ||
stream: options.stream, | ||
messages: chat, | ||
}), | ||
}); | ||
|
||
// Important! we assume that the response is a stream, as this is true for most G4F models. | ||
// If in the future this is not the case, we should add separate handling for non-streaming responses. | ||
const reader = response.body; | ||
for await (let chunk of reader) { | ||
const str = chunk.toString(); | ||
let lines = str.split("\n"); | ||
for (let i = 0; i < lines.length; i++) { | ||
let line = lines[i]; | ||
if (line.startsWith("data: ")) { | ||
let chunk = line.substring(6); | ||
if (chunk.trim() === "[DONE]") return; // trim() is important | ||
|
||
try { | ||
let data = JSON.parse(chunk); | ||
let delta = data["choices"][0]["delta"]["content"]; | ||
if (delta) { | ||
yield delta; | ||
} | ||
} catch (e) {} // eslint-disable-line | ||
} | ||
} | ||
} | ||
}; | ||
|
||
/// utilities | ||
|
||
// check if the G4F API is running | ||
// with a request timeout of 0.5 seconds (since it's localhost) | ||
const isG4FRunning = async () => { | ||
try { | ||
const controller = new AbortController(); | ||
setTimeout(() => controller.abort(), 500); | ||
const response = await fetch(BASE_URL, { signal: controller.signal }); | ||
return response.ok; | ||
} catch (e) { | ||
return false; | ||
} | ||
}; | ||
|
||
// get available models | ||
const getG4FModels = async () => { | ||
try { | ||
const response = await fetch(MODELS_URL); | ||
return (await response.json()).data || []; | ||
} catch (e) { | ||
return []; | ||
} | ||
}; | ||
|
||
// get available models as dropdown component | ||
export const getG4FModelsDropdown = async () => { | ||
const models = await getG4FModels(); | ||
return ( | ||
<Form.Dropdown id="model" title="Model" defaultValue={await getSelectedG4FModel()}> | ||
{models.map((model) => { | ||
return <Form.Dropdown.Item title={model.id} key={model.id} value={model.id} />; | ||
})} | ||
</Form.Dropdown> | ||
); | ||
}; | ||
|
||
// get G4F executable path from storage | ||
export const getG4FExecutablePath = async () => { | ||
return await Storage.read("g4f_executable", "g4f"); | ||
}; | ||
|
||
// get the currently selected G4F model from storage | ||
const getSelectedG4FModel = async () => { | ||
return await Storage.read("g4f_model", DEFAULT_MODEL); | ||
}; | ||
|
||
// get G4F API timeout (in seconds) from storage | ||
export const getG4FTimeout = async () => { | ||
return parseInt(await Storage.read("g4f_timeout", DEFAULT_TIMEOUT)) || parseInt(DEFAULT_TIMEOUT); | ||
}; | ||
|
||
// start the G4F API | ||
const startG4F = async () => { | ||
const exe = await getG4FExecutablePath(); | ||
const timeout_s = await getG4FTimeout(); | ||
const START_COMMAND = `export PATH="/opt/homebrew/bin:$PATH"; ( ${exe} api ) & sleep ${timeout_s} ; kill $!`; | ||
const dirPath = environment.supportPath; | ||
try { | ||
const child = exec(START_COMMAND, { cwd: dirPath }); | ||
console.log("G4F API Process ID:", child.pid); | ||
child.stderr.on("data", (data) => { | ||
console.log("g4f >", data); | ||
}); | ||
// sleep for some time to allow the API to start | ||
await new Promise((resolve) => setTimeout(resolve, 2000)); | ||
console.log(`G4F API started with timeout ${timeout_s}`); | ||
} catch (e) { | ||
console.log(e); | ||
} | ||
}; |
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
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { getG4FExecutablePath, getG4FTimeout, DEFAULT_TIMEOUT, getG4FModelsDropdown } from "./api/Providers/g4f_local"; | ||
import { Storage } from "./api/storage"; | ||
import { help_action } from "./helpers/helpPage"; | ||
|
||
import { Form, ActionPanel, Action, useNavigation, showToast, Toast } from "@raycast/api"; | ||
import { useState, useEffect } from "react"; | ||
|
||
export default function ConfigureG4FLocalApi() { | ||
const [executablePath, setExecutablePath] = useState(""); | ||
const [timeout, setTimeout] = useState(""); | ||
const [modelsDropdown, setModelsDropdown] = useState([]); | ||
const [rendered, setRendered] = useState(false); | ||
|
||
const { pop } = useNavigation(); | ||
|
||
useEffect(() => { | ||
(async () => { | ||
setExecutablePath(await getG4FExecutablePath()); | ||
setTimeout((await getG4FTimeout()).toString()); | ||
setModelsDropdown(await getG4FModelsDropdown()); | ||
setRendered(true); | ||
})(); | ||
}, []); | ||
|
||
return ( | ||
<Form | ||
actions={ | ||
<ActionPanel> | ||
<Action.SubmitForm | ||
title="Save" | ||
onSubmit={async (values) => { | ||
pop(); | ||
await Storage.write("g4f_executable", values.g4f_executable); | ||
await Storage.write("g4f_timeout", values.g4f_timeout || DEFAULT_TIMEOUT); | ||
await Storage.write("g4f_model", values.model); | ||
await showToast(Toast.Style.Success, "Configuration Saved"); | ||
}} | ||
/> | ||
{help_action("g4fLocal")} | ||
</ActionPanel> | ||
} | ||
> | ||
<Form.Description text="Configure the GPT4Free Local API. Select 'Help' for the full guide." /> | ||
<Form.TextField | ||
id="g4f_executable" | ||
title="G4F Executable Path" | ||
value={executablePath} | ||
onChange={(x) => { | ||
if (rendered) setExecutablePath(x); | ||
}} | ||
/> | ||
<Form.TextField | ||
id="g4f_timeout" | ||
title="G4F API Timeout (in seconds)" | ||
info="After this timeout, the G4F API will be stopped. It will be automatically started again when used. This saves resources when the API is not in use." | ||
value={timeout} | ||
onChange={(x) => { | ||
if (rendered) setTimeout(x); | ||
}} | ||
/> | ||
{modelsDropdown} | ||
</Form> | ||
); | ||
} |
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
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