mirror of
https://github.com/celesrenata/end-4-flakes.git
synced 2026-06-19 07:29:57 -05:00
fix: add wayland dev headers and scanner for pywayland build on NixOS
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import QtQuick;
|
||||
|
||||
/**
|
||||
* Represents a message in an AI conversation. (Kind of) follows the OpenAI API message structure.
|
||||
*/
|
||||
QtObject {
|
||||
property string role
|
||||
property string content
|
||||
property string rawContent
|
||||
property string model
|
||||
property bool thinking: true
|
||||
property bool done: false
|
||||
property var annotations: []
|
||||
property var annotationSources: []
|
||||
property list<string> searchQueries: []
|
||||
property string functionName
|
||||
property var functionCall
|
||||
property string functionResponse
|
||||
property bool functionPending: false
|
||||
property bool visibleToUser: true
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import QtQuick;
|
||||
|
||||
/**
|
||||
* An AI model representation.
|
||||
* - name: Friendly name of the model
|
||||
* - icon: Icon name of the model
|
||||
* - description: Description of the model
|
||||
* - endpoint: Endpoint of the model
|
||||
* - model: Model code (like gpt-4.1 or gemini-2.5-flash)
|
||||
* - requires_key: Whether the model requires an API key
|
||||
* - key_id: The identifier of the API key. Use the same identifier for models that can be accessed with the same key.
|
||||
* - key_get_link: Link to get an API key
|
||||
* - key_get_description: Description of pricing and how to get an API key
|
||||
* - api_format: The API format of the model. Can be "openai" or "gemini". Default is "openai".
|
||||
* - extraParams: Extra parameters to be passed to the model. This is a JSON object.
|
||||
*/
|
||||
|
||||
QtObject {
|
||||
property string name
|
||||
property string icon
|
||||
property string description
|
||||
property string homepage
|
||||
property string endpoint
|
||||
property string model
|
||||
property bool requires_key: true
|
||||
property string key_id
|
||||
property string key_get_link
|
||||
property string key_get_description
|
||||
property string api_format: "openai"
|
||||
property var tools
|
||||
property var extraParams: ({})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
function buildEndpoint(model: AiModel): string { throw new Error("Not implemented") }
|
||||
function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>) { throw new Error("Not implemented") }
|
||||
function buildAuthorizationHeader(apiKeyEnvVarName: string): string { throw new Error("Not implemented") }
|
||||
function parseResponseLine(line: string, message: AiMessageData) { throw new Error("Not implemented") }
|
||||
function onRequestFinished(message: AiMessageData): var { return {} } // Default: no special handling
|
||||
function reset() { } // Reset any internal state if needed
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import QtQuick
|
||||
|
||||
ApiStrategy {
|
||||
property string buffer: ""
|
||||
|
||||
function buildEndpoint(model: AiModel): string {
|
||||
const result = model.endpoint + `?key=\$\{${root.apiKeyEnvVarName}\}`
|
||||
// console.log("[AI] Endpoint: " + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>) {
|
||||
let baseData = {
|
||||
"contents": messages.map(message => {
|
||||
const geminiApiRoleName = (message.role === "assistant") ? "model" : message.role;
|
||||
const usingSearch = tools[0]?.google_search !== undefined
|
||||
if (!usingSearch && message.functionCall != undefined && message.functionName.length > 0) {
|
||||
return {
|
||||
"role": geminiApiRoleName,
|
||||
"parts": [{
|
||||
functionCall: {
|
||||
"name": message.functionName,
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
if (!usingSearch && message.functionResponse != undefined && message.functionName.length > 0) {
|
||||
return {
|
||||
"role": geminiApiRoleName,
|
||||
"parts": [{
|
||||
functionResponse: {
|
||||
"name": message.functionName,
|
||||
"response": { "content": message.functionResponse }
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
return {
|
||||
"role": geminiApiRoleName,
|
||||
"parts": [{
|
||||
text: message.rawContent,
|
||||
}]
|
||||
}
|
||||
}),
|
||||
"tools": tools,
|
||||
"system_instruction": {
|
||||
"parts": [{ text: systemPrompt }]
|
||||
},
|
||||
"generationConfig": {
|
||||
"temperature": temperature,
|
||||
},
|
||||
};
|
||||
return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;
|
||||
}
|
||||
|
||||
function buildAuthorizationHeader(apiKeyEnvVarName: string): string {
|
||||
// Gemini doesn't use Authorization header, key is in URL
|
||||
return "";
|
||||
}
|
||||
|
||||
function parseResponseLine(line, message) {
|
||||
if (line.startsWith("[")) {
|
||||
buffer += line.slice(1).trim();
|
||||
} else if (line === "]") {
|
||||
buffer += line.slice(0, -1).trim();
|
||||
return parseBuffer(message);
|
||||
} else if (line.startsWith(",")) {
|
||||
return parseBuffer(message);
|
||||
} else {
|
||||
buffer += line.trim();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function parseBuffer(message) {
|
||||
// console.log("[Ai] Gemini buffer: ", buffer);
|
||||
let finished = false;
|
||||
try {
|
||||
if (buffer.length === 0) return {};
|
||||
const dataJson = JSON.parse(buffer);
|
||||
if (!dataJson.candidates) return {};
|
||||
|
||||
if (dataJson.candidates[0]?.finishReason) {
|
||||
finished = true;
|
||||
}
|
||||
|
||||
// Function call handling
|
||||
if (dataJson.candidates[0]?.content?.parts[0]?.functionCall) {
|
||||
const functionCall = dataJson.candidates[0]?.content?.parts[0]?.functionCall;
|
||||
message.functionName = functionCall.name;
|
||||
message.functionCall = functionCall.name;
|
||||
const newContent = `\n\n[[ Function: ${functionCall.name}(${JSON.stringify(functionCall.args, null, 2)}) ]]\n`
|
||||
message.rawContent += newContent;
|
||||
message.content += newContent;
|
||||
return { functionCall: { name: functionCall.name, args: functionCall.args }, finished: finished };
|
||||
}
|
||||
|
||||
// Normal text response
|
||||
const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text
|
||||
message.rawContent += responseContent;
|
||||
message.content += responseContent;
|
||||
|
||||
// Handle annotations and metadata
|
||||
const annotationSources = dataJson.candidates[0]?.groundingMetadata?.groundingChunks?.map(chunk => {
|
||||
return {
|
||||
"type": "url_citation",
|
||||
"text": chunk?.web?.title,
|
||||
"url": chunk?.web?.uri,
|
||||
}
|
||||
}) ?? [];
|
||||
|
||||
const annotations = dataJson.candidates[0]?.groundingMetadata?.groundingSupports?.map(citation => {
|
||||
return {
|
||||
"type": "url_citation",
|
||||
"start_index": citation.segment?.startIndex,
|
||||
"end_index": citation.segment?.endIndex,
|
||||
"text": citation?.segment.text,
|
||||
"url": annotationSources[citation.groundingChunkIndices[0]]?.url,
|
||||
"sources": citation.groundingChunkIndices
|
||||
}
|
||||
});
|
||||
message.annotationSources = annotationSources;
|
||||
message.annotations = annotations;
|
||||
message.searchQueries = dataJson.candidates[0]?.groundingMetadata?.webSearchQueries ?? [];
|
||||
|
||||
// Usage metadata
|
||||
if (dataJson.usageMetadata) {
|
||||
return {
|
||||
tokenUsage: {
|
||||
input: dataJson.usageMetadata.promptTokenCount ?? -1,
|
||||
output: dataJson.usageMetadata.candidatesTokenCount ?? -1,
|
||||
total: dataJson.usageMetadata.totalTokenCount ?? -1
|
||||
},
|
||||
finished: finished
|
||||
};
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.log("[AI] Gemini: Could not parse buffer: ", e);
|
||||
message.rawContent += buffer;
|
||||
message.content += buffer;
|
||||
} finally {
|
||||
buffer = "";
|
||||
}
|
||||
return { finished: finished };
|
||||
}
|
||||
|
||||
function onRequestFinished(message) {
|
||||
return parseBuffer(message);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
buffer = "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import QtQuick
|
||||
|
||||
ApiStrategy {
|
||||
property bool isReasoning: false
|
||||
|
||||
function buildEndpoint(model: AiModel): string {
|
||||
// console.log("[AI] Endpoint: " + model.endpoint);
|
||||
return model.endpoint;
|
||||
}
|
||||
|
||||
function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>) {
|
||||
let baseData = {
|
||||
"model": model.model,
|
||||
"messages": [
|
||||
{role: "system", content: systemPrompt},
|
||||
...messages.map(message => {
|
||||
const hasFunctionCall = message.functionCall != undefined && message.functionName.length > 0
|
||||
let messageData = {
|
||||
"role": message.role,
|
||||
"content": message.rawContent,
|
||||
}
|
||||
if (hasFunctionCall) {
|
||||
if (message.functionResponse?.length > 0) {
|
||||
messageData.name = message.functionName; // Does the func call also need this name? or just the func output?
|
||||
messageData.role = "tool";
|
||||
messageData.content = message.functionResponse;
|
||||
messageData.tool_call_id = message.functionCall.id
|
||||
}
|
||||
}
|
||||
return messageData
|
||||
}),
|
||||
],
|
||||
"stream": true,
|
||||
"temperature": temperature,
|
||||
"tools": tools,
|
||||
};
|
||||
// console.log("[AI] Request data: ", JSON.stringify(baseData, null, 2));
|
||||
return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;
|
||||
}
|
||||
|
||||
function buildAuthorizationHeader(apiKeyEnvVarName: string): string {
|
||||
return `-H "Authorization: Bearer \$\{${apiKeyEnvVarName}\}"`;
|
||||
}
|
||||
|
||||
function parseResponseLine(line, message) {
|
||||
// Remove 'data: ' prefix if present and trim whitespace
|
||||
let cleanData = line.trim();
|
||||
if (cleanData.startsWith("data:")) {
|
||||
cleanData = cleanData.slice(5).trim();
|
||||
}
|
||||
|
||||
// Handle special cases
|
||||
if (!cleanData || cleanData.startsWith(":")) return {};
|
||||
if (cleanData === "[DONE]") {
|
||||
return { finished: true };
|
||||
}
|
||||
|
||||
// Real stuff
|
||||
try {
|
||||
const dataJson = JSON.parse(cleanData);
|
||||
let newContent = "";
|
||||
|
||||
const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content;
|
||||
const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content;
|
||||
|
||||
// Function call
|
||||
if (dataJson.choices[0]?.delta?.tool_calls) {
|
||||
const functionCall = dataJson.choices[0].delta.tool_calls[0];
|
||||
const functionName = functionCall.function.name;
|
||||
const functionArgs = JSON.parse(functionCall.function.arguments) || {}; // Args are given as string???
|
||||
const functionId = functionCall.id;
|
||||
const newContent = `\n\n[[ Function: ${functionName}(${JSON.stringify(functionArgs, null, 2)}) ]]\n`;
|
||||
message.rawContent += newContent;
|
||||
message.content += newContent;
|
||||
message.functionName = functionName;
|
||||
message.functionCall = functionName;
|
||||
return { functionCall: { name: functionName, args: functionArgs, id: functionId } };
|
||||
}
|
||||
|
||||
// Thinking?
|
||||
if (responseContent && responseContent.length > 0) {
|
||||
if (isReasoning) {
|
||||
isReasoning = false;
|
||||
const endBlock = "\n\n</think>\n\n";
|
||||
message.content += endBlock;
|
||||
message.rawContent += endBlock;
|
||||
}
|
||||
newContent = responseContent;
|
||||
} else if (responseReasoning && responseReasoning.length > 0) {
|
||||
if (!isReasoning) {
|
||||
isReasoning = true;
|
||||
const startBlock = "\n\n<think>\n\n";
|
||||
message.rawContent += startBlock;
|
||||
message.content += startBlock;
|
||||
}
|
||||
newContent = responseReasoning;
|
||||
}
|
||||
|
||||
// Text
|
||||
message.content += newContent;
|
||||
message.rawContent += newContent;
|
||||
|
||||
if (`dataJson`.done) {
|
||||
return { finished: true };
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.log("[AI] Mistral: Could not parse line: ", e);
|
||||
message.rawContent += line;
|
||||
message.content += line;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function onRequestFinished(message) {
|
||||
return {};
|
||||
}
|
||||
|
||||
function reset() {
|
||||
isReasoning = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import QtQuick
|
||||
|
||||
ApiStrategy {
|
||||
property bool isReasoning: false
|
||||
|
||||
function buildEndpoint(model: AiModel): string {
|
||||
// console.log("[AI] Endpoint: " + model.endpoint);
|
||||
return model.endpoint;
|
||||
}
|
||||
|
||||
function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>) {
|
||||
let baseData = {
|
||||
"model": model.model,
|
||||
"messages": [
|
||||
{role: "system", content: systemPrompt},
|
||||
...messages.map(message => {
|
||||
return {
|
||||
"role": message.role,
|
||||
"content": message.rawContent,
|
||||
}
|
||||
}),
|
||||
],
|
||||
"stream": true,
|
||||
"temperature": temperature,
|
||||
};
|
||||
return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;
|
||||
}
|
||||
|
||||
function buildAuthorizationHeader(apiKeyEnvVarName: string): string {
|
||||
return `-H "Authorization: Bearer \$\{${apiKeyEnvVarName}\}"`;
|
||||
}
|
||||
|
||||
function parseResponseLine(line, message) {
|
||||
// Remove 'data: ' prefix if present and trim whitespace
|
||||
let cleanData = line.trim();
|
||||
if (cleanData.startsWith("data:")) {
|
||||
cleanData = cleanData.slice(5).trim();
|
||||
}
|
||||
|
||||
// Handle special cases
|
||||
if (!cleanData || cleanData.startsWith(":")) return {};
|
||||
if (cleanData === "[DONE]") {
|
||||
return { finished: true };
|
||||
}
|
||||
|
||||
// Real stuff
|
||||
try {
|
||||
const dataJson = JSON.parse(cleanData);
|
||||
let newContent = "";
|
||||
|
||||
const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content;
|
||||
const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content;
|
||||
|
||||
if (responseContent && responseContent.length > 0) {
|
||||
if (isReasoning) {
|
||||
isReasoning = false;
|
||||
const endBlock = "\n\n</think>\n\n";
|
||||
message.content += endBlock;
|
||||
message.rawContent += endBlock;
|
||||
}
|
||||
newContent = responseContent;
|
||||
} else if (responseReasoning && responseReasoning.length > 0) {
|
||||
if (!isReasoning) {
|
||||
isReasoning = true;
|
||||
const startBlock = "\n\n<think>\n\n";
|
||||
message.rawContent += startBlock;
|
||||
message.content += startBlock;
|
||||
}
|
||||
newContent = responseReasoning;
|
||||
}
|
||||
|
||||
message.content += newContent;
|
||||
message.rawContent += newContent;
|
||||
|
||||
if (dataJson.done) {
|
||||
return { finished: true };
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.log("[AI] OpenAI: Could not parse line: ", e);
|
||||
message.rawContent += line;
|
||||
message.content += line;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function onRequestFinished(message) {
|
||||
// OpenAI format doesn't need special finish handling
|
||||
return {};
|
||||
}
|
||||
|
||||
function reset() {
|
||||
isReasoning = false;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user