show prompt responses in dialog

This commit is contained in:
2026-02-07 21:56:47 +02:00
parent 71d957882c
commit 16ab8e4207
4 changed files with 60 additions and 18 deletions

View File

@@ -58,6 +58,7 @@ async fn main() {
.plugin(tauri_plugin_global_shortcut::Builder::new().build()) .plugin(tauri_plugin_global_shortcut::Builder::new().build())
.invoke_handler(tauri::generate_handler![toggle_popup, prompt_llm]) .invoke_handler(tauri::generate_handler![toggle_popup, prompt_llm])
.setup(|app| { .setup(|app| {
/* Auto-hide popup when focus is lost
if let Some(window) = app.get_webview_window("popup") { if let Some(window) = app.get_webview_window("popup") {
let w = window.clone(); let w = window.clone();
window.on_window_event(move |event| { window.on_window_event(move |event| {
@@ -68,6 +69,8 @@ async fn main() {
} }
}) })
} }
*/
// Global shortcut to pull up the popup
let shortcut = Shortcut::new(Some(Modifiers::META), Code::Space); let shortcut = Shortcut::new(Some(Modifiers::META), Code::Space);
app.global_shortcut() app.global_shortcut()
.on_shortcut(shortcut, move |app, _shortcut, event| { .on_shortcut(shortcut, move |app, _shortcut, event| {

View File

@@ -22,8 +22,8 @@
"label": "popup", "label": "popup",
"title": "AI Quick Action", "title": "AI Quick Action",
"url": "/popup", "url": "/popup",
"width": 600, "width": 800,
"height": 260, "height": 400,
"decorations": false, "decorations": false,
"transparent": true, "transparent": true,
"alwaysOnTop": true, "alwaysOnTop": true,

View File

@@ -3,12 +3,19 @@ use leptos::{ev::keydown, html::Input, prelude::*};
use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use wasm_bindgen_futures::spawn_local; use wasm_bindgen_futures::spawn_local;
#[derive(Clone, Debug)]
struct Message {
id: usize,
text: String,
is_user: bool,
}
#[component] #[component]
pub fn Popup() -> impl IntoView { pub fn Popup() -> impl IntoView {
// Prompt signals and and action // Prompt signals and and action
let prompt_input_ref = NodeRef::<Input>::new(); let prompt_input_ref = NodeRef::<Input>::new();
let (prompt_text, set_prompt_text) = signal(String::new()); let (prompt_text, set_prompt_text) = signal(String::new());
let (prompt_result, set_prompt_result) = signal(String::new()); let (messages, set_messages) = signal(Vec::<Message>::new());
// Action that calls the promp daemon // Action that calls the promp daemon
let prompt_action = Action::new_local(|prompt: &String| { let prompt_action = Action::new_local(|prompt: &String| {
let prompt = prompt.clone(); let prompt = prompt.clone();
@@ -25,7 +32,13 @@ pub fn Popup() -> impl IntoView {
// Update the model response div with the prompt result // Update the model response div with the prompt result
Effect::new(move |_| { Effect::new(move |_| {
if let Some(result) = prompt_action.value().get() { if let Some(result) = prompt_action.value().get() {
set_prompt_result.set(result) set_messages.update(|previous| {
previous.push(Message {
id: previous.len(),
text: result,
is_user: false,
});
});
} }
}); });
// Clear the propt text-input when the window loses focus (and is hidden) // Clear the propt text-input when the window loses focus (and is hidden)
@@ -64,15 +77,26 @@ pub fn Popup() -> impl IntoView {
on:input=move |ev| set_prompt_text.set(event_target_value(&ev)) on:input=move |ev| set_prompt_text.set(event_target_value(&ev))
on:keydown=move |ev| { on:keydown=move |ev| {
if ev.key() == "Enter" { if ev.key() == "Enter" {
set_messages.update(|previous| {
previous.push(Message {
id: previous.len(),
text: prompt_text.get(),
is_user: true,
});
});
prompt_action.dispatch(prompt_text.get()); prompt_action.dispatch(prompt_text.get());
//set_prompt_result.update(|s| *s = prompt_action.value());
set_prompt_text.update(|s| *s = "".to_string()); set_prompt_text.update(|s| *s = "".to_string());
} }
} }
prop:value=prompt_text prop:value=prompt_text
/> />
<div class="response-area"> <div class="response-area">
<p>{move || prompt_result.get()}</p> <For each=move || messages.get()
key=|msg| msg.id
let(msg)
>
<div class=if msg.is_user {"msg msg-user"} else {"msg msg-model"}>{msg.text}</div>
</For>
</div> </div>
</main> </main>
} }

View File

@@ -1,23 +1,38 @@
body { body {
background-color: transparent !important; background-color: transparent !important;
margin: 0; margin: 0;
color: #f0f0f0; color: #f0f0f0;
} }
.window-shell { .window-shell {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-sizing: border-box; box-sizing: border-box;
padding: 15px; padding: 15px;
} }
.opaque-bg { .opaque-bg {
background-color: #212121; background-color: #212121;
} }
.rounded-container { .rounded-container {
background-color: #212121; background-color: #212121;
border-radius: 15px; border-radius: 15px;
}
.response-area {
width: 100%;
height: 300px;
overflow-y: scroll;
}
.msg {
margin: 1px;
border: solid 1px #808080;
}
.msg-user {
text-align: end;
} }