kbot - tauri gui - ex

This commit is contained in:
babayaga 2025-09-14 14:18:43 +02:00
parent 930e69a033
commit 9eb9da4ede
5 changed files with 138 additions and 84 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,8 @@
use tauri::Manager;
use serde::{Serialize, Deserialize};
struct CliArgs(std::sync::Mutex<Vec<String>>);
#[derive(Serialize, Deserialize)]
struct Payload {
prompt: String,
@ -19,6 +21,11 @@ fn submit_prompt(prompt: &str, files: Vec<String>, window: tauri::Window) {
let _ = window.app_handle().exit(0);
}
#[tauri::command]
fn get_cli_args(state: tauri::State<'_, CliArgs>) -> Result<Vec<String>, String> {
Ok(state.0.lock().unwrap().clone())
}
#[tauri::command]
fn log_error_to_console(error: &str) {
eprintln!("[WebView ERROR]: {}", error);
@ -26,10 +33,13 @@ fn log_error_to_console(error: &str) {
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let cli_args: Vec<String> = std::env::args().skip(1).collect();
tauri::Builder::default()
.manage(CliArgs(std::sync::Mutex::new(cli_args)))
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![submit_prompt, log_error_to_console])
.invoke_handler(tauri::generate_handler![submit_prompt, log_error_to_console, get_cli_args])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@ -1,76 +1,103 @@
import { useState } from "react";
import { invoke } from "@tauri-apps/api/core";
import { open } from '@tauri-apps/plugin-dialog';
function App() {
const [prompt, setPrompt] = useState("");
const [files, setFiles] = useState<string[]>([]);
async function submit() {
await invoke("submit_prompt", { prompt, files });
}
async function openFilePicker() {
const selected = await open({
multiple: true,
filters: [{
name: 'Images',
extensions: ['png', 'jpeg', 'jpg']
}]
});
if (Array.isArray(selected)) {
setFiles(selected);
}
}
return (
<main className="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4">
<div className="w-full max-w-2xl">
<h1 className="text-4xl font-bold mb-6 text-center">Image Prompt</h1>
<form
className="flex flex-col items-center"
onSubmit={(e) => {
e.preventDefault();
submit();
}}
>
<textarea
id="prompt-input"
onChange={(e) => setPrompt(e.currentTarget.value)}
placeholder="Enter your image prompt..."
className="w-full bg-gray-800 border border-gray-700 rounded-lg p-4 mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-200"
rows={5}
/>
<button
type="button"
onClick={openFilePicker}
className="w-full bg-gray-700 hover:bg-gray-600 text-white font-bold py-3 px-6 rounded-lg mb-4 transition duration-200"
>
Select Images
</button>
{files.length > 0 && (
<div className="w-full bg-gray-800 border border-gray-700 rounded-lg p-4 mb-4">
<h2 className="text-lg font-semibold mb-2">Selected Files:</h2>
<ul className="list-disc list-inside">
{files.map((file, index) => (
<li key={index} className="truncate">{file}</li>
))}
</ul>
</div>
)}
<button
type="submit"
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition duration-200"
>
Generate
</button>
</form>
</div>
</main>
);
}
export default App;
import { useState, useEffect } from "react";
import { invoke, convertFileSrc } from "@tauri-apps/api/core";
import { open } from '@tauri-apps/plugin-dialog';
interface ImageFile {
path: string;
src: string;
}
function App() {
const [prompt, setPrompt] = useState("");
const [files, setFiles] = useState<ImageFile[]>([]);
const addFiles = (newFiles: string[]) => {
const uniqueNewFiles = newFiles.filter(nf => !files.some(f => f.path === nf));
const imageFiles = uniqueNewFiles.map(path => ({ path, src: convertFileSrc(path) }));
setFiles(prevFiles => [...prevFiles, ...imageFiles]);
};
useEffect(() => {
const fetchCliArgs = async () => {
try {
const cliFiles = await invoke<string[]>('get_cli_args');
if (cliFiles && cliFiles.length > 0) {
addFiles(cliFiles);
}
} catch (e) {
console.error("Failed to get CLI arguments:", e);
}
};
fetchCliArgs();
}, []);
async function submit() {
await invoke("submit_prompt", { prompt, files: files.map(f => f.path) });
}
async function openFilePicker() {
const selected = await open({
multiple: true,
filters: [{
name: 'Images',
extensions: ['png', 'jpeg', 'jpg']
}]
});
if (Array.isArray(selected)) {
addFiles(selected);
}
}
return (
<main className="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4">
<div className="w-full max-w-4xl">
<h1 className="text-4xl font-bold mb-6 text-center">Image Prompt</h1>
<form
className="flex flex-col items-center"
onSubmit={(e) => {
e.preventDefault();
submit();
}}
>
<textarea
id="prompt-input"
onChange={(e) => setPrompt(e.currentTarget.value)}
placeholder="Enter your image prompt..."
className="w-full bg-gray-800 border border-gray-700 rounded-lg p-4 mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-200"
rows={3}
/>
<button
type="button"
onClick={openFilePicker}
className="w-full bg-gray-700 hover:bg-gray-600 text-white font-bold py-3 px-6 rounded-lg mb-4 transition duration-200"
>
Select Images
</button>
{files.length > 0 && (
<div className="w-full bg-gray-800 border border-gray-700 rounded-lg p-4 mb-4">
<h2 className="text-lg font-semibold mb-2">Selected Images:</h2>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
{files.map((file) => (
<div key={file.path} className="relative aspect-square">
<img src={file.src} alt={file.path} className="object-cover w-full h-full rounded-md" />
</div>
))}
</div>
</div>
)}
<button
type="submit"
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition duration-200"
>
Generate
</button>
</form>
</div>
</main>
);
}
export default App;

View File

@ -22,5 +22,11 @@
"command": "kbot-d",
"args": "image --alt=true --prompt=\"$(FullName)\" --dst=\"&{SRC_DIR}/&{SRC_NAME}.png\"",
"description": "Create an image from a text file"
},
"edit-image-gui": {
"name": "Edit Image GUI",
"command": "kbot-d",
"args": "image --alt=true --gui=true --include=\"$(FullName)\" --dst=\"&{SRC_DIR}/&{SRC_NAME}.png\"",
"description": "Edit an image using a GUI"
}
}

View File

@ -33,7 +33,7 @@ export const ImageOptionsSchema = () => {
});
}
async function launchGuiAndGetPrompt(): Promise<string | null> {
async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
return new Promise((resolve, reject) => {
const guiAppPath = path.join(process.cwd(), 'gui', 'tauri-app', 'src-tauri', 'target', 'release', 'tauri-app.exe');
@ -41,7 +41,13 @@ async function launchGuiAndGetPrompt(): Promise<string | null> {
return reject(new Error(`GUI application not found at: ${guiAppPath}. Please build it first by running 'npm run tauri build' in 'gui/tauri-app'.`));
}
const tauriProcess = spawn(guiAppPath, [], { stdio: ['pipe', 'pipe', 'pipe'] });
const args: string[] = [];
if (argv.include) {
const includes = Array.isArray(argv.include) ? argv.include : [argv.include];
args.push(...includes);
}
const tauriProcess = spawn(guiAppPath, args, { stdio: ['pipe', 'pipe', 'pipe'] });
let output = '';
tauriProcess.stdout.on('data', (data) => {
@ -72,7 +78,7 @@ export const imageCommand = async (argv: any) => {
if (argv.gui) {
try {
const guiOutput = await launchGuiAndGetPrompt();
const guiOutput = await launchGuiAndGetPrompt(argv);
if (guiOutput) {
const payload = JSON.parse(guiOutput);
argv.prompt = payload.prompt;