kbot - tauri gui - ex
This commit is contained in:
parent
930e69a033
commit
9eb9da4ede
File diff suppressed because one or more lines are too long
@ -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");
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user