-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feature: event based plugin * chore: update README.md * Update yarn script for build plugins (janhq#363) * Update yarn script for build plugins * Plugin-core install from npmjs instead of from local --------- Co-authored-by: Hien To <> * janhq#360 plugin preferences (janhq#361) * feature: janhq#360 plugin preferences * chore: update core-plugin README.md * chore: create collections on start * chore: bumb core version * chore: update README * chore: notify preferences update * fix: preference update --------- Co-authored-by: hiento09 <[email protected]>
- Loading branch information
Showing
30 changed files
with
4,447 additions
and
501 deletions.
There are no files selected for viewing
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
Large diffs are not rendered by default.
Oops, something went wrong.
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 |
---|---|---|
@@ -1,23 +1,97 @@ | ||
const MODULE_PATH = "inference-plugin/dist/module.js"; | ||
|
||
const initModel = async (product) => | ||
new Promise(async (resolve) => { | ||
if (window.electronAPI) { | ||
window.electronAPI | ||
.invokePluginFunc(MODULE_PATH, "initModel", product) | ||
.then((res) => resolve(res)); | ||
} | ||
}); | ||
import { EventName, InferenceService, NewMessageRequest, PluginService, core, events, store } from "@janhq/plugin-core"; | ||
|
||
const inferenceUrl = () => "http://localhost:3928/llama/chat_completion"; | ||
const PluginName = "inference-plugin"; | ||
const MODULE_PATH = `${PluginName}/dist/module.js`; | ||
const inferenceUrl = "http://localhost:3928/llama/chat_completion"; | ||
|
||
const initModel = async (product) => core.invokePluginFunc(MODULE_PATH, "initModel", product); | ||
|
||
const stopModel = () => { | ||
window.electronAPI.invokePluginFunc(MODULE_PATH, "killSubprocess"); | ||
core.invokePluginFunc(MODULE_PATH, "killSubprocess"); | ||
}; | ||
|
||
async function handleMessageRequest(data: NewMessageRequest) { | ||
// TODO: Common collections should be able to access via core functions instead of store | ||
const messageHistory = | ||
(await store.findMany("messages", { conversationId: data.conversationId }, [{ createdAt: "asc" }])) ?? []; | ||
const recentMessages = messageHistory | ||
.filter((e) => e.message !== "" && (e.user === "user" || e.user === "assistant")) | ||
.slice(-10) | ||
.map((message) => { | ||
return { | ||
content: message.message.trim(), | ||
role: message.user === "user" ? "user" : "assistant", | ||
}; | ||
}); | ||
|
||
const message = { | ||
...data, | ||
message: "", | ||
user: "assistant", | ||
createdAt: new Date().toISOString(), | ||
_id: undefined, | ||
}; | ||
// TODO: Common collections should be able to access via core functions instead of store | ||
const id = await store.insertOne("messages", message); | ||
|
||
message._id = id; | ||
events.emit(EventName.OnNewMessageResponse, message); | ||
|
||
const response = await fetch(inferenceUrl, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Accept: "text/event-stream", | ||
"Access-Control-Allow-Origi": "*", | ||
}, | ||
body: JSON.stringify({ | ||
messages: recentMessages, | ||
stream: true, | ||
model: "gpt-3.5-turbo", | ||
max_tokens: 500, | ||
}), | ||
}); | ||
const stream = response.body; | ||
|
||
const decoder = new TextDecoder("utf-8"); | ||
const reader = stream?.getReader(); | ||
let answer = ""; | ||
|
||
while (true && reader) { | ||
const { done, value } = await reader.read(); | ||
if (done) { | ||
console.log("SSE stream closed"); | ||
break; | ||
} | ||
const text = decoder.decode(value); | ||
const lines = text.trim().split("\n"); | ||
for (const line of lines) { | ||
if (line.startsWith("data: ") && !line.includes("data: [DONE]")) { | ||
const data = JSON.parse(line.replace("data: ", "")); | ||
answer += data.choices[0]?.delta?.content ?? ""; | ||
if (answer.startsWith("assistant: ")) { | ||
answer = answer.replace("assistant: ", ""); | ||
} | ||
message.message = answer; | ||
events.emit(EventName.OnMessageResponseUpdate, message); | ||
} | ||
} | ||
} | ||
message.message = answer.trim(); | ||
// TODO: Common collections should be able to access via core functions instead of store | ||
await store.updateOne("messages", message._id, message); | ||
} | ||
|
||
const registerListener = () => { | ||
events.on(EventName.OnNewMessageRequest, handleMessageRequest); | ||
}; | ||
|
||
const onStart = async () => { | ||
registerListener(); | ||
}; | ||
// Register all the above functions and objects with the relevant extension points | ||
export function init({ register }) { | ||
register("initModel", "initModel", initModel); | ||
register("inferenceUrl", "inferenceUrl", inferenceUrl); | ||
register("stopModel", "stopModel", stopModel); | ||
register(PluginService.OnStart, PluginName, onStart); | ||
register(InferenceService.InitModel, initModel.name, initModel); | ||
register(InferenceService.StopModel, stopModel.name, stopModel); | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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 |
---|---|---|
@@ -1,22 +1,13 @@ | ||
{ | ||
"compilerOptions": { | ||
/* Visit https://aka.ms/tsconfig to read more about this file */ | ||
/* Language and Environment */ | ||
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, | ||
/* Modules */ | ||
"module": "ES6" /* Specify what module code is generated. */, | ||
// "rootDir": "./", /* Specify the root folder within your source files. */ | ||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ | ||
// "baseUrl": "." /* Specify the base directory to resolve non-relative module names. */, | ||
// "paths": {} /* Specify a set of entries that re-map imports to additional lookup locations. */, | ||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ | ||
// "resolveJsonModule": true, /* Enable importing .json files. */ | ||
"target": "es2016", | ||
"module": "ES6", | ||
"moduleResolution": "node", | ||
|
||
"outDir": "./dist" /* Specify an output folder for all emitted files. */, | ||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, | ||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, | ||
/* Type Checking */ | ||
"strict": false /* Enable all strict type-checking options. */, | ||
"skipLibCheck": true /* Skip type checking all .d.ts files. */ | ||
"outDir": "./dist", | ||
"esModuleInterop": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"strict": false, | ||
"skipLibCheck": true | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
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
7 changes: 4 additions & 3 deletions
7
electron/core/plugins/model-management-plugin/package-lock.json
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,115 @@ | ||
import { | ||
PluginService, | ||
EventName, | ||
NewMessageRequest, | ||
events, | ||
store, | ||
preferences, | ||
RegisterExtensionPoint, | ||
} from "@janhq/plugin-core"; | ||
import { Configuration, OpenAIApi } from "azure-openai"; | ||
|
||
const PluginName = "openai-plugin"; | ||
|
||
const setRequestHeader = XMLHttpRequest.prototype.setRequestHeader; | ||
XMLHttpRequest.prototype.setRequestHeader = function newSetRequestHeader(key: string, val: string) { | ||
if (key.toLocaleLowerCase() === "user-agent") { | ||
return; | ||
} | ||
setRequestHeader.apply(this, [key, val]); | ||
}; | ||
|
||
var openai: OpenAIApi | undefined = undefined; | ||
|
||
const setup = async () => { | ||
const apiKey: string = (await preferences.get(PluginName, "apiKey")) ?? ""; | ||
const endpoint: string = (await preferences.get(PluginName, "endpoint")) ?? ""; | ||
const deploymentName: string = (await preferences.get(PluginName, "deploymentName")) ?? ""; | ||
try { | ||
openai = new OpenAIApi( | ||
new Configuration({ | ||
azure: { | ||
apiKey, //Your API key goes here | ||
endpoint, //Your endpoint goes here. It is like: "https://endpointname.openai.azure.com/" | ||
deploymentName, //Your deployment name goes here. It is like "chatgpt" | ||
}, | ||
}) | ||
); | ||
} catch (err) { | ||
openai = undefined; | ||
console.log(err); | ||
} | ||
}; | ||
|
||
async function onStart() { | ||
setup(); | ||
registerListener(); | ||
} | ||
|
||
async function handleMessageRequest(data: NewMessageRequest) { | ||
if (!openai) { | ||
const message = { | ||
...data, | ||
message: "Your API key is not set. Please set it in the plugin preferences.", | ||
user: "GPT-3", | ||
avatar: "https://static-assets.jan.ai/openai-icon.jpg", | ||
createdAt: new Date().toISOString(), | ||
_id: undefined, | ||
}; | ||
const id = await store.insertOne("messages", message); | ||
message._id = id; | ||
events.emit(EventName.OnNewMessageResponse, message); | ||
return; | ||
} | ||
|
||
const message = { | ||
...data, | ||
message: "", | ||
user: "GPT-3", | ||
avatar: "https://static-assets.jan.ai/openai-icon.jpg", | ||
createdAt: new Date().toISOString(), | ||
_id: undefined, | ||
}; | ||
const id = await store.insertOne("messages", message); | ||
|
||
message._id = id; | ||
events.emit(EventName.OnNewMessageResponse, message); | ||
const response = await openai.createChatCompletion({ | ||
messages: [{ role: "user", content: data.message }], | ||
model: "gpt-3.5-turbo", | ||
}); | ||
message.message = response.data.choices[0].message.content; | ||
events.emit(EventName.OnMessageResponseUpdate, message); | ||
await store.updateOne("messages", message._id, message); | ||
} | ||
|
||
const registerListener = () => { | ||
events.on(EventName.OnNewMessageRequest, handleMessageRequest); | ||
}; | ||
|
||
const onPreferencesUpdate = () => { | ||
setup(); | ||
}; | ||
// Register all the above functions and objects with the relevant extension points | ||
export function init({ register }: { register: RegisterExtensionPoint }) { | ||
register(PluginService.OnStart, PluginName, onStart); | ||
register(PluginService.OnPreferencesUpdate, PluginName, onPreferencesUpdate); | ||
|
||
preferences.registerPreferences<string>(register, PluginName, "apiKey", "API Key", "Azure Project API Key", ""); | ||
preferences.registerPreferences<string>( | ||
register, | ||
PluginName, | ||
"endpoint", | ||
"API Endpoint", | ||
"Azure Deployment Endpoint API", | ||
"" | ||
); | ||
preferences.registerPreferences<string>( | ||
register, | ||
PluginName, | ||
"deploymentName", | ||
"Deployment Name", | ||
"The deployment name you chose when you deployed the model", | ||
"" | ||
); | ||
} |
Oops, something went wrong.