Compare commits
8 Commits
v0.12.3-de
...
ready/ui-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f59e66065 | ||
|
|
51ac7f152d | ||
|
|
df74c06ba2 | ||
|
|
5f144ca24d | ||
|
|
de66b1349a | ||
|
|
3d888fee64 | ||
|
|
1abcc8ee3c | ||
|
|
d0d5c309e6 |
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"minServerVersion": "0.12.3",
|
|
||||||
"latestUIVersion": "0.12.3-rtl",
|
|
||||||
"uiPackageURL": "https://github.com/MusiCode1/CodeNomad/releases/download/v0.12.3-rtl/codenomad-ui-rtl.zip",
|
|
||||||
"sha256": "a2ce1aaa04345a2f9ca9d3c3149567867f3a5e477cf6eb269381e6dc1bec7ca2"
|
|
||||||
}
|
|
||||||
67
packages/tauri-app/Cargo.lock
generated
67
packages/tauri-app/Cargo.lock
generated
@@ -473,7 +473,6 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-global-shortcut",
|
|
||||||
"tauri-plugin-notification",
|
"tauri-plugin-notification",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
@@ -1351,16 +1350,6 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gethostname"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
|
||||||
dependencies = [
|
|
||||||
"rustix 1.1.4",
|
|
||||||
"windows-link 0.2.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
@@ -1493,24 +1482,6 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "global-hotkey"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-channel",
|
|
||||||
"keyboard-types",
|
|
||||||
"objc2",
|
|
||||||
"objc2-app-kit",
|
|
||||||
"once_cell",
|
|
||||||
"serde",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
"x11rb",
|
|
||||||
"xkeysym",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gobject-sys"
|
name = "gobject-sys"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@@ -4084,21 +4055,6 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-plugin-global-shortcut"
|
|
||||||
version = "2.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "424af23c7e88d05e4a1a6fc2c7be077912f8c76bd7900fd50aa2b7cbf5a2c405"
|
|
||||||
dependencies = [
|
|
||||||
"global-hotkey",
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tauri",
|
|
||||||
"tauri-plugin",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-notification"
|
name = "tauri-plugin-notification"
|
||||||
version = "2.3.3"
|
version = "2.3.3"
|
||||||
@@ -5779,29 +5735,6 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "x11rb"
|
|
||||||
version = "0.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
|
||||||
dependencies = [
|
|
||||||
"gethostname",
|
|
||||||
"rustix 1.1.4",
|
|
||||||
"x11rb-protocol",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "x11rb-protocol"
|
|
||||||
version = "0.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xkeysym"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ keepawake = "0.6"
|
|||||||
tauri-plugin-dialog = "2"
|
tauri-plugin-dialog = "2"
|
||||||
dirs = "5"
|
dirs = "5"
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
tauri-plugin-global-shortcut = "2"
|
|
||||||
url = "2"
|
url = "2"
|
||||||
tauri-plugin-notification = "2"
|
tauri-plugin-notification = "2"
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2378,72 +2378,6 @@
|
|||||||
"const": "dialog:deny-save",
|
"const": "dialog:deny-save",
|
||||||
"markdownDescription": "Denies the save command without any pre-configured scope."
|
"markdownDescription": "Denies the save command without any pre-configured scope."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"description": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:default",
|
|
||||||
"markdownDescription": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the is_registered command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:allow-is-registered",
|
|
||||||
"markdownDescription": "Enables the is_registered command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the register command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:allow-register",
|
|
||||||
"markdownDescription": "Enables the register command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the register_all command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:allow-register-all",
|
|
||||||
"markdownDescription": "Enables the register_all command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the unregister command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:allow-unregister",
|
|
||||||
"markdownDescription": "Enables the unregister command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the unregister_all command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:allow-unregister-all",
|
|
||||||
"markdownDescription": "Enables the unregister_all command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the is_registered command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:deny-is-registered",
|
|
||||||
"markdownDescription": "Denies the is_registered command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the register command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:deny-register",
|
|
||||||
"markdownDescription": "Denies the register command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the register_all command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:deny-register-all",
|
|
||||||
"markdownDescription": "Denies the register_all command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the unregister command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:deny-unregister",
|
|
||||||
"markdownDescription": "Denies the unregister command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the unregister_all command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "global-shortcut:deny-unregister-all",
|
|
||||||
"markdownDescription": "Denies the unregister_all command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -8,14 +8,10 @@ use serde::Deserialize;
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder};
|
use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder};
|
||||||
use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
|
use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
|
||||||
use tauri::webview::Webview;
|
use tauri::webview::Webview;
|
||||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WindowEvent, Wry};
|
use tauri::{AppHandle, Emitter, Manager, Runtime, Wry};
|
||||||
use tauri_plugin_global_shortcut::{
|
|
||||||
Code as ShortcutCode, GlobalShortcutExt, Shortcut, ShortcutState,
|
|
||||||
};
|
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -29,10 +25,6 @@ use std::os::windows::ffi::OsStrExt;
|
|||||||
use windows_sys::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
|
use windows_sys::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
|
||||||
|
|
||||||
static QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
|
static QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
|
||||||
const DEFAULT_ZOOM_LEVEL: f64 = 1.0;
|
|
||||||
const ZOOM_STEP: f64 = 0.2;
|
|
||||||
const MIN_ZOOM_LEVEL: f64 = 0.2;
|
|
||||||
const MAX_ZOOM_LEVEL: f64 = 5.0;
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
|
const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
|
||||||
@@ -40,7 +32,6 @@ const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
|
|||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub manager: CliProcessManager,
|
pub manager: CliProcessManager,
|
||||||
pub wake_lock: Mutex<Option<KeepAwake>>,
|
pub wake_lock: Mutex<Option<KeepAwake>>,
|
||||||
pub zoom_level: Mutex<f64>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
@@ -166,83 +157,6 @@ fn emit_folder_drop_event(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clamp_zoom_level(value: f64) -> f64 {
|
|
||||||
value.clamp(MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_main_window_zoom(app_handle: &AppHandle, next_zoom: f64) {
|
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
|
||||||
let normalized = clamp_zoom_level(next_zoom);
|
|
||||||
if window.set_zoom(normalized).is_ok() {
|
|
||||||
if let Ok(mut zoom_level) = app_handle.state::<AppState>().zoom_level.lock() {
|
|
||||||
*zoom_level = normalized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reload_main_window(app_handle: &AppHandle) {
|
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
|
||||||
let _ = window.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn force_reload_main_window(app_handle: &AppHandle) {
|
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
|
||||||
if let Ok(mut url) = window.url() {
|
|
||||||
if should_allow_internal(&url) {
|
|
||||||
let reload_token = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.as_millis()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let existing_pairs: Vec<(String, String)> = url
|
|
||||||
.query_pairs()
|
|
||||||
.into_owned()
|
|
||||||
.filter(|(key, _)| key != "__codenomad_force_reload")
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut pairs = url.query_pairs_mut();
|
|
||||||
pairs.clear();
|
|
||||||
for (key, value) in existing_pairs {
|
|
||||||
pairs.append_pair(&key, &value);
|
|
||||||
}
|
|
||||||
pairs.append_pair("__codenomad_force_reload", &reload_token);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = window.navigate(url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = window.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_fullscreen_window(app_handle: &AppHandle) {
|
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
|
||||||
let next_fullscreen = !window.is_fullscreen().unwrap_or(false);
|
|
||||||
let _ = window.set_fullscreen(next_fullscreen);
|
|
||||||
if cfg!(not(target_os = "macos")) {
|
|
||||||
if next_fullscreen {
|
|
||||||
let _ = window.hide_menu();
|
|
||||||
} else {
|
|
||||||
let _ = window.show_menu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fullscreen_shortcut() -> Option<Shortcut> {
|
|
||||||
if cfg!(target_os = "macos") {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(Shortcut::new(None, ShortcutCode::F11))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn set_windows_app_user_model_id() {
|
fn set_windows_app_user_model_id() {
|
||||||
let app_id: Vec<u16> = OsStr::new(WINDOWS_APP_USER_MODEL_ID)
|
let app_id: Vec<u16> = OsStr::new(WINDOWS_APP_USER_MODEL_ID)
|
||||||
@@ -267,48 +181,15 @@ fn main() {
|
|||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.plugin(
|
|
||||||
tauri_plugin_global_shortcut::Builder::new()
|
|
||||||
.with_handler(|app, shortcut, event| {
|
|
||||||
if event.state() != ShortcutState::Pressed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if fullscreen_shortcut().as_ref() == Some(shortcut) {
|
|
||||||
toggle_fullscreen_window(app);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.plugin(tauri_plugin_notification::init())
|
.plugin(tauri_plugin_notification::init())
|
||||||
.plugin(navigation_guard)
|
.plugin(navigation_guard)
|
||||||
.manage(AppState {
|
.manage(AppState {
|
||||||
manager: CliProcessManager::new(),
|
manager: CliProcessManager::new(),
|
||||||
wake_lock: Mutex::new(None),
|
wake_lock: Mutex::new(None),
|
||||||
zoom_level: Mutex::new(DEFAULT_ZOOM_LEVEL),
|
|
||||||
})
|
})
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
set_windows_app_user_model_id();
|
set_windows_app_user_model_id();
|
||||||
build_menu(&app.handle())?;
|
build_menu(&app.handle())?;
|
||||||
if let Some(shortcut) = fullscreen_shortcut() {
|
|
||||||
let shortcut_manager = app.handle().global_shortcut();
|
|
||||||
let _ = shortcut_manager.register(shortcut.clone());
|
|
||||||
|
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
|
||||||
let app_handle = app.handle().clone();
|
|
||||||
window.on_window_event(move |event| {
|
|
||||||
if let WindowEvent::Focused(focused) = event {
|
|
||||||
let shortcut_manager = app_handle.global_shortcut();
|
|
||||||
if *focused {
|
|
||||||
let _ = shortcut_manager.register(shortcut.clone());
|
|
||||||
} else {
|
|
||||||
let _ = shortcut_manager.unregister(shortcut.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let dev_mode = is_dev_mode();
|
let dev_mode = is_dev_mode();
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
let manager = app.state::<AppState>().manager.clone();
|
let manager = app.state::<AppState>().manager.clone();
|
||||||
@@ -333,42 +214,36 @@ fn main() {
|
|||||||
let _ = window.emit("menu:newInstance", ());
|
let _ = window.emit("menu:newInstance", ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"close" => {
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let _ = window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
"quit" => {
|
"quit" => {
|
||||||
app_handle.exit(0);
|
app_handle.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// View menu
|
// View menu
|
||||||
"reload" => {
|
"reload" => {
|
||||||
reload_main_window(app_handle);
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let _ = window.eval("window.location.reload()");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"force_reload" => {
|
"force_reload" => {
|
||||||
force_reload_main_window(app_handle);
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let _ = window.eval("window.location.reload(true)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"toggle_devtools" => {
|
"toggle_devtools" => {
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
if window.is_devtools_open() {
|
|
||||||
window.close_devtools();
|
|
||||||
} else {
|
|
||||||
window.open_devtools();
|
window.open_devtools();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
"reset_zoom" => {
|
|
||||||
set_main_window_zoom(app_handle, DEFAULT_ZOOM_LEVEL);
|
|
||||||
}
|
|
||||||
"zoom_in" => {
|
|
||||||
if let Ok(zoom_level) = app_handle.state::<AppState>().zoom_level.lock() {
|
|
||||||
set_main_window_zoom(app_handle, *zoom_level + ZOOM_STEP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"zoom_out" => {
|
|
||||||
if let Ok(zoom_level) = app_handle.state::<AppState>().zoom_level.lock() {
|
|
||||||
set_main_window_zoom(app_handle, *zoom_level - ZOOM_STEP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"toggle_fullscreen" => {
|
"toggle_fullscreen" => {
|
||||||
toggle_fullscreen_window(app_handle);
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let _ = window.set_fullscreen(!window.is_fullscreen().unwrap_or(false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window menu
|
// Window menu
|
||||||
@@ -382,11 +257,6 @@ fn main() {
|
|||||||
let _ = window.maximize();
|
let _ = window.maximize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"close_window" => {
|
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
|
||||||
let _ = window.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// App menu (macOS)
|
// App menu (macOS)
|
||||||
"about" => {
|
"about" => {
|
||||||
@@ -474,7 +344,6 @@ fn main() {
|
|||||||
|
|
||||||
fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||||
let is_mac = cfg!(target_os = "macos");
|
let is_mac = cfg!(target_os = "macos");
|
||||||
let is_linux = cfg!(target_os = "linux");
|
|
||||||
|
|
||||||
// Create submenus
|
// Create submenus
|
||||||
let mut submenus = Vec::new();
|
let mut submenus = Vec::new();
|
||||||
@@ -502,74 +371,16 @@ fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
|||||||
Some("CmdOrCtrl+N"),
|
Some("CmdOrCtrl+N"),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let file_menu = if is_mac {
|
let file_menu = SubmenuBuilder::new(app, "File")
|
||||||
SubmenuBuilder::new(app, "File")
|
|
||||||
.item(&new_instance_item)
|
.item(&new_instance_item)
|
||||||
.separator()
|
.separator()
|
||||||
.close_window()
|
.text(
|
||||||
.build()?
|
if is_mac { "close" } else { "quit" },
|
||||||
} else {
|
if is_mac { "Close" } else { "Quit" },
|
||||||
SubmenuBuilder::new(app, "File")
|
)
|
||||||
.item(&new_instance_item)
|
.build()?;
|
||||||
.separator()
|
|
||||||
.text("quit", "Quit")
|
|
||||||
.build()?
|
|
||||||
};
|
|
||||||
submenus.push(file_menu);
|
submenus.push(file_menu);
|
||||||
|
|
||||||
let reload_item = MenuItem::with_id(app, "reload", "Reload", true, Some("CmdOrCtrl+R"))?;
|
|
||||||
let force_reload_item = MenuItem::with_id(
|
|
||||||
app,
|
|
||||||
"force_reload",
|
|
||||||
"Force Reload",
|
|
||||||
true,
|
|
||||||
Some("CmdOrCtrl+Shift+R"),
|
|
||||||
)?;
|
|
||||||
let toggle_devtools_item = MenuItem::with_id(
|
|
||||||
app,
|
|
||||||
"toggle_devtools",
|
|
||||||
"Toggle Developer Tools",
|
|
||||||
true,
|
|
||||||
Some("Alt+CmdOrCtrl+I"),
|
|
||||||
)?;
|
|
||||||
let reset_zoom_item =
|
|
||||||
MenuItem::with_id(app, "reset_zoom", "Actual Size", true, Some("CmdOrCtrl+0"))?;
|
|
||||||
let zoom_in_item = MenuItem::with_id(
|
|
||||||
app,
|
|
||||||
"zoom_in",
|
|
||||||
if is_mac { "Zoom In" } else { "Zoom In\tCtrl++" },
|
|
||||||
true,
|
|
||||||
None::<&str>,
|
|
||||||
)?;
|
|
||||||
let zoom_out_item = MenuItem::with_id(
|
|
||||||
app,
|
|
||||||
"zoom_out",
|
|
||||||
if is_mac {
|
|
||||||
"Zoom Out"
|
|
||||||
} else {
|
|
||||||
"Zoom Out\tCtrl+-"
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
None::<&str>,
|
|
||||||
)?;
|
|
||||||
let toggle_fullscreen_item = MenuItem::with_id(
|
|
||||||
app,
|
|
||||||
"toggle_fullscreen",
|
|
||||||
if is_mac {
|
|
||||||
"Toggle Full Screen"
|
|
||||||
} else {
|
|
||||||
"Toggle Full Screen\tF11"
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
if is_mac {
|
|
||||||
Some("Ctrl+Cmd+F")
|
|
||||||
} else {
|
|
||||||
None::<&str>
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
let close_window_item =
|
|
||||||
MenuItem::with_id(app, "close_window", "Close", true, Some("CmdOrCtrl+W"))?;
|
|
||||||
|
|
||||||
// Edit menu with predefined items for standard functionality
|
// Edit menu with predefined items for standard functionality
|
||||||
let edit_menu = SubmenuBuilder::new(app, "Edit")
|
let edit_menu = SubmenuBuilder::new(app, "Edit")
|
||||||
.undo()
|
.undo()
|
||||||
@@ -585,39 +396,20 @@ fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
|||||||
|
|
||||||
// View menu
|
// View menu
|
||||||
let view_menu = SubmenuBuilder::new(app, "View")
|
let view_menu = SubmenuBuilder::new(app, "View")
|
||||||
.item(&reload_item)
|
.text("reload", "Reload")
|
||||||
.item(&force_reload_item)
|
.text("force_reload", "Force Reload")
|
||||||
.item(&toggle_devtools_item)
|
.text("toggle_devtools", "Toggle Developer Tools")
|
||||||
.separator()
|
.separator()
|
||||||
.item(&reset_zoom_item)
|
|
||||||
.item(&zoom_in_item)
|
|
||||||
.item(&zoom_out_item)
|
|
||||||
.separator()
|
.separator()
|
||||||
.item(&toggle_fullscreen_item)
|
.text("toggle_fullscreen", "Toggle Full Screen")
|
||||||
.build()?;
|
.build()?;
|
||||||
submenus.push(view_menu);
|
submenus.push(view_menu);
|
||||||
|
|
||||||
// Window menu
|
// Window menu
|
||||||
let window_menu = if is_linux {
|
let window_menu = SubmenuBuilder::new(app, "Window")
|
||||||
SubmenuBuilder::new(app, "Window")
|
|
||||||
.text("minimize", "Minimize")
|
.text("minimize", "Minimize")
|
||||||
.text("zoom", "Zoom")
|
.text("zoom", "Zoom")
|
||||||
.separator()
|
.build()?;
|
||||||
.item(&close_window_item)
|
|
||||||
.build()?
|
|
||||||
} else if is_mac {
|
|
||||||
SubmenuBuilder::new(app, "Window")
|
|
||||||
.minimize()
|
|
||||||
.maximize()
|
|
||||||
.build()?
|
|
||||||
} else {
|
|
||||||
SubmenuBuilder::new(app, "Window")
|
|
||||||
.minimize()
|
|
||||||
.maximize()
|
|
||||||
.separator()
|
|
||||||
.close_window()
|
|
||||||
.build()?
|
|
||||||
};
|
|
||||||
submenus.push(window_menu);
|
submenus.push(window_menu);
|
||||||
|
|
||||||
// Build the main menu with all submenus
|
// Build the main menu with all submenus
|
||||||
|
|||||||
@@ -404,7 +404,6 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
class="text-sm font-medium text-primary whitespace-normal break-words transition-colors"
|
class="text-sm font-medium text-primary whitespace-normal break-words transition-colors"
|
||||||
dir="auto"
|
|
||||||
classList={{
|
classList={{
|
||||||
"text-accent": isFocused(),
|
"text-accent": isFocused(),
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
Show,
|
Show,
|
||||||
Suspense,
|
|
||||||
createEffect,
|
createEffect,
|
||||||
createMemo,
|
createMemo,
|
||||||
createSignal,
|
createSignal,
|
||||||
lazy,
|
|
||||||
onCleanup,
|
onCleanup,
|
||||||
type Accessor,
|
type Accessor,
|
||||||
type Component,
|
type Component,
|
||||||
@@ -22,6 +20,11 @@ import type { Session } from "../../../../types/session"
|
|||||||
import type { DrawerViewState } from "../types"
|
import type { DrawerViewState } from "../types"
|
||||||
import type { DiffContextMode, DiffViewMode, DiffWordWrapMode, RightPanelTab } from "./types"
|
import type { DiffContextMode, DiffViewMode, DiffWordWrapMode, RightPanelTab } from "./types"
|
||||||
|
|
||||||
|
import ChangesTab from "./tabs/ChangesTab"
|
||||||
|
import FilesTab from "./tabs/FilesTab"
|
||||||
|
import GitChangesTab from "./tabs/GitChangesTab"
|
||||||
|
import StatusTab from "./tabs/StatusTab"
|
||||||
|
|
||||||
import { getDefaultWorktreeSlug, getOrCreateWorktreeClient, getWorktreeSlugForSession } from "../../../../stores/worktrees"
|
import { getDefaultWorktreeSlug, getOrCreateWorktreeClient, getWorktreeSlugForSession } from "../../../../stores/worktrees"
|
||||||
import { requestData } from "../../../../lib/opencode-api"
|
import { requestData } from "../../../../lib/opencode-api"
|
||||||
import { buildUnifiedDiffFromSdkPatch, tryReverseApplyUnifiedDiff } from "../../../../lib/unified-diff-reverse"
|
import { buildUnifiedDiffFromSdkPatch, tryReverseApplyUnifiedDiff } from "../../../../lib/unified-diff-reverse"
|
||||||
@@ -46,15 +49,6 @@ import {
|
|||||||
readStoredRightPanelTab,
|
readStoredRightPanelTab,
|
||||||
} from "../storage"
|
} from "../storage"
|
||||||
|
|
||||||
const LazyChangesTab = lazy(() => import("./tabs/ChangesTab"))
|
|
||||||
const LazyGitChangesTab = lazy(() => import("./tabs/GitChangesTab"))
|
|
||||||
const LazyFilesTab = lazy(() => import("./tabs/FilesTab"))
|
|
||||||
const LazyStatusTab = lazy(() => import("./tabs/StatusTab"))
|
|
||||||
|
|
||||||
function RightPanelTabFallback() {
|
|
||||||
return <div class="flex-1 min-h-0" />
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RightPanelProps {
|
interface RightPanelProps {
|
||||||
t: (key: string, vars?: Record<string, any>) => string
|
t: (key: string, vars?: Record<string, any>) => string
|
||||||
|
|
||||||
@@ -571,13 +565,6 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
void loadBrowserEntries(browserPath())
|
void loadBrowserEntries(browserPath())
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (rightPanelTab() === "files") return
|
|
||||||
setBrowserSelectedContent(null)
|
|
||||||
setBrowserSelectedLoading(false)
|
|
||||||
setBrowserSelectedError(null)
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (rightPanelTab() !== "git-changes") return
|
if (rightPanelTab() !== "git-changes") return
|
||||||
if (gitStatusLoading()) return
|
if (gitStatusLoading()) return
|
||||||
@@ -585,14 +572,6 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
void loadGitStatus()
|
void loadGitStatus()
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (rightPanelTab() === "git-changes") return
|
|
||||||
setGitSelectedBefore(null)
|
|
||||||
setGitSelectedAfter(null)
|
|
||||||
setGitSelectedLoading(false)
|
|
||||||
setGitSelectedError(null)
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleSelectChangesFile = (file: string, closeList: boolean) => {
|
const handleSelectChangesFile = (file: string, closeList: boolean) => {
|
||||||
setSelectedFile(file)
|
setSelectedFile(file)
|
||||||
if (closeList) {
|
if (closeList) {
|
||||||
@@ -759,8 +738,7 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
|
|
||||||
<div class="flex-1 overflow-y-auto">
|
<div class="flex-1 overflow-y-auto">
|
||||||
<Show when={rightPanelTab() === "changes"}>
|
<Show when={rightPanelTab() === "changes"}>
|
||||||
<Suspense fallback={<RightPanelTabFallback />}>
|
<ChangesTab
|
||||||
<LazyChangesTab
|
|
||||||
t={props.t}
|
t={props.t}
|
||||||
instanceId={props.instanceId}
|
instanceId={props.instanceId}
|
||||||
activeSessionId={props.activeSessionId}
|
activeSessionId={props.activeSessionId}
|
||||||
@@ -780,12 +758,10 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
onResizeTouchStart={handleSplitResizeTouchStart("changes")}
|
onResizeTouchStart={handleSplitResizeTouchStart("changes")}
|
||||||
isPhoneLayout={props.isPhoneLayout}
|
isPhoneLayout={props.isPhoneLayout}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={rightPanelTab() === "git-changes"}>
|
<Show when={rightPanelTab() === "git-changes"}>
|
||||||
<Suspense fallback={<RightPanelTabFallback />}>
|
<GitChangesTab
|
||||||
<LazyGitChangesTab
|
|
||||||
t={props.t}
|
t={props.t}
|
||||||
activeSessionId={props.activeSessionId}
|
activeSessionId={props.activeSessionId}
|
||||||
entries={gitStatusEntries}
|
entries={gitStatusEntries}
|
||||||
@@ -804,7 +780,7 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
onViewModeChange={setDiffViewMode}
|
onViewModeChange={setDiffViewMode}
|
||||||
onContextModeChange={setDiffContextMode}
|
onContextModeChange={setDiffContextMode}
|
||||||
onWordWrapModeChange={setDiffWordWrapMode}
|
onWordWrapModeChange={setDiffWordWrapMode}
|
||||||
onOpenFile={(path: string) => void openGitFile(path)}
|
onOpenFile={(path) => void openGitFile(path)}
|
||||||
onRefresh={() => void refreshGitStatus()}
|
onRefresh={() => void refreshGitStatus()}
|
||||||
listOpen={gitChangesListOpen}
|
listOpen={gitChangesListOpen}
|
||||||
onToggleList={toggleGitList}
|
onToggleList={toggleGitList}
|
||||||
@@ -813,12 +789,10 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
onResizeTouchStart={handleSplitResizeTouchStart("git-changes")}
|
onResizeTouchStart={handleSplitResizeTouchStart("git-changes")}
|
||||||
isPhoneLayout={props.isPhoneLayout}
|
isPhoneLayout={props.isPhoneLayout}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={rightPanelTab() === "files"}>
|
<Show when={rightPanelTab() === "files"}>
|
||||||
<Suspense fallback={<RightPanelTabFallback />}>
|
<FilesTab
|
||||||
<LazyFilesTab
|
|
||||||
t={props.t}
|
t={props.t}
|
||||||
browserPath={browserPath}
|
browserPath={browserPath}
|
||||||
browserEntries={browserEntries}
|
browserEntries={browserEntries}
|
||||||
@@ -830,8 +804,8 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
browserSelectedError={browserSelectedError}
|
browserSelectedError={browserSelectedError}
|
||||||
parentPath={browserParentPath}
|
parentPath={browserParentPath}
|
||||||
scopeKey={browserScopeKey}
|
scopeKey={browserScopeKey}
|
||||||
onLoadEntries={(path: string) => void loadBrowserEntries(path)}
|
onLoadEntries={(path) => void loadBrowserEntries(path)}
|
||||||
onOpenFile={(path: string) => void openBrowserFile(path)}
|
onOpenFile={(path) => void openBrowserFile(path)}
|
||||||
onRefresh={() => void refreshFilesTab()}
|
onRefresh={() => void refreshFilesTab()}
|
||||||
listOpen={filesListOpen}
|
listOpen={filesListOpen}
|
||||||
onToggleList={toggleFilesList}
|
onToggleList={toggleFilesList}
|
||||||
@@ -840,12 +814,10 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
onResizeTouchStart={handleSplitResizeTouchStart("files")}
|
onResizeTouchStart={handleSplitResizeTouchStart("files")}
|
||||||
isPhoneLayout={props.isPhoneLayout}
|
isPhoneLayout={props.isPhoneLayout}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={rightPanelTab() === "status"}>
|
<Show when={rightPanelTab() === "status"}>
|
||||||
<Suspense fallback={<RightPanelTabFallback />}>
|
<StatusTab
|
||||||
<LazyStatusTab
|
|
||||||
t={props.t}
|
t={props.t}
|
||||||
instanceId={props.instanceId}
|
instanceId={props.instanceId}
|
||||||
instance={props.instance}
|
instance={props.instance}
|
||||||
@@ -861,7 +833,6 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
onExpandedItemsChange={handleAccordionChange}
|
onExpandedItemsChange={handleAccordionChange}
|
||||||
onOpenChangesTab={openChangesTabFromStatus}
|
onOpenChangesTab={openChangesTabFromStatus}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -244,7 +244,6 @@ export function Markdown(props: MarkdownProps) {
|
|||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
class="markdown-body"
|
class="markdown-body"
|
||||||
dir="auto"
|
|
||||||
data-view="markdown"
|
data-view="markdown"
|
||||||
data-part-id={resolved().partId}
|
data-part-id={resolved().partId}
|
||||||
data-markdown-theme={resolved().themeKey}
|
data-markdown-theme={resolved().themeKey}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { For, Match, Show, Suspense, Switch, createEffect, createMemo, createSignal, lazy, onCleanup, untrack } from "solid-js"
|
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup, untrack } from "solid-js"
|
||||||
import { ChevronsDownUp, ChevronsUpDown, ExternalLink, FoldVertical, ListStart, Trash } from "lucide-solid"
|
import { ChevronsDownUp, ChevronsUpDown, ExternalLink, FoldVertical, ListStart, Trash } from "lucide-solid"
|
||||||
import MessageItem from "./message-item"
|
import MessageItem from "./message-item"
|
||||||
|
import ToolCall from "./tool-call"
|
||||||
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
||||||
import type { ClientPart, MessageInfo } from "../types/message"
|
import type { ClientPart, MessageInfo } from "../types/message"
|
||||||
import { partHasRenderableText } from "../types/message"
|
import { partHasRenderableText } from "../types/message"
|
||||||
@@ -28,12 +29,6 @@ const USER_BORDER_COLOR = "var(--message-user-border)"
|
|||||||
const ASSISTANT_BORDER_COLOR = "var(--message-assistant-border)"
|
const ASSISTANT_BORDER_COLOR = "var(--message-assistant-border)"
|
||||||
const TOOL_BORDER_COLOR = "var(--message-tool-border)"
|
const TOOL_BORDER_COLOR = "var(--message-tool-border)"
|
||||||
|
|
||||||
const LazyToolCall = lazy(() => import("./tool-call"))
|
|
||||||
|
|
||||||
function ToolCallFallback() {
|
|
||||||
return <div class="tool-call tool-call-loading" />
|
|
||||||
}
|
|
||||||
|
|
||||||
type ToolCallPart = Extract<ClientPart, { type: "tool" }>
|
type ToolCallPart = Extract<ClientPart, { type: "tool" }>
|
||||||
|
|
||||||
|
|
||||||
@@ -505,8 +500,7 @@ function ToolCallItem(props: ToolCallItemProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Suspense fallback={<ToolCallFallback />}>
|
<ToolCall
|
||||||
<LazyToolCall
|
|
||||||
toolCall={resolvedToolPart()}
|
toolCall={resolvedToolPart()}
|
||||||
toolCallId={props.partId}
|
toolCallId={props.partId}
|
||||||
messageId={props.messageId}
|
messageId={props.messageId}
|
||||||
@@ -516,7 +510,6 @@ function ToolCallItem(props: ToolCallItemProps) {
|
|||||||
sessionId={props.sessionId}
|
sessionId={props.sessionId}
|
||||||
onContentRendered={props.onContentRendered}
|
onContentRendered={props.onContentRendered}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
@@ -909,7 +902,6 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
selectedMessageIds={props.selectedMessageIds}
|
selectedMessageIds={props.selectedMessageIds}
|
||||||
onToggleSelectedMessage={props.onToggleSelectedMessage}
|
onToggleSelectedMessage={props.onToggleSelectedMessage}
|
||||||
onContentRendered={props.onContentRendered}
|
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
@@ -1288,7 +1280,6 @@ interface ReasoningCardProps {
|
|||||||
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
selectedMessageIds?: () => Set<string>
|
selectedMessageIds?: () => Set<string>
|
||||||
onToggleSelectedMessage?: (messageId: string, selected: boolean) => void
|
onToggleSelectedMessage?: (messageId: string, selected: boolean) => void
|
||||||
onContentRendered?: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ReasoningCard(props: ReasoningCardProps) {
|
function ReasoningCard(props: ReasoningCardProps) {
|
||||||
@@ -1297,25 +1288,6 @@ function ReasoningCard(props: ReasoningCardProps) {
|
|||||||
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
||||||
const [deletingUpTo, setDeletingUpTo] = createSignal(false)
|
const [deletingUpTo, setDeletingUpTo] = createSignal(false)
|
||||||
const isSelectedForDeletion = () => Boolean(props.selectedMessageIds?.().has(props.messageId))
|
const isSelectedForDeletion = () => Boolean(props.selectedMessageIds?.().has(props.messageId))
|
||||||
let pendingRenderNotificationFrame: number | null = null
|
|
||||||
|
|
||||||
const notifyContentRendered = () => {
|
|
||||||
if (!props.onContentRendered || typeof requestAnimationFrame !== "function") return
|
|
||||||
if (pendingRenderNotificationFrame !== null) {
|
|
||||||
cancelAnimationFrame(pendingRenderNotificationFrame)
|
|
||||||
}
|
|
||||||
pendingRenderNotificationFrame = requestAnimationFrame(() => {
|
|
||||||
pendingRenderNotificationFrame = null
|
|
||||||
props.onContentRendered?.()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
if (pendingRenderNotificationFrame !== null) {
|
|
||||||
cancelAnimationFrame(pendingRenderNotificationFrame)
|
|
||||||
pendingRenderNotificationFrame = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
setExpanded(Boolean(props.defaultExpanded))
|
setExpanded(Boolean(props.defaultExpanded))
|
||||||
@@ -1384,12 +1356,6 @@ function ReasoningCard(props: ReasoningCardProps) {
|
|||||||
const viewHideLabel = () =>
|
const viewHideLabel = () =>
|
||||||
expanded() ? t("messageBlock.reasoning.indicator.hide") : t("messageBlock.reasoning.indicator.view")
|
expanded() ? t("messageBlock.reasoning.indicator.hide") : t("messageBlock.reasoning.indicator.view")
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (!expanded()) return
|
|
||||||
reasoningText()
|
|
||||||
notifyContentRendered()
|
|
||||||
})
|
|
||||||
|
|
||||||
const canDeleteMessage = () => Boolean(props.showDeleteMessage) && !deletingMessage()
|
const canDeleteMessage = () => Boolean(props.showDeleteMessage) && !deletingMessage()
|
||||||
|
|
||||||
const handleDeleteMessage = async (event: MouseEvent) => {
|
const handleDeleteMessage = async (event: MouseEvent) => {
|
||||||
|
|||||||
@@ -542,7 +542,7 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="pt-1 whitespace-pre-wrap break-words leading-[1.1]" dir="auto">
|
<div class="pt-1 whitespace-pre-wrap break-words leading-[1.1]">
|
||||||
|
|
||||||
|
|
||||||
<Show when={props.isQueued && isUser()}>
|
<Show when={props.isQueued && isUser()}>
|
||||||
@@ -550,7 +550,7 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={errorMessage()}>
|
<Show when={errorMessage()}>
|
||||||
<div class="message-error-block" dir="auto">⚠️ {errorMessage()}</div>
|
<div class="message-error-block">⚠️ {errorMessage()}</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={isGenerating()}>
|
<Show when={isGenerating()}>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Match, Show, Suspense, Switch, lazy } from "solid-js"
|
import { Show, Match, Switch } from "solid-js"
|
||||||
|
import ToolCall from "./tool-call"
|
||||||
import { isItemExpanded, toggleItemExpanded } from "../stores/tool-call-state"
|
import { isItemExpanded, toggleItemExpanded } from "../stores/tool-call-state"
|
||||||
import { Markdown } from "./markdown"
|
import { Markdown } from "./markdown"
|
||||||
import { useTheme } from "../lib/theme"
|
import { useTheme } from "../lib/theme"
|
||||||
@@ -6,8 +7,6 @@ import { partHasRenderableText, SDKPart, TextPart, ClientPart } from "../types/m
|
|||||||
|
|
||||||
type ToolCallPart = Extract<ClientPart, { type: "tool" }>
|
type ToolCallPart = Extract<ClientPart, { type: "tool" }>
|
||||||
|
|
||||||
const LazyToolCall = lazy(() => import("./tool-call"))
|
|
||||||
|
|
||||||
interface MessagePartProps {
|
interface MessagePartProps {
|
||||||
part: ClientPart
|
part: ClientPart
|
||||||
messageType?: "user" | "assistant"
|
messageType?: "user" | "assistant"
|
||||||
@@ -134,12 +133,11 @@ export default function MessagePart(props: MessagePartProps) {
|
|||||||
<Show when={!shouldHideTextPart() && partHasRenderableText(props.part)}>
|
<Show when={!shouldHideTextPart() && partHasRenderableText(props.part)}>
|
||||||
<div
|
<div
|
||||||
class={canRenderMarkdown() ? markdownContainerClass() : textContainerClass()}
|
class={canRenderMarkdown() ? markdownContainerClass() : textContainerClass()}
|
||||||
dir="auto"
|
|
||||||
data-role={textContainerRole()}
|
data-role={textContainerRole()}
|
||||||
data-part-type="text"
|
data-part-type="text"
|
||||||
data-part-id={typeof (props.part as any)?.id === "string" ? (props.part as any).id : undefined}
|
data-part-id={typeof (props.part as any)?.id === "string" ? (props.part as any).id : undefined}
|
||||||
>
|
>
|
||||||
<Show when={canRenderMarkdown()} fallback={<span class="text-primary" dir="auto">{plainTextContent()}</span>}>
|
<Show when={canRenderMarkdown()} fallback={<span class="text-primary">{plainTextContent()}</span>}>
|
||||||
<Markdown
|
<Markdown
|
||||||
part={createTextPartForMarkdown()}
|
part={createTextPartForMarkdown()}
|
||||||
instanceId={props.instanceId}
|
instanceId={props.instanceId}
|
||||||
@@ -154,14 +152,12 @@ export default function MessagePart(props: MessagePartProps) {
|
|||||||
</Match>
|
</Match>
|
||||||
|
|
||||||
<Match when={partType() === "tool"}>
|
<Match when={partType() === "tool"}>
|
||||||
<Suspense fallback={<div class="tool-call tool-call-loading" />}>
|
<ToolCall
|
||||||
<LazyToolCall
|
|
||||||
toolCall={props.part as ToolCallPart}
|
toolCall={props.part as ToolCallPart}
|
||||||
toolCallId={props.part?.id}
|
toolCallId={props.part?.id}
|
||||||
instanceId={props.instanceId}
|
instanceId={props.instanceId}
|
||||||
sessionId={props.sessionId}
|
sessionId={props.sessionId}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
|
||||||
</Match>
|
</Match>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import type { DeleteHoverState } from "../types/delete-hover"
|
|||||||
import { buildRecordDisplayData } from "../stores/message-v2/record-display-cache"
|
import { buildRecordDisplayData } from "../stores/message-v2/record-display-cache"
|
||||||
import { getPartCharCount } from "../lib/token-utils"
|
import { getPartCharCount } from "../lib/token-utils"
|
||||||
|
|
||||||
const SCROLL_SENTINEL_MARGIN_PX = 8
|
const SCROLL_SENTINEL_MARGIN_PX = 48
|
||||||
const MESSAGE_SCROLL_CACHE_SCOPE = "message-stream"
|
const MESSAGE_SCROLL_CACHE_SCOPE = "message-stream"
|
||||||
const QUOTE_SELECTION_MAX_LENGTH = 2000
|
const QUOTE_SELECTION_MAX_LENGTH = 2000
|
||||||
const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href
|
const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { For, Show, Suspense, createMemo, createSignal, createEffect, lazy, onCleanup, type Component } from "solid-js"
|
import { For, Show, createMemo, createSignal, createEffect, onCleanup, type Component } from "solid-js"
|
||||||
import type { PermissionRequestLike } from "../types/permission"
|
import type { PermissionRequestLike } from "../types/permission"
|
||||||
import { getPermissionCallId, getPermissionDisplayTitle, getPermissionKind, getPermissionMessageId, getPermissionSessionId } from "../types/permission"
|
import { getPermissionCallId, getPermissionDisplayTitle, getPermissionKind, getPermissionMessageId, getPermissionSessionId } from "../types/permission"
|
||||||
import { getQuestionCallId, getQuestionMessageId, getQuestionSessionId, type QuestionRequest } from "../types/question"
|
import { getQuestionCallId, getQuestionMessageId, getQuestionSessionId, type QuestionRequest } from "../types/question"
|
||||||
@@ -12,8 +12,7 @@ import {
|
|||||||
} from "../stores/instances"
|
} from "../stores/instances"
|
||||||
import { ensureSessionParentExpanded, loadMessages, sessions as sessionStateSessions, setActiveSessionFromList } from "../stores/sessions"
|
import { ensureSessionParentExpanded, loadMessages, sessions as sessionStateSessions, setActiveSessionFromList } from "../stores/sessions"
|
||||||
import { messageStoreBus } from "../stores/message-v2/bus"
|
import { messageStoreBus } from "../stores/message-v2/bus"
|
||||||
|
import ToolCall from "./tool-call"
|
||||||
const LazyToolCall = lazy(() => import("./tool-call"))
|
|
||||||
|
|
||||||
interface PermissionApprovalModalProps {
|
interface PermissionApprovalModalProps {
|
||||||
instanceId: string
|
instanceId: string
|
||||||
@@ -409,8 +408,7 @@ const PermissionApprovalModal: Component<PermissionApprovalModalProps> = (props)
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(data) => (
|
{(data) => (
|
||||||
<Suspense fallback={<div class="tool-call tool-call-loading" />}>
|
<ToolCall
|
||||||
<LazyToolCall
|
|
||||||
toolCall={data().toolPart}
|
toolCall={data().toolPart}
|
||||||
toolCallId={data().toolPart.id}
|
toolCallId={data().toolPart.id}
|
||||||
messageId={data().messageId}
|
messageId={data().messageId}
|
||||||
@@ -419,7 +417,6 @@ const PermissionApprovalModal: Component<PermissionApprovalModalProps> = (props)
|
|||||||
instanceId={props.instanceId}
|
instanceId={props.instanceId}
|
||||||
sessionId={data().sessionId}
|
sessionId={data().sessionId}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Suspense, createEffect, createSignal, lazy, on, onCleanup, onMount, Show } from "solid-js"
|
import { createSignal, Show, onMount, onCleanup, createEffect, on } from "solid-js"
|
||||||
import { ArrowBigUp, ArrowBigDown } from "lucide-solid"
|
import { ArrowBigUp, ArrowBigDown } from "lucide-solid"
|
||||||
|
import UnifiedPicker from "./unified-picker"
|
||||||
import ExpandButton from "./expand-button"
|
import ExpandButton from "./expand-button"
|
||||||
import { clearAttachments, removeAttachment } from "../stores/attachments"
|
import { clearAttachments, removeAttachment } from "../stores/attachments"
|
||||||
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
||||||
@@ -19,7 +20,6 @@ import { usePromptAttachments } from "./prompt-input/usePromptAttachments"
|
|||||||
import { usePromptPicker } from "./prompt-input/usePromptPicker"
|
import { usePromptPicker } from "./prompt-input/usePromptPicker"
|
||||||
import { usePromptKeyDown } from "./prompt-input/usePromptKeyDown"
|
import { usePromptKeyDown } from "./prompt-input/usePromptKeyDown"
|
||||||
const log = getLogger("actions")
|
const log = getLogger("actions")
|
||||||
const LazyUnifiedPicker = lazy(() => import("./unified-picker"))
|
|
||||||
|
|
||||||
function getConsumedPastedTextAttachmentIds(text: string, attachments: Attachment[]): string[] {
|
function getConsumedPastedTextAttachmentIds(text: string, attachments: Attachment[]): string[] {
|
||||||
if (!text || attachments.length === 0) return []
|
if (!text || attachments.length === 0) return []
|
||||||
@@ -467,8 +467,7 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
>
|
>
|
||||||
<Show when={showPicker() && instance()}>
|
<Show when={showPicker() && instance()}>
|
||||||
<Suspense fallback={null}>
|
<UnifiedPicker
|
||||||
<LazyUnifiedPicker
|
|
||||||
open={showPicker()}
|
open={showPicker()}
|
||||||
mode={pickerMode()}
|
mode={pickerMode()}
|
||||||
onClose={handlePickerClose}
|
onClose={handlePickerClose}
|
||||||
@@ -480,7 +479,6 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
textareaRef={textareaRef}
|
textareaRef={textareaRef}
|
||||||
workspaceId={props.instanceId}
|
workspaceId={props.instanceId}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div class="flex flex-1 flex-col">
|
<div class="flex flex-1 flex-col">
|
||||||
@@ -490,7 +488,6 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
<textarea
|
<textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
class={`prompt-input ${mode() === "shell" ? "shell-mode" : ""} ${expandState() === "expanded" ? "is-expanded" : ""}`}
|
class={`prompt-input ${mode() === "shell" ? "shell-mode" : ""} ${expandState() === "expanded" ? "is-expanded" : ""}`}
|
||||||
dir="auto"
|
|
||||||
placeholder={getPlaceholder()}
|
placeholder={getPlaceholder()}
|
||||||
value={prompt()}
|
value={prompt()}
|
||||||
onInput={handleInput}
|
onInput={handleInput}
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ const SessionList: Component<SessionListProps> = (props) => {
|
|||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
{rowProps.isChild ? <Bot class="w-4 h-4 flex-shrink-0" /> : <User class="w-4 h-4 flex-shrink-0" />}
|
{rowProps.isChild ? <Bot class="w-4 h-4 flex-shrink-0" /> : <User class="w-4 h-4 flex-shrink-0" />}
|
||||||
<span class="session-item-title session-item-title--clamp" dir="auto">{title()}</span>
|
<span class="session-item-title session-item-title--clamp">{title()}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="session-item-row session-item-meta">
|
<div class="session-item-row session-item-meta">
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ const SessionRenameDialog: Component<SessionRenameDialogProps> = (props) => {
|
|||||||
inputRef = element
|
inputRef = element
|
||||||
}}
|
}}
|
||||||
type="text"
|
type="text"
|
||||||
dir="auto"
|
|
||||||
value={title()}
|
value={title()}
|
||||||
onInput={(event) => setTitle(event.currentTarget.value)}
|
onInput={(event) => setTitle(event.currentTarget.value)}
|
||||||
placeholder={t("sessionRenameDialog.input.placeholder")}
|
placeholder={t("sessionRenameDialog.input.placeholder")}
|
||||||
|
|||||||
@@ -514,7 +514,6 @@ function ToolCallDetails(props: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { renderDiffContent } = createDiffContentRenderer({
|
const { renderDiffContent } = createDiffContentRenderer({
|
||||||
toolState: props.toolState,
|
|
||||||
preferences: props.preferences,
|
preferences: props.preferences,
|
||||||
setDiffViewMode: props.setDiffViewMode,
|
setDiffViewMode: props.setDiffViewMode,
|
||||||
isDark: props.isDark,
|
isDark: props.isDark,
|
||||||
|
|||||||
@@ -20,14 +20,6 @@ export function createAnsiContentRenderer(params: {
|
|||||||
const runningAnsiRenderer = createAnsiStreamRenderer()
|
const runningAnsiRenderer = createAnsiStreamRenderer()
|
||||||
let runningAnsiSource = ""
|
let runningAnsiSource = ""
|
||||||
|
|
||||||
const registerTracked = (element: HTMLDivElement | null) => {
|
|
||||||
params.scrollHelpers.registerContainer(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerUntracked = (element: HTMLDivElement | null) => {
|
|
||||||
params.scrollHelpers.registerContainer(element, { disableTracking: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMode = () => {
|
const getMode = () => {
|
||||||
const version = params.partVersion?.()
|
const version = params.partVersion?.()
|
||||||
return typeof version === "number" ? String(version) : undefined
|
return typeof version === "number" ? String(version) : undefined
|
||||||
@@ -44,8 +36,6 @@ export function createAnsiContentRenderer(params: {
|
|||||||
const cached = cacheHandle.get<AnsiRenderCache>()
|
const cached = cacheHandle.get<AnsiRenderCache>()
|
||||||
const mode = getMode()
|
const mode = getMode()
|
||||||
const isRunningVariant = options.variant === "running"
|
const isRunningVariant = options.variant === "running"
|
||||||
const disableScrollTracking = !isRunningVariant
|
|
||||||
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
|
||||||
|
|
||||||
let nextCache: AnsiRenderCache
|
let nextCache: AnsiRenderCache
|
||||||
|
|
||||||
@@ -97,9 +87,9 @@ export function createAnsiContentRenderer(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={messageClass} ref={registerRef} onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}>
|
<div class={messageClass} ref={params.scrollHelpers.registerContainer} onScroll={params.scrollHelpers.handleScroll}>
|
||||||
<pre class="tool-call-content tool-call-ansi" dir="auto" innerHTML={nextCache.html} />
|
<pre class="tool-call-content tool-call-ansi" innerHTML={nextCache.html} />
|
||||||
{params.scrollHelpers.renderSentinel({ disableTracking: disableScrollTracking })}
|
{params.scrollHelpers.renderSentinel()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function renderDiagnosticsSection(
|
|||||||
{entry.displayPath}
|
{entry.displayPath}
|
||||||
<span class="tool-call-diagnostic-coords">:L{entry.line || "-"}:C{entry.column || "-"}</span>
|
<span class="tool-call-diagnostic-coords">:L{entry.line || "-"}:C{entry.column || "-"}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="tool-call-diagnostic-message" dir="auto">{entry.message}</span>
|
<span class="tool-call-diagnostic-message">{entry.message}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Suspense, lazy, onMount, type Accessor, type JSXElement } from "solid-js"
|
import { Suspense, lazy, onMount, type Accessor, type JSXElement } from "solid-js"
|
||||||
import type { ToolState } from "@opencode-ai/sdk/v2"
|
|
||||||
import type { RenderCache } from "../../types/message"
|
import type { RenderCache } from "../../types/message"
|
||||||
import type { DiffViewMode } from "../../stores/preferences"
|
import type { DiffViewMode } from "../../stores/preferences"
|
||||||
import type { DiffPayload, DiffRenderOptions, ToolScrollHelpers } from "./types"
|
import type { DiffPayload, DiffRenderOptions, ToolScrollHelpers } from "./types"
|
||||||
@@ -32,7 +31,6 @@ type DiffPrefs = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createDiffContentRenderer(params: {
|
export function createDiffContentRenderer(params: {
|
||||||
toolState: Accessor<ToolState | undefined>
|
|
||||||
preferences: Accessor<DiffPrefs>
|
preferences: Accessor<DiffPrefs>
|
||||||
setDiffViewMode: (mode: DiffViewMode) => void
|
setDiffViewMode: (mode: DiffViewMode) => void
|
||||||
isDark: Accessor<boolean>
|
isDark: Accessor<boolean>
|
||||||
@@ -60,10 +58,7 @@ export function createDiffContentRenderer(params: {
|
|||||||
const cacheHandle = selectedVariant === "permission-diff" ? params.permissionDiffCache : params.diffCache
|
const cacheHandle = selectedVariant === "permission-diff" ? params.permissionDiffCache : params.diffCache
|
||||||
const diffMode = () => (params.preferences().diffViewMode || "split") as DiffViewMode
|
const diffMode = () => (params.preferences().diffViewMode || "split") as DiffViewMode
|
||||||
const themeKey = params.isDark() ? "dark" : "light"
|
const themeKey = params.isDark() ? "dark" : "light"
|
||||||
const state = params.toolState()
|
const disableScrollTracking = Boolean(options?.disableScrollTracking)
|
||||||
const disableScrollTracking = Boolean(
|
|
||||||
options?.disableScrollTracking || (state?.status !== "running" && state?.status !== "pending"),
|
|
||||||
)
|
|
||||||
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
||||||
|
|
||||||
const baseEntryParams = cacheHandle.params() as any
|
const baseEntryParams = cacheHandle.params() as any
|
||||||
|
|||||||
@@ -31,9 +31,10 @@ export function createMarkdownContentRenderer(params: {
|
|||||||
const size = options.size || "default"
|
const size = options.size || "default"
|
||||||
const disableHighlight = options.disableHighlight || false
|
const disableHighlight = options.disableHighlight || false
|
||||||
const messageClass = `message-text tool-call-markdown${size === "large" ? " tool-call-markdown-large" : ""}`
|
const messageClass = `message-text tool-call-markdown${size === "large" ? " tool-call-markdown-large" : ""}`
|
||||||
const state = params.toolState()
|
const disableScrollTracking = options.disableScrollTracking || false
|
||||||
const disableScrollTracking = options.disableScrollTracking || (state?.status !== "running" && state?.status !== "pending")
|
|
||||||
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
||||||
|
|
||||||
|
const state = params.toolState()
|
||||||
const shouldDeferMarkdown = Boolean(state && (state.status === "running" || state.status === "pending") && disableHighlight)
|
const shouldDeferMarkdown = Boolean(state && (state.status === "running" || state.status === "pending") && disableHighlight)
|
||||||
if (shouldDeferMarkdown) {
|
if (shouldDeferMarkdown) {
|
||||||
return (
|
return (
|
||||||
@@ -42,7 +43,7 @@ export function createMarkdownContentRenderer(params: {
|
|||||||
ref={registerRef}
|
ref={registerRef}
|
||||||
onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}
|
onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}
|
||||||
>
|
>
|
||||||
<pre class="whitespace-pre-wrap break-words text-sm font-mono" dir="auto">{options.content}</pre>
|
<pre class="whitespace-pre-wrap break-words text-sm font-mono">{options.content}</pre>
|
||||||
{params.scrollHelpers.renderSentinel({ disableTracking: disableScrollTracking })}
|
{params.scrollHelpers.renderSentinel({ disableTracking: disableScrollTracking })}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
200
packages/ui/src/lib/git-diff-lowlight.ts
Normal file
200
packages/ui/src/lib/git-diff-lowlight.ts
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import { createLowlight, common } from "lowlight"
|
||||||
|
|
||||||
|
type AstNode = {
|
||||||
|
type: string
|
||||||
|
value?: string
|
||||||
|
children?: AstNode[]
|
||||||
|
startIndex?: number
|
||||||
|
endIndex?: number
|
||||||
|
lineNumber?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyntaxNodeEntry = {
|
||||||
|
node: AstNode
|
||||||
|
wrapper?: AstNode
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyntaxFileLine = {
|
||||||
|
value: string
|
||||||
|
lineNumber: number
|
||||||
|
valueLength: number
|
||||||
|
nodeList: SyntaxNodeEntry[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type LowlightApi = ReturnType<typeof createLowlight>
|
||||||
|
|
||||||
|
export function processAST(ast: { children: AstNode[] }) {
|
||||||
|
let lineNumber = 1
|
||||||
|
const syntaxObj: Record<number, SyntaxFileLine> = {}
|
||||||
|
|
||||||
|
const loopAST = (nodes: AstNode[], wrapper?: AstNode) => {
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
if (node.type === "text") {
|
||||||
|
const textValue = node.value ?? ""
|
||||||
|
if (!textValue.includes("\n")) {
|
||||||
|
const valueLength = textValue.length
|
||||||
|
if (!syntaxObj[lineNumber]) {
|
||||||
|
node.startIndex = 0
|
||||||
|
node.endIndex = valueLength - 1
|
||||||
|
syntaxObj[lineNumber] = {
|
||||||
|
value: textValue,
|
||||||
|
lineNumber,
|
||||||
|
valueLength,
|
||||||
|
nodeList: [{ node, wrapper }],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node.startIndex = syntaxObj[lineNumber].valueLength
|
||||||
|
node.endIndex = node.startIndex + valueLength - 1
|
||||||
|
syntaxObj[lineNumber].value += textValue
|
||||||
|
syntaxObj[lineNumber].valueLength += valueLength
|
||||||
|
syntaxObj[lineNumber].nodeList.push({ node, wrapper })
|
||||||
|
}
|
||||||
|
node.lineNumber = lineNumber
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = textValue.split("\n")
|
||||||
|
node.children = node.children || []
|
||||||
|
for (let index = 0; index < lines.length; index++) {
|
||||||
|
const value = index === lines.length - 1 ? lines[index] : `${lines[index]}\n`
|
||||||
|
const currentLineNumber = index === 0 ? lineNumber : ++lineNumber
|
||||||
|
const valueLength = value.length
|
||||||
|
const childNode: AstNode = {
|
||||||
|
type: "text",
|
||||||
|
value,
|
||||||
|
startIndex: Infinity,
|
||||||
|
endIndex: Infinity,
|
||||||
|
lineNumber: currentLineNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!syntaxObj[currentLineNumber]) {
|
||||||
|
childNode.startIndex = 0
|
||||||
|
childNode.endIndex = valueLength - 1
|
||||||
|
syntaxObj[currentLineNumber] = {
|
||||||
|
value,
|
||||||
|
lineNumber: currentLineNumber,
|
||||||
|
valueLength,
|
||||||
|
nodeList: [{ node: childNode, wrapper }],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
childNode.startIndex = syntaxObj[currentLineNumber].valueLength
|
||||||
|
childNode.endIndex = childNode.startIndex + valueLength - 1
|
||||||
|
syntaxObj[currentLineNumber].value += value
|
||||||
|
syntaxObj[currentLineNumber].valueLength += valueLength
|
||||||
|
syntaxObj[currentLineNumber].nodeList.push({ node: childNode, wrapper })
|
||||||
|
}
|
||||||
|
|
||||||
|
node.children.push(childNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.lineNumber = lineNumber
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
loopAST(node.children, node)
|
||||||
|
node.lineNumber = lineNumber
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loopAST(ast.children)
|
||||||
|
return { syntaxFileObject: syntaxObj, syntaxFileLineNumber: lineNumber }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _getAST() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowlight = createLowlight(common)
|
||||||
|
|
||||||
|
lowlight.register("vue", function hljsDefineVue(hljs: any) {
|
||||||
|
return {
|
||||||
|
subLanguage: "xml",
|
||||||
|
contains: [
|
||||||
|
hljs.COMMENT("<!--", "-->", { relevance: 10 }),
|
||||||
|
{
|
||||||
|
begin: /^(\s*)(<script>)/gm,
|
||||||
|
end: /^(\s*)(<\/script>)/gm,
|
||||||
|
subLanguage: "javascript",
|
||||||
|
excludeBegin: true,
|
||||||
|
excludeEnd: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
begin: /^(?:\s*)(?:<script\s+lang=(["'])ts\1>)/gm,
|
||||||
|
end: /^(\s*)(<\/script>)/gm,
|
||||||
|
subLanguage: "typescript",
|
||||||
|
excludeBegin: true,
|
||||||
|
excludeEnd: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
begin: /^(\s*)(<style(\s+scoped)?>)/gm,
|
||||||
|
end: /^(\s*)(<\/style>)/gm,
|
||||||
|
subLanguage: "css",
|
||||||
|
excludeBegin: true,
|
||||||
|
excludeEnd: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
begin: /^(?:\s*)(?:<style(?:\s+scoped)?\s+lang=(["'])(?:s[ca]ss)\1(?:\s+scoped)?>)/gm,
|
||||||
|
end: /^(\s*)(<\/style>)/gm,
|
||||||
|
subLanguage: "scss",
|
||||||
|
excludeBegin: true,
|
||||||
|
excludeEnd: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
begin: /^(?:\s*)(?:<style(?:\s+scoped)?\s+lang=(["'])stylus\1(?:\s+scoped)?>)/gm,
|
||||||
|
end: /^(\s*)(<\/style>)/gm,
|
||||||
|
subLanguage: "stylus",
|
||||||
|
excludeBegin: true,
|
||||||
|
excludeEnd: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let maxLineToIgnoreSyntax = 2000
|
||||||
|
const ignoreSyntaxHighlightList: (string | RegExp)[] = []
|
||||||
|
|
||||||
|
export const highlighter = {
|
||||||
|
name: "lowlight",
|
||||||
|
get maxLineToIgnoreSyntax() {
|
||||||
|
return maxLineToIgnoreSyntax
|
||||||
|
},
|
||||||
|
setMaxLineToIgnoreSyntax(value: number) {
|
||||||
|
maxLineToIgnoreSyntax = value
|
||||||
|
},
|
||||||
|
get ignoreSyntaxHighlightList() {
|
||||||
|
return ignoreSyntaxHighlightList
|
||||||
|
},
|
||||||
|
setIgnoreSyntaxHighlightList(values: (string | RegExp)[]) {
|
||||||
|
ignoreSyntaxHighlightList.length = 0
|
||||||
|
ignoreSyntaxHighlightList.push(...values)
|
||||||
|
},
|
||||||
|
getAST(raw: string, fileName?: string, lang?: string) {
|
||||||
|
const language = typeof lang === "string" ? lang.trim() : ""
|
||||||
|
if (
|
||||||
|
fileName &&
|
||||||
|
ignoreSyntaxHighlightList.some((item) => (item instanceof RegExp ? item.test(fileName) : fileName === item))
|
||||||
|
) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (language && lowlight.registered(language)) {
|
||||||
|
return lowlight.highlight(language, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowlight.highlightAuto(raw)
|
||||||
|
},
|
||||||
|
processAST(ast: { children: AstNode[] }) {
|
||||||
|
return processAST(ast)
|
||||||
|
},
|
||||||
|
hasRegisteredCurrentLang(lang: string) {
|
||||||
|
return lowlight.registered(lang)
|
||||||
|
},
|
||||||
|
getHighlighterEngine(): LowlightApi {
|
||||||
|
return lowlight
|
||||||
|
},
|
||||||
|
type: "class" as const,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const versions = "local-common"
|
||||||
@@ -2,6 +2,11 @@ import { createContext, createEffect, createMemo, createSignal, onCleanup, onMou
|
|||||||
import type { ParentComponent } from "solid-js"
|
import type { ParentComponent } from "solid-js"
|
||||||
import { useConfig } from "../../stores/preferences"
|
import { useConfig } from "../../stores/preferences"
|
||||||
import { enMessages } from "./messages/en"
|
import { enMessages } from "./messages/en"
|
||||||
|
import { esMessages } from "./messages/es"
|
||||||
|
import { frMessages } from "./messages/fr"
|
||||||
|
import { ruMessages } from "./messages/ru"
|
||||||
|
import { jaMessages } from "./messages/ja"
|
||||||
|
import { zhHansMessages } from "./messages/zh-Hans"
|
||||||
|
|
||||||
type Messages = Record<string, string>
|
type Messages = Record<string, string>
|
||||||
|
|
||||||
@@ -10,18 +15,14 @@ export type TranslateParams = Record<string, unknown>
|
|||||||
export type Locale = "en" | "es" | "fr" | "ru" | "ja" | "zh-Hans"
|
export type Locale = "en" | "es" | "fr" | "ru" | "ja" | "zh-Hans"
|
||||||
|
|
||||||
const SUPPORTED_LOCALES: readonly Locale[] = ["en", "es", "fr", "ru", "ja", "zh-Hans"] as const
|
const SUPPORTED_LOCALES: readonly Locale[] = ["en", "es", "fr", "ru", "ja", "zh-Hans"] as const
|
||||||
const SUPPORTED_LOCALES_BY_LOWER = new Map(SUPPORTED_LOCALES.map((locale) => [locale.toLowerCase(), locale]))
|
|
||||||
|
|
||||||
const localeMessagesCache = new Map<Locale, Messages>([["en", enMessages]])
|
const messagesByLocale: Record<Locale, Messages> = {
|
||||||
const localeMessagesPromises = new Map<Locale, Promise<Messages>>()
|
en: enMessages,
|
||||||
|
es: esMessages,
|
||||||
const localeLoaders: Record<Locale, () => Promise<Messages>> = {
|
fr: frMessages,
|
||||||
en: async () => enMessages,
|
ru: ruMessages,
|
||||||
es: async () => (await import("./messages/es")).esMessages,
|
ja: jaMessages,
|
||||||
fr: async () => (await import("./messages/fr")).frMessages,
|
"zh-Hans": zhHansMessages,
|
||||||
ru: async () => (await import("./messages/ru")).ruMessages,
|
|
||||||
ja: async () => (await import("./messages/ja")).jaMessages,
|
|
||||||
"zh-Hans": async () => (await import("./messages/zh-Hans")).zhHansMessages,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeLocaleTag(value: string): string {
|
function normalizeLocaleTag(value: string): string {
|
||||||
@@ -33,7 +34,8 @@ function matchSupportedLocale(value: string | undefined): Locale | null {
|
|||||||
|
|
||||||
const normalized = normalizeLocaleTag(value)
|
const normalized = normalizeLocaleTag(value)
|
||||||
const lower = normalized.toLowerCase()
|
const lower = normalized.toLowerCase()
|
||||||
const exact = SUPPORTED_LOCALES_BY_LOWER.get(lower)
|
const supportedLower = new Map(SUPPORTED_LOCALES.map((locale) => [locale.toLowerCase(), locale]))
|
||||||
|
const exact = supportedLower.get(lower)
|
||||||
if (exact) return exact
|
if (exact) return exact
|
||||||
|
|
||||||
const parts = lower.split("-")
|
const parts = lower.split("-")
|
||||||
@@ -41,11 +43,11 @@ function matchSupportedLocale(value: string | undefined): Locale | null {
|
|||||||
if (!base) return null
|
if (!base) return null
|
||||||
|
|
||||||
if (base === "zh") {
|
if (base === "zh") {
|
||||||
const zhHans = SUPPORTED_LOCALES_BY_LOWER.get("zh-hans")
|
const zhHans = supportedLower.get("zh-hans")
|
||||||
return zhHans ?? null
|
return zhHans ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseMatch = SUPPORTED_LOCALES_BY_LOWER.get(base)
|
const baseMatch = supportedLower.get(base)
|
||||||
return baseMatch ?? null
|
return baseMatch ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,54 +84,8 @@ function translateFrom(messages: Messages, key: string, params?: TranslateParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [globalRevision, setGlobalRevision] = createSignal(0)
|
const [globalRevision, setGlobalRevision] = createSignal(0)
|
||||||
let globalMessages: Messages = enMessages
|
const initialGlobalLocale: Locale = detectNavigatorLocale() ?? "en"
|
||||||
let globalLocale: Locale = "en"
|
let globalMessages: Messages = messagesByLocale[initialGlobalLocale]
|
||||||
|
|
||||||
function getMessagesForLocale(locale: Locale): Messages {
|
|
||||||
return localeMessagesCache.get(locale) ?? enMessages
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadLocaleMessages(locale: Locale): Promise<Messages> {
|
|
||||||
const cached = localeMessagesCache.get(locale)
|
|
||||||
if (cached) {
|
|
||||||
return cached
|
|
||||||
}
|
|
||||||
|
|
||||||
const pending = localeMessagesPromises.get(locale)
|
|
||||||
if (pending) {
|
|
||||||
return pending
|
|
||||||
}
|
|
||||||
|
|
||||||
const loader = localeLoaders[locale]
|
|
||||||
const promise = loader()
|
|
||||||
.then((messages) => {
|
|
||||||
localeMessagesCache.set(locale, messages)
|
|
||||||
localeMessagesPromises.delete(locale)
|
|
||||||
return messages
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
localeMessagesPromises.delete(locale)
|
|
||||||
throw error
|
|
||||||
})
|
|
||||||
|
|
||||||
localeMessagesPromises.set(locale, promise)
|
|
||||||
return promise
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function preloadLocaleMessages(preferredLocale?: string | null): Promise<Locale> {
|
|
||||||
const resolvedLocale = matchSupportedLocale(preferredLocale ?? undefined) ?? detectNavigatorLocale() ?? "en"
|
|
||||||
try {
|
|
||||||
globalMessages = await loadLocaleMessages(resolvedLocale)
|
|
||||||
globalLocale = resolvedLocale
|
|
||||||
setGlobalRevision((value) => value + 1)
|
|
||||||
return resolvedLocale
|
|
||||||
} catch {
|
|
||||||
globalMessages = enMessages
|
|
||||||
globalLocale = "en"
|
|
||||||
setGlobalRevision((value) => value + 1)
|
|
||||||
return "en"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tGlobal(key: string, params?: TranslateParams): string {
|
export function tGlobal(key: string, params?: TranslateParams): string {
|
||||||
globalRevision()
|
globalRevision()
|
||||||
@@ -145,10 +101,9 @@ const I18nContext = createContext<I18nContextValue>()
|
|||||||
|
|
||||||
export const I18nProvider: ParentComponent = (props) => {
|
export const I18nProvider: ParentComponent = (props) => {
|
||||||
const { preferences } = useConfig()
|
const { preferences } = useConfig()
|
||||||
const [detectedLocale, setDetectedLocale] = createSignal<Locale>(globalLocale)
|
const [detectedLocale, setDetectedLocale] = createSignal<Locale>("en")
|
||||||
const [resolvedLocale, setResolvedLocale] = createSignal<Locale>(globalLocale)
|
|
||||||
const previousGlobalMessages = globalMessages
|
const previousMessages = globalMessages
|
||||||
const previousGlobalLocale = globalLocale
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const detected = detectNavigatorLocale()
|
const detected = detectNavigatorLocale()
|
||||||
@@ -160,44 +115,19 @@ export const I18nProvider: ParentComponent = (props) => {
|
|||||||
return configured ?? detectedLocale() ?? "en"
|
return configured ?? detectedLocale() ?? "en"
|
||||||
})
|
})
|
||||||
|
|
||||||
const messages = createMemo<Messages>(() => getMessagesForLocale(resolvedLocale()))
|
const messages = createMemo<Messages>(() => messagesByLocale[locale()])
|
||||||
|
|
||||||
function t(key: string, params?: TranslateParams): string {
|
function t(key: string, params?: TranslateParams): string {
|
||||||
return translateFrom(messages(), key, params)
|
return translateFrom(messages(), key, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const nextLocale = locale()
|
globalMessages = messages()
|
||||||
let cancelled = false
|
|
||||||
|
|
||||||
void loadLocaleMessages(nextLocale)
|
|
||||||
.then((loadedMessages) => {
|
|
||||||
if (cancelled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setResolvedLocale(nextLocale)
|
|
||||||
globalLocale = nextLocale
|
|
||||||
globalMessages = loadedMessages
|
|
||||||
setGlobalRevision((value) => value + 1)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (cancelled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setResolvedLocale("en")
|
|
||||||
globalMessages = enMessages
|
|
||||||
globalLocale = "en"
|
|
||||||
setGlobalRevision((value) => value + 1)
|
setGlobalRevision((value) => value + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
cancelled = true
|
globalMessages = previousMessages
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
globalMessages = previousGlobalMessages
|
|
||||||
globalLocale = previousGlobalLocale
|
|
||||||
setGlobalRevision((value) => value + 1)
|
setGlobalRevision((value) => value + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ThemeProvider } from "./lib/theme"
|
|||||||
import { ConfigProvider } from "./stores/preferences"
|
import { ConfigProvider } from "./stores/preferences"
|
||||||
import { InstanceConfigProvider } from "./stores/instance-config"
|
import { InstanceConfigProvider } from "./stores/instance-config"
|
||||||
import { runtimeEnv } from "./lib/runtime-env"
|
import { runtimeEnv } from "./lib/runtime-env"
|
||||||
import { I18nProvider, preloadLocaleMessages } from "./lib/i18n"
|
import { I18nProvider } from "./lib/i18n"
|
||||||
import { storage } from "./lib/storage"
|
import { storage } from "./lib/storage"
|
||||||
import "./index.css"
|
import "./index.css"
|
||||||
import "@git-diff-view/solid/styles/diff-view-pure.css"
|
import "@git-diff-view/solid/styles/diff-view-pure.css"
|
||||||
@@ -31,19 +31,15 @@ async function bootstrap() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const uiConfig = await storage.loadConfigOwner("ui")
|
const uiConfig = await storage.loadConfigOwner("ui")
|
||||||
const theme = (uiConfig as any)?.theme
|
const theme = (uiConfig as any)?.theme ?? "system"
|
||||||
const locale = typeof (uiConfig as any)?.settings?.locale === "string" ? (uiConfig as any).settings.locale : undefined
|
|
||||||
|
|
||||||
if (theme === "light" || theme === "dark") {
|
if (theme === "system") {
|
||||||
document.documentElement.setAttribute("data-theme", theme)
|
|
||||||
} else {
|
|
||||||
document.documentElement.removeAttribute("data-theme")
|
document.documentElement.removeAttribute("data-theme")
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute("data-theme", theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
await preloadLocaleMessages(locale)
|
|
||||||
} catch {
|
} catch {
|
||||||
// If config fails to load, fall back to CSS defaults.
|
// If config fails to load, fall back to CSS defaults.
|
||||||
await preloadLocaleMessages()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,21 +24,6 @@
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Auto-detect text direction per block element for RTL language support (e.g. Hebrew, Arabic) */
|
|
||||||
.markdown-body p,
|
|
||||||
.markdown-body li,
|
|
||||||
.markdown-body h1,
|
|
||||||
.markdown-body h2,
|
|
||||||
.markdown-body h3,
|
|
||||||
.markdown-body h4,
|
|
||||||
.markdown-body h5,
|
|
||||||
.markdown-body h6,
|
|
||||||
.markdown-body blockquote,
|
|
||||||
.markdown-body td,
|
|
||||||
.markdown-body th {
|
|
||||||
unicode-bidi: plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h1,
|
.markdown-body h1,
|
||||||
.markdown-body h2,
|
.markdown-body h2,
|
||||||
.markdown-body h3,
|
.markdown-body h3,
|
||||||
@@ -144,19 +129,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body blockquote {
|
.markdown-body blockquote {
|
||||||
border-inline-start: 3px solid var(--border-base);
|
border-left: 3px solid var(--border-base);
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
background-color: var(--surface-muted);
|
background-color: var(--surface-muted);
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
border-start-start-radius: 0;
|
border-radius: 0 8px 8px 0;
|
||||||
border-start-end-radius: 8px;
|
|
||||||
border-end-end-radius: 8px;
|
|
||||||
border-end-start-radius: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body ul,
|
.markdown-body ul,
|
||||||
.markdown-body ol {
|
.markdown-body ol {
|
||||||
padding-inline-start: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +166,7 @@
|
|||||||
.markdown-body td {
|
.markdown-body td {
|
||||||
border: 1px solid var(--border-base);
|
border: 1px solid var(--border-base);
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
text-align: start;
|
text-align: left;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
@@ -239,7 +221,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
|
transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
|
||||||
margin-inline-start: auto;
|
margin-left: auto;
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,13 +132,6 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-stream-block {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-step-start {
|
.message-step-start {
|
||||||
background-color: var(--message-assistant-bg);
|
background-color: var(--message-assistant-bg);
|
||||||
border-left: 4px solid var(--message-assistant-border);
|
border-left: 4px solid var(--message-assistant-border);
|
||||||
|
|||||||
Reference in New Issue
Block a user