forked from Shinonome/dots-hyprland
ai: gemini: configurator
This commit is contained in:
@@ -5,7 +5,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
property QtObject ai: QtObject {
|
property QtObject ai: QtObject {
|
||||||
property string systemPrompt: qsTr("Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in bold to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`.")
|
property string systemPrompt: qsTr("Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in bold to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`. When making changes to the user's config, you must get the config to know what values there are before setting.")
|
||||||
}
|
}
|
||||||
|
|
||||||
property QtObject appearance: QtObject {
|
property QtObject appearance: QtObject {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
property QtObject ai: QtObject {
|
property QtObject ai: QtObject {
|
||||||
property string model: "gemini-2.0-flash-search"
|
property string model
|
||||||
}
|
}
|
||||||
|
|
||||||
property QtObject sidebar: QtObject {
|
property QtObject sidebar: QtObject {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ pragma Singleton
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
||||||
|
import "root:/modules/common/functions/object_utils.js" as ObjectUtils
|
||||||
import "root:/modules/common"
|
import "root:/modules/common"
|
||||||
import Quickshell;
|
import Quickshell;
|
||||||
import Quickshell.Io;
|
import Quickshell.Io;
|
||||||
@@ -23,6 +24,7 @@ Singleton {
|
|||||||
property var messageByID: ({})
|
property var messageByID: ({})
|
||||||
readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {}
|
readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {}
|
||||||
readonly property var apiKeysLoaded: KeyringStorage.loaded
|
readonly property var apiKeysLoaded: KeyringStorage.loaded
|
||||||
|
property var postResponseHook
|
||||||
|
|
||||||
function idForMessage(message) {
|
function idForMessage(message) {
|
||||||
// Generate a unique ID using timestamp and random value
|
// Generate a unique ID using timestamp and random value
|
||||||
@@ -48,7 +50,7 @@ Singleton {
|
|||||||
// - extraParams: Extra parameters to be passed to the model. This is a JSON object.
|
// - extraParams: Extra parameters to be passed to the model. This is a JSON object.
|
||||||
property var models: {
|
property var models: {
|
||||||
"gemini-2.0-flash-search": {
|
"gemini-2.0-flash-search": {
|
||||||
"name": "Gemini 2.0 Flash",
|
"name": "Gemini 2.0 Flash (Search)",
|
||||||
"icon": "google-gemini-symbolic",
|
"icon": "google-gemini-symbolic",
|
||||||
"description": qsTr("Online | Google's model\nGives up-to-date information with search."),
|
"description": qsTr("Online | Google's model\nGives up-to-date information with search."),
|
||||||
"homepage": "https://aistudio.google.com",
|
"homepage": "https://aistudio.google.com",
|
||||||
@@ -65,8 +67,53 @@ Singleton {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"gemini-2.5-flash-preview-05-20": {
|
"gemini-2.0-flash-tools": {
|
||||||
"name": "Gemini 2.5 Flash (preview)",
|
"name": "Gemini 2.0 Flash (Tools)",
|
||||||
|
"icon": "google-gemini-symbolic",
|
||||||
|
"description": qsTr("Experimental | Online | Google's model\nCan do a little more but doesn't search quickly"),
|
||||||
|
"homepage": "https://aistudio.google.com",
|
||||||
|
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent",
|
||||||
|
"model": "gemini-2.0-flash",
|
||||||
|
"requires_key": true,
|
||||||
|
"key_id": "gemini",
|
||||||
|
"key_get_link": "https://aistudio.google.com/app/apikey",
|
||||||
|
"key_get_description": qsTr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
|
||||||
|
"api_format": "gemini",
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"functionDeclarations": [
|
||||||
|
{
|
||||||
|
"name": "switch_to_search_mode",
|
||||||
|
"description": "Search the web",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get_shell_config",
|
||||||
|
"description": "Get the desktop shell config file contents",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "set_shell_config",
|
||||||
|
"description": "Set a field in the desktop graphical shell config file. Must only be used after `get_shell_config`.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The key to set, e.g. `bar.borderless`. MUST NOT BE GUESSED, use `get_shell_config` to see what keys are available before setting.",
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The value to set, e.g. `true`"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["key", "value"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gemini-2.5-flash-search": {
|
||||||
|
"name": "Gemini 2.5 Flash (Search)",
|
||||||
"icon": "google-gemini-symbolic",
|
"icon": "google-gemini-symbolic",
|
||||||
"description": qsTr("Online | Google's model\nGives up-to-date information with search."),
|
"description": qsTr("Online | Google's model\nGives up-to-date information with search."),
|
||||||
"homepage": "https://aistudio.google.com",
|
"homepage": "https://aistudio.google.com",
|
||||||
@@ -83,6 +130,51 @@ Singleton {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"gemini-2.5-flash-tools": {
|
||||||
|
"name": "Gemini 2.5 Flash (Tools)",
|
||||||
|
"icon": "google-gemini-symbolic",
|
||||||
|
"description": qsTr("Experimental | Online | Google's model\nCan do a little more but doesn't search quickly"),
|
||||||
|
"homepage": "https://aistudio.google.com",
|
||||||
|
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:streamGenerateContent",
|
||||||
|
"model": "gemini-2.5-flash-preview-05-20",
|
||||||
|
"requires_key": true,
|
||||||
|
"key_id": "gemini",
|
||||||
|
"key_get_link": "https://aistudio.google.com/app/apikey",
|
||||||
|
"key_get_description": qsTr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
|
||||||
|
"api_format": "gemini",
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"functionDeclarations": [
|
||||||
|
{
|
||||||
|
"name": "switch_to_search_mode",
|
||||||
|
"description": "Search the web",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get_shell_config",
|
||||||
|
"description": "Get the desktop shell config file contents",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "set_shell_config",
|
||||||
|
"description": "Set a field in the desktop graphical shell config file. Must only be used after `get_shell_config`.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The key to set, e.g. `bar.borderless`. MUST NOT BE GUESSED, use `get_shell_config` to see what keys are available before setting.",
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The value to set, e.g. `true`"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["key", "value"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"openrouter-llama4-maverick": {
|
"openrouter-llama4-maverick": {
|
||||||
"name": "Llama 4 Maverick",
|
"name": "Llama 4 Maverick",
|
||||||
"icon": "ollama-symbolic",
|
"icon": "ollama-symbolic",
|
||||||
@@ -109,7 +201,7 @@ Singleton {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
property var modelList: Object.keys(root.models)
|
property var modelList: Object.keys(root.models)
|
||||||
property var currentModelId: PersistentStates.ai.model
|
property var currentModelId: PersistentStates?.ai?.model || modelList[0]
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
setModel(currentModelId, false); // Do necessary setup for model
|
setModel(currentModelId, false); // Do necessary setup for model
|
||||||
@@ -205,7 +297,7 @@ Singleton {
|
|||||||
modelId = modelId.toLowerCase()
|
modelId = modelId.toLowerCase()
|
||||||
if (modelList.indexOf(modelId) !== -1) {
|
if (modelList.indexOf(modelId) !== -1) {
|
||||||
PersistentStateManager.setState("ai.model", modelId);
|
PersistentStateManager.setState("ai.model", modelId);
|
||||||
if (feedback) root.addMessage(StringUtils.format(StringUtils.format("Model set to {0}"), models[modelId].name), Ai.interfaceRole)
|
if (feedback) root.addMessage(StringUtils.format(StringUtils.format("Model set to {0}"), models[modelId].name), root.interfaceRole)
|
||||||
if (models[modelId].requires_key) {
|
if (models[modelId].requires_key) {
|
||||||
// If key not there show advice
|
// If key not there show advice
|
||||||
if (root.apiKeysLoaded && (!root.apiKeys[models[modelId].key_id] || root.apiKeys[models[modelId].key_id].length === 0)) {
|
if (root.apiKeysLoaded && (!root.apiKeys[models[modelId].key_id] || root.apiKeys[models[modelId].key_id].length === 0)) {
|
||||||
@@ -271,12 +363,45 @@ Singleton {
|
|||||||
return model.endpoint;
|
return model.endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function markDone() {
|
||||||
|
requester.message.done = true;
|
||||||
|
if (root.postResponseHook) {
|
||||||
|
root.postResponseHook();
|
||||||
|
root.postResponseHook = null; // Reset hook after use
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildGeminiRequestData(model, messages) {
|
function buildGeminiRequestData(model, messages) {
|
||||||
let baseData = {
|
let baseData = {
|
||||||
"contents": messages.filter(message => (message.role != Ai.interfaceRole)).map(message => ({
|
"contents": messages.filter(message => (message.role != Ai.interfaceRole)).map(message => {
|
||||||
"role": message.role,
|
if (message.functionCall != undefined && message.functionCall.length > 0) {
|
||||||
"parts": [{ text: message.content }]
|
return {
|
||||||
})),
|
"role": message.role,
|
||||||
|
"parts": [{
|
||||||
|
functionCall: {
|
||||||
|
"name": message.functionName,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (message.functionResponse != undefined && message.functionResponse.length > 0) {
|
||||||
|
return {
|
||||||
|
"role": message.role,
|
||||||
|
"parts": [{
|
||||||
|
functionResponse: {
|
||||||
|
"name": message.functionName,
|
||||||
|
"response": { "content": message.functionResponse }
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"role": message.role,
|
||||||
|
"parts": [{
|
||||||
|
text: message.content,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}),
|
||||||
"tools": [
|
"tools": [
|
||||||
...model.tools,
|
...model.tools,
|
||||||
],
|
],
|
||||||
@@ -315,6 +440,7 @@ Singleton {
|
|||||||
const endpoint = (apiFormat === "gemini") ? buildGeminiEndpoint(model) : buildOpenAIEndpoint(model);
|
const endpoint = (apiFormat === "gemini") ? buildGeminiEndpoint(model) : buildOpenAIEndpoint(model);
|
||||||
const messageArray = root.messageIDs.map(id => root.messageByID[id]);
|
const messageArray = root.messageIDs.map(id => root.messageByID[id]);
|
||||||
const data = (apiFormat === "gemini") ? buildGeminiRequestData(model, messageArray) : buildOpenAIRequestData(model, messageArray);
|
const data = (apiFormat === "gemini") ? buildGeminiRequestData(model, messageArray) : buildOpenAIRequestData(model, messageArray);
|
||||||
|
// console.log("REQUEST DATA: ", JSON.stringify(data, null, 2));
|
||||||
|
|
||||||
let requestHeaders = {
|
let requestHeaders = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -355,9 +481,20 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseGeminiBuffer() {
|
function parseGeminiBuffer() {
|
||||||
// console.log("BUFFER DATA: ", requester.geminiBuffer);
|
console.log("BUFFER DATA: ", requester.geminiBuffer);
|
||||||
try {
|
try {
|
||||||
|
if (requester.geminiBuffer.length === 0) return;
|
||||||
const dataJson = JSON.parse(requester.geminiBuffer);
|
const dataJson = JSON.parse(requester.geminiBuffer);
|
||||||
|
// Function call handling
|
||||||
|
if (dataJson.candidates[0]?.content?.parts[0]?.functionCall) {
|
||||||
|
const functionCall = dataJson.candidates[0]?.content?.parts[0]?.functionCall;
|
||||||
|
requester.message.functionName = functionCall.name;
|
||||||
|
requester.message.functionCall = functionCall.name;
|
||||||
|
requester.message.content += `\n\n[[ Function: ${functionCall.name}(${JSON.stringify(functionCall.args, null, 2)}) ]]\n`;
|
||||||
|
root.handleGeminiFunctionCall(functionCall.name, functionCall.args);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Normal text response
|
||||||
const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text
|
const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text
|
||||||
requester.message.content += responseContent;
|
requester.message.content += responseContent;
|
||||||
const annotationSources = dataJson.candidates[0]?.groundingMetadata?.groundingChunks?.map(chunk => {
|
const annotationSources = dataJson.candidates[0]?.groundingMetadata?.groundingChunks?.map(chunk => {
|
||||||
@@ -394,7 +531,7 @@ Singleton {
|
|||||||
} else if (line == "]") {
|
} else if (line == "]") {
|
||||||
requester.geminiBuffer += line.slice(0, -1).trim();
|
requester.geminiBuffer += line.slice(0, -1).trim();
|
||||||
parseGeminiBuffer();
|
parseGeminiBuffer();
|
||||||
requester.message.done = true;
|
requester.markDone();
|
||||||
} else if (line.startsWith(",")) { // end of one entry
|
} else if (line.startsWith(",")) { // end of one entry
|
||||||
parseGeminiBuffer();
|
parseGeminiBuffer();
|
||||||
} else {
|
} else {
|
||||||
@@ -412,7 +549,7 @@ Singleton {
|
|||||||
if (!cleanData || cleanData.startsWith(":")) return;
|
if (!cleanData || cleanData.startsWith(":")) return;
|
||||||
|
|
||||||
if (cleanData === "[DONE]") {
|
if (cleanData === "[DONE]") {
|
||||||
requester.message.done = true;
|
requester.markDone();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dataJson = JSON.parse(cleanData);
|
const dataJson = JSON.parse(cleanData);
|
||||||
@@ -438,7 +575,7 @@ Singleton {
|
|||||||
|
|
||||||
requester.message.content += newContent;
|
requester.message.content += newContent;
|
||||||
|
|
||||||
if (dataJson.done) requester.message.done = true;
|
if (dataJson.done) requester.markDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
@@ -467,7 +604,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
requester.message.done = true;
|
requester.markDone();
|
||||||
if (requester.apiFormat == "gemini") requester.parseGeminiBuffer();
|
if (requester.apiFormat == "gemini") requester.parseGeminiBuffer();
|
||||||
|
|
||||||
try { // to parse full response into json for error handling
|
try { // to parse full response into json for error handling
|
||||||
@@ -490,4 +627,60 @@ Singleton {
|
|||||||
requester.makeRequest();
|
requester.makeRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addFunctionOutputMessage(name, output) {
|
||||||
|
const aiMessage = aiMessageComponent.createObject(root, {
|
||||||
|
"role": "user",
|
||||||
|
"content": `[[ Output of ${name} ]]`,
|
||||||
|
"functionName": name,
|
||||||
|
"functionResponse": output,
|
||||||
|
"thinking": false,
|
||||||
|
"done": true,
|
||||||
|
});
|
||||||
|
console.log("Adding function output message: ", JSON.stringify(aiMessage));
|
||||||
|
const id = idForMessage(aiMessage);
|
||||||
|
root.messageIDs = [...root.messageIDs, id];
|
||||||
|
root.messageByID[id] = aiMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGeminiFunctionOutput(name, output) {
|
||||||
|
const functionResponsePart = {
|
||||||
|
"name": name,
|
||||||
|
"response": { "content": output }
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"role": "user",
|
||||||
|
"parts": [{
|
||||||
|
functionResponse: functionResponsePart,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGeminiFunctionCall(name, args) {
|
||||||
|
if (name === "switch_to_search_mode") {
|
||||||
|
if (root.currentModelId === "gemini-2.5-flash-tools") {
|
||||||
|
root.setModel("gemini-2.5-flash-search", false);
|
||||||
|
root.postResponseHook = () => root.setModel("gemini-2.5-flash-tools", false);
|
||||||
|
} else if (root.currentModelId === "gemini-2.0-flash-tools") {
|
||||||
|
root.setModel("gemini-2.0-flash-search", false);
|
||||||
|
root.postResponseHook = () => root.setModel("gemini-2.0-flash-tools", false);
|
||||||
|
}
|
||||||
|
addFunctionOutputMessage(name, qsTr("Switched to search mode. Continue with the user's request."))
|
||||||
|
requester.makeRequest();
|
||||||
|
} else if (name === "get_shell_config") {
|
||||||
|
const configJson = ObjectUtils.toPlainObject(ConfigOptions)
|
||||||
|
addFunctionOutputMessage(name, JSON.stringify(configJson));
|
||||||
|
requester.makeRequest();
|
||||||
|
} else if (name === "set_shell_config") {
|
||||||
|
if (!args.key || !args.value) {
|
||||||
|
addFunctionOutputMessage(name, qsTr("Invalid arguments. Must provide `key` and `value`."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const key = args.key;
|
||||||
|
const value = args.value;
|
||||||
|
ConfigLoader.setLiveConfigValue(key, value);
|
||||||
|
ConfigLoader.saveConfig();
|
||||||
|
}
|
||||||
|
else root.addMessage(qsTr("Unknown function call: {0}"), "assistant");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,7 @@ QtObject {
|
|||||||
property bool done: false
|
property bool done: false
|
||||||
property var annotations: []
|
property var annotations: []
|
||||||
property var annotationSources: []
|
property var annotationSources: []
|
||||||
|
property string functionName
|
||||||
|
property string functionCall
|
||||||
|
property string functionResponse
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import "root:/modules/common"
|
import "root:/modules/common"
|
||||||
import "root:/modules/common/functions/file_utils.js" as FileUtils
|
import "root:/modules/common/functions/file_utils.js" as FileUtils
|
||||||
|
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
||||||
import "root:/modules/common/functions/object_utils.js" as ObjectUtils
|
import "root:/modules/common/functions/object_utils.js" as ObjectUtils
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -38,9 +39,47 @@ Singleton {
|
|||||||
console.error("[ConfigLoader] Error reading file:", e);
|
console.error("[ConfigLoader] Error reading file:", e);
|
||||||
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
|
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLiveConfigValue(nestedKey, value) {
|
||||||
|
let keys = nestedKey.split(".");
|
||||||
|
let obj = ConfigOptions;
|
||||||
|
let parents = [obj];
|
||||||
|
|
||||||
|
// Traverse and collect parent objects
|
||||||
|
for (let i = 0; i < keys.length - 1; ++i) {
|
||||||
|
if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") {
|
||||||
|
obj[keys[i]] = {};
|
||||||
|
}
|
||||||
|
obj = obj[keys[i]];
|
||||||
|
parents.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert value to correct type using JSON.parse when safe
|
||||||
|
let convertedValue = value;
|
||||||
|
if (typeof value === "string") {
|
||||||
|
let trimmed = value.trim();
|
||||||
|
if (trimmed === "true" || trimmed === "false" || !isNaN(Number(trimmed))) {
|
||||||
|
try {
|
||||||
|
convertedValue = JSON.parse(trimmed);
|
||||||
|
} catch (e) {
|
||||||
|
convertedValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(parents.join("."));
|
||||||
|
console.log(`[ConfigLoader] Setting live config value: ${nestedKey} = ${convertedValue}`);
|
||||||
|
obj[keys[keys.length - 1]] = convertedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConfig() {
|
||||||
|
const plainConfig = ObjectUtils.toPlainObject(ConfigOptions)
|
||||||
|
Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${root.filePath}'`)
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: delayedFileRead
|
id: delayedFileRead
|
||||||
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
|
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
|
||||||
@@ -67,8 +106,7 @@ Singleton {
|
|||||||
onLoadFailed: (error) => {
|
onLoadFailed: (error) => {
|
||||||
if(error == FileViewError.FileNotFound) {
|
if(error == FileViewError.FileNotFound) {
|
||||||
console.log("[ConfigLoader] File not found, creating new file.")
|
console.log("[ConfigLoader] File not found, creating new file.")
|
||||||
const plainConfig = ObjectUtils.toPlainObject(ConfigOptions)
|
root.saveConfig()
|
||||||
configFileView.setText(JSON.stringify(plainConfig, null, 2))
|
|
||||||
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration created")}" "${root.filePath}"`)
|
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration created")}" "${root.filePath}"`)
|
||||||
} else {
|
} else {
|
||||||
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
|
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
|
||||||
|
|||||||
Reference in New Issue
Block a user