feat: Add Gemini AI settings and inline action buttons
- Introduced GeminiSettings component for configuring Gemini AI settings including API key, model selection, and inline suggestions. - Updated App.vue to include GeminiSettings in the view management. - Enhanced MonacoEditor with AI action buttons for code fixing, explaining, refactoring, and optimizing. - Implemented responsive design for GeminiSettings and MonacoEditor components. - Added sidebar button to toggle Gemini settings. - Integrated API calls for saving and testing Gemini configuration.
This commit is contained in:
474
AEditor/src-tauri/Cargo.lock
generated
474
AEditor/src-tauri/Cargo.lock
generated
@@ -14,12 +14,15 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"discord-rich-presence",
|
||||
"regex",
|
||||
"reqwest 0.11.27",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-opener",
|
||||
"tokio",
|
||||
"uuid 1.18.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -526,6 +529,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
@@ -549,9 +562,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -562,7 +575,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -862,7 +875,7 @@ dependencies = [
|
||||
"rustc_version",
|
||||
"toml 0.9.8",
|
||||
"vswhom",
|
||||
"winreg",
|
||||
"winreg 0.55.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -871,6 +884,15 @@ version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.0"
|
||||
@@ -993,6 +1015,15 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
@@ -1000,7 +1031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||
dependencies = [
|
||||
"foreign-types-macros",
|
||||
"foreign-types-shared",
|
||||
"foreign-types-shared 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1014,6 +1045,12 @@ dependencies = [
|
||||
"syn 2.0.108",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.3.1"
|
||||
@@ -1424,6 +1461,25 @@ dependencies = [
|
||||
"syn 2.0.108",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.12.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -1472,6 +1528,17 @@ dependencies = [
|
||||
"match_token",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
@@ -1483,6 +1550,17 @@ dependencies = [
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
@@ -1490,7 +1568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1501,8 +1579,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
@@ -1512,6 +1590,36 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.7.0"
|
||||
@@ -1522,8 +1630,8 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
@@ -1533,6 +1641,19 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper 0.14.32",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.17"
|
||||
@@ -1544,14 +1665,14 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.7.0",
|
||||
"ipnet",
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.6.1",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -2055,6 +2176,23 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
@@ -2429,6 +2567,50 @@ dependencies = [
|
||||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.108",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.110"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
@@ -3030,6 +3212,46 @@ version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.32",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 0.1.2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg 0.50.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.24"
|
||||
@@ -3040,10 +3262,10 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper 1.7.0",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
@@ -3052,7 +3274,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
@@ -3112,6 +3334,15 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
@@ -3133,6 +3364,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
@@ -3196,6 +3436,29 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.24.0"
|
||||
@@ -3450,6 +3713,16 @@ version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.1"
|
||||
@@ -3469,7 +3742,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"cfg_aliases",
|
||||
"core-graphics",
|
||||
"foreign-types",
|
||||
"foreign-types 0.5.0",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
@@ -3584,6 +3857,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
@@ -3604,6 +3883,27 @@ dependencies = [
|
||||
"syn 2.0.108",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.2"
|
||||
@@ -3625,7 +3925,7 @@ checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2 0.6.2",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dispatch",
|
||||
@@ -3690,7 +3990,7 @@ dependencies = [
|
||||
"glob",
|
||||
"gtk",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -3704,7 +4004,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
"reqwest",
|
||||
"reqwest 0.12.24",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
@@ -3876,7 +4176,7 @@ dependencies = [
|
||||
"cookie",
|
||||
"dpi",
|
||||
"gtk",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"jni",
|
||||
"objc2 0.6.3",
|
||||
"objc2-ui-kit",
|
||||
@@ -3899,7 +4199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "929f5df216f5c02a9e894554401bcdab6eec3e39ec6a4a7731c7067fc8688a93"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"jni",
|
||||
"log",
|
||||
"objc2 0.6.3",
|
||||
@@ -3932,7 +4232,7 @@ dependencies = [
|
||||
"dunce",
|
||||
"glob",
|
||||
"html5ever",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"infer",
|
||||
"json-patch",
|
||||
"kuchikiki",
|
||||
@@ -4081,13 +4381,36 @@ dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"socket2 0.6.1",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.108",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.17"
|
||||
@@ -4206,7 +4529,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
@@ -4221,8 +4544,8 @@ dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"iri-string",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
@@ -4434,6 +4757,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.1"
|
||||
@@ -4921,6 +5250,24 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -4963,6 +5310,21 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -5020,6 +5382,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -5038,6 +5406,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5056,6 +5430,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -5086,6 +5466,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5104,6 +5490,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -5122,6 +5514,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -5140,6 +5538,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5170,6 +5574,16 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.55.0"
|
||||
@@ -5208,7 +5622,7 @@ dependencies = [
|
||||
"gdkx11",
|
||||
"gtk",
|
||||
"html5ever",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"javascriptcore-rs",
|
||||
"jni",
|
||||
"kuchikiki",
|
||||
|
||||
@@ -25,4 +25,7 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
discord-rich-presence = "0.2"
|
||||
regex = "1"
|
||||
reqwest = { version = "0.11", features = ["json", "blocking"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
|
||||
|
||||
@@ -8,6 +8,29 @@ use discord_rich_presence::{activity, DiscordIpc, DiscordIpcClient};
|
||||
// Cliente Discord RPC global
|
||||
static DISCORD_CLIENT: Mutex<Option<DiscordIpcClient>> = Mutex::new(None);
|
||||
|
||||
// Structs para Codeium API
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct CodeiumCompletionRequest {
|
||||
text: String,
|
||||
cursor_position: usize,
|
||||
language: String,
|
||||
file_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct CodeiumCompletion {
|
||||
text: String,
|
||||
range: CodeiumRange,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct CodeiumRange {
|
||||
start_line: usize,
|
||||
start_column: usize,
|
||||
end_line: usize,
|
||||
end_column: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FileInfo {
|
||||
@@ -749,6 +772,397 @@ fn get_package_scripts(project_root: String) -> Result<Vec<(String, String)>, St
|
||||
Ok(scripts)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// GEMINI API INTEGRATION (Code Completion)
|
||||
// ============================================
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct GeminiRequest {
|
||||
contents: Vec<GeminiContent>,
|
||||
#[serde(rename = "generationConfig")]
|
||||
generation_config: GeminiGenerationConfig,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct GeminiContent {
|
||||
parts: Vec<GeminiPart>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct GeminiPart {
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct GeminiGenerationConfig {
|
||||
temperature: f32,
|
||||
#[serde(rename = "maxOutputTokens")]
|
||||
max_output_tokens: i32,
|
||||
#[serde(rename = "candidateCount")]
|
||||
candidate_count: i32,
|
||||
#[serde(rename = "thinkingConfig", skip_serializing_if = "Option::is_none")]
|
||||
thinking_config: Option<GeminiThinkingConfig>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct GeminiThinkingConfig {
|
||||
#[serde(rename = "thinkingBudget")]
|
||||
thinking_budget: i32,
|
||||
#[serde(rename = "includeThoughts")]
|
||||
include_thoughts: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct GeminiResponse {
|
||||
candidates: Option<Vec<GeminiCandidate>>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct GeminiCandidate {
|
||||
content: GeminiResponseContent,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct GeminiResponseContent {
|
||||
parts: Vec<GeminiResponsePart>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct GeminiResponsePart {
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_gemini_completion(
|
||||
text: String,
|
||||
cursor_position: usize,
|
||||
language: String,
|
||||
file_path: String,
|
||||
api_key: String,
|
||||
model: String,
|
||||
agent_mode: Option<bool>,
|
||||
) -> Result<Vec<String>, String> {
|
||||
use reqwest::Client;
|
||||
|
||||
if api_key.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
// Extraer contexto antes y después del cursor
|
||||
let before = text.chars().take(cursor_position).collect::<String>();
|
||||
let after = text.chars().skip(cursor_position).take(200).collect::<String>();
|
||||
|
||||
// Tomar últimas 15 líneas de contexto
|
||||
let context_lines: Vec<&str> = before.lines().rev().take(15).collect();
|
||||
let context = context_lines.into_iter().rev().collect::<Vec<&str>>().join("\n");
|
||||
|
||||
// Crear prompt optimizado para autocompletado
|
||||
let prompt = format!(
|
||||
"You are a code completion AI. Complete the {} code at the cursor position.
|
||||
|
||||
File: {}
|
||||
Code:
|
||||
```{}
|
||||
{}[CURSOR]{}
|
||||
```
|
||||
|
||||
Complete ONLY what comes immediately after [CURSOR]. Output raw code only, no markdown, no explanations:",
|
||||
language, file_path, language, context, after
|
||||
);
|
||||
|
||||
// Configurar thinking mode basado en agent_mode
|
||||
let (thinking_config, max_tokens) = if agent_mode.unwrap_or(false) {
|
||||
println!("🤖 Modo Agent activado con thinking dinámico");
|
||||
(
|
||||
Some(GeminiThinkingConfig {
|
||||
thinking_budget: -1, // Dinámico: el modelo decide cuánto "pensar"
|
||||
include_thoughts: true, // Incluir resumen de pensamientos
|
||||
}),
|
||||
512 // Más tokens cuando thinking está activado
|
||||
)
|
||||
} else {
|
||||
println!("⚡ Modo rápido: thinking desactivado");
|
||||
(
|
||||
Some(GeminiThinkingConfig {
|
||||
thinking_budget: 0, // Sin thinking para completions rápidos
|
||||
include_thoughts: false,
|
||||
}),
|
||||
120 // Tokens normales para autocompletado rápido
|
||||
)
|
||||
};
|
||||
|
||||
let request_body = GeminiRequest {
|
||||
contents: vec![GeminiContent {
|
||||
parts: vec![GeminiPart { text: prompt }],
|
||||
}],
|
||||
generation_config: GeminiGenerationConfig {
|
||||
temperature: 0.2,
|
||||
max_output_tokens: max_tokens,
|
||||
candidate_count: 1,
|
||||
thinking_config,
|
||||
},
|
||||
};
|
||||
|
||||
let url = format!(
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/{}:generateContent?key={}",
|
||||
model, api_key
|
||||
);
|
||||
|
||||
println!("🚀 Llamando a Gemini API...");
|
||||
println!(" Modelo: {}", model);
|
||||
println!(" URL: {}", url.split("?key=").next().unwrap_or(""));
|
||||
|
||||
let client = Client::new();
|
||||
|
||||
match client
|
||||
.post(&url)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&request_body)
|
||||
.timeout(std::time::Duration::from_secs(8))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
let status = response.status();
|
||||
println!("📡 Status: {}", status);
|
||||
|
||||
if response.status().is_success() {
|
||||
// Primero obtener el texto para debuggear
|
||||
let response_text = response.text().await.map_err(|e| e.to_string())?;
|
||||
println!("📦 Respuesta completa: {}", response_text);
|
||||
|
||||
// Intentar parsear manualmente
|
||||
match serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
Ok(json) => {
|
||||
println!("✅ JSON parseado correctamente");
|
||||
let mut suggestions = Vec::new();
|
||||
|
||||
// Navegar por la estructura real de Gemini
|
||||
if let Some(candidates) = json["candidates"].as_array() {
|
||||
println!("✅ Candidatos encontrados: {}", candidates.len());
|
||||
for (i, candidate) in candidates.iter().enumerate() {
|
||||
println!(" 📋 Procesando candidato #{}", i + 1);
|
||||
|
||||
// Ver el finishReason
|
||||
if let Some(finish_reason) = candidate["finishReason"].as_str() {
|
||||
println!(" 🏁 Finish reason: {}", finish_reason);
|
||||
}
|
||||
|
||||
// Intentar extraer el texto de diferentes formas
|
||||
if let Some(content) = candidate.get("content") {
|
||||
if let Some(parts) = content["parts"].as_array() {
|
||||
println!(" ✅ Parts encontrados: {}", parts.len());
|
||||
for part in parts {
|
||||
// Detectar si es un "thought" (pensamiento del modelo)
|
||||
let is_thought = part["thought"].as_bool().unwrap_or(false);
|
||||
|
||||
if let Some(text) = part["text"].as_str() {
|
||||
if is_thought {
|
||||
println!(" 💭 Pensamiento del modelo: {}", text);
|
||||
// Los pensamientos NO se agregan como sugerencias
|
||||
continue;
|
||||
}
|
||||
|
||||
let cleaned = text.trim()
|
||||
.trim_start_matches("```")
|
||||
.trim_start_matches(&language)
|
||||
.trim_end_matches("```")
|
||||
.trim();
|
||||
|
||||
if !cleaned.is_empty() {
|
||||
println!(" ✨ Sugerencia: {}", cleaned);
|
||||
suggestions.push(cleaned.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!(" ⚠️ No hay 'parts' en content");
|
||||
}
|
||||
} else {
|
||||
println!(" ⚠️ No hay 'content' en candidate");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if suggestions.is_empty() {
|
||||
println!("⚠️ No se pudieron extraer sugerencias del JSON");
|
||||
}
|
||||
|
||||
Ok(suggestions)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error parsing JSON: {:?}", e);
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let error_text = response.text().await.unwrap_or_default();
|
||||
eprintln!("❌ Gemini API error {}: {}", status, error_text);
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error calling Gemini: {:?}", e);
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nueva función para procesar prompts directos (Fix, Explain, etc.)
|
||||
#[tauri::command]
|
||||
async fn ask_gemini(
|
||||
prompt: String,
|
||||
api_key: String,
|
||||
model: String,
|
||||
use_thinking: bool,
|
||||
) -> Result<String, String> {
|
||||
use reqwest::Client;
|
||||
|
||||
if api_key.is_empty() {
|
||||
return Err("No API key provided".to_string());
|
||||
}
|
||||
|
||||
// Configurar thinking mode
|
||||
let (thinking_config, max_tokens) = if use_thinking {
|
||||
println!("🤖 ask_gemini con thinking activado");
|
||||
(
|
||||
Some(GeminiThinkingConfig {
|
||||
thinking_budget: -1,
|
||||
include_thoughts: true,
|
||||
}),
|
||||
2048 // Más tokens para respuestas completas
|
||||
)
|
||||
} else {
|
||||
println!("⚡ ask_gemini modo rápido");
|
||||
(
|
||||
Some(GeminiThinkingConfig {
|
||||
thinking_budget: 0,
|
||||
include_thoughts: false,
|
||||
}),
|
||||
1024
|
||||
)
|
||||
};
|
||||
|
||||
let request_body = GeminiRequest {
|
||||
contents: vec![GeminiContent {
|
||||
parts: vec![GeminiPart { text: prompt }],
|
||||
}],
|
||||
generation_config: GeminiGenerationConfig {
|
||||
temperature: 0.3,
|
||||
max_output_tokens: max_tokens,
|
||||
candidate_count: 1,
|
||||
thinking_config,
|
||||
},
|
||||
};
|
||||
|
||||
let url = format!(
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/{}:generateContent?key={}",
|
||||
model, api_key
|
||||
);
|
||||
|
||||
println!("🚀 ask_gemini llamando a Gemini API...");
|
||||
println!(" Modelo: {}", model);
|
||||
|
||||
let client = Client::new();
|
||||
|
||||
match client
|
||||
.post(&url)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&request_body)
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
let status = response.status();
|
||||
println!("📡 Status: {}", status);
|
||||
|
||||
if response.status().is_success() {
|
||||
let response_text = response.text().await.map_err(|e| e.to_string())?;
|
||||
|
||||
match serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
Ok(json) => {
|
||||
if let Some(candidates) = json["candidates"].as_array() {
|
||||
if let Some(candidate) = candidates.first() {
|
||||
if let Some(content) = candidate.get("content") {
|
||||
if let Some(parts) = content["parts"].as_array() {
|
||||
// Concatenar todas las partes de texto (ignorando thoughts)
|
||||
let mut result = String::new();
|
||||
for part in parts {
|
||||
let is_thought = part["thought"].as_bool().unwrap_or(false);
|
||||
if !is_thought {
|
||||
if let Some(text) = part["text"].as_str() {
|
||||
result.push_str(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !result.is_empty() {
|
||||
println!("✅ Respuesta obtenida: {} caracteres", result.len());
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err("No se encontró contenido en la respuesta".to_string())
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error parsing JSON: {:?}", e);
|
||||
Err(format!("Error parsing response: {}", e))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let error_text = response.text().await.unwrap_or_default();
|
||||
eprintln!("❌ Gemini API error {}: {}", status, error_text);
|
||||
Err(format!("API error: {}", error_text))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error calling Gemini: {:?}", e);
|
||||
Err(format!("Network error: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Guardar configuración de Gemini
|
||||
#[tauri::command]
|
||||
fn save_gemini_config(api_key: String, model: String, app_data_dir: String, agent_mode: Option<bool>, inline_suggestions_enabled: Option<bool>) -> Result<(), String> {
|
||||
// Crear el directorio si no existe
|
||||
let dir_path = Path::new(&app_data_dir);
|
||||
if !dir_path.exists() {
|
||||
fs::create_dir_all(dir_path).map_err(|e| format!("Error creando directorio: {}", e))?;
|
||||
}
|
||||
|
||||
let config_path = dir_path.join("gemini_config.json");
|
||||
|
||||
let config = serde_json::json!({
|
||||
"api_key": api_key,
|
||||
"model": model,
|
||||
"enabled": true,
|
||||
"agent_mode": agent_mode.unwrap_or(false),
|
||||
"inline_suggestions_enabled": inline_suggestions_enabled.unwrap_or(false)
|
||||
});
|
||||
|
||||
fs::write(&config_path, serde_json::to_string_pretty(&config).unwrap())
|
||||
.map_err(|e| format!("Error guardando archivo: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Leer configuración de Gemini
|
||||
#[tauri::command]
|
||||
fn load_gemini_config(app_data_dir: String) -> Result<String, String> {
|
||||
let config_path = Path::new(&app_data_dir).join("gemini_config.json");
|
||||
|
||||
if !config_path.exists() {
|
||||
return Err("No hay API key configurada".to_string());
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&config_path).map_err(|e| e.to_string())?;
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
@@ -778,7 +1192,11 @@ pub fn run() {
|
||||
write_env_file,
|
||||
scan_env_variables,
|
||||
scan_env_variables_with_locations,
|
||||
get_package_scripts
|
||||
get_package_scripts,
|
||||
get_gemini_completion,
|
||||
ask_gemini,
|
||||
save_gemini_config,
|
||||
load_gemini_config
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -10,6 +10,7 @@ import CommandPalette from "./components/CommandPalette.vue";
|
||||
import SkeletonLoader from "./components/SkeletonLoader.vue";
|
||||
import DatabaseViewer from "./components/DatabaseViewer.vue";
|
||||
import EnvManager from "./components/EnvManager.vue";
|
||||
import GeminiSettings from "./components/GeminiSettings.vue";
|
||||
import type { ProjectStats, FileInfo, Command, Event } from "./types/bot";
|
||||
|
||||
// Estado de la aplicación
|
||||
@@ -31,7 +32,7 @@ const events = ref<FileInfo[]>([]);
|
||||
const allFiles = ref<FileInfo[]>([]);
|
||||
const selectedFile = ref<FileInfo | null>(null);
|
||||
const fileContent = ref<string>("");
|
||||
const currentView = ref<"editor" | "command-creator" | "event-creator" | "database" | "env-manager">("editor");
|
||||
const currentView = ref<"editor" | "command-creator" | "event-creator" | "database" | "env-manager" | "gemini-settings">("editor");
|
||||
const loading = ref(false);
|
||||
const errorMsg = ref<string>("");
|
||||
const schemaContent = ref<string>("");
|
||||
@@ -340,6 +341,17 @@ function toggleEnvManager() {
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle Gemini Settings
|
||||
function toggleGeminiSettings() {
|
||||
if (currentView.value === 'gemini-settings') {
|
||||
currentView.value = 'editor';
|
||||
updateDiscordRPC("Navegando proyecto", "En el editor");
|
||||
} else {
|
||||
currentView.value = 'gemini-settings';
|
||||
updateDiscordRPC("Configurando Gemini AI", "Google Gemini Assistant");
|
||||
}
|
||||
}
|
||||
|
||||
// Guardar schema de base de datos
|
||||
async function saveSchema(content: string) {
|
||||
try {
|
||||
@@ -423,6 +435,7 @@ function handlePaletteCommand(commandId: string) {
|
||||
@toggle-dev-ultra="toggleDevUltra"
|
||||
@toggle-database="toggleDatabase"
|
||||
@toggle-env-manager="toggleEnvManager"
|
||||
@toggle-gemini-settings="toggleGeminiSettings"
|
||||
@notify="showNotification"
|
||||
/>
|
||||
|
||||
@@ -474,6 +487,11 @@ function handlePaletteCommand(commandId: string) {
|
||||
@notify="showNotification"
|
||||
/>
|
||||
|
||||
<!-- Gemini Settings -->
|
||||
<GeminiSettings
|
||||
v-if="currentView === 'gemini-settings'"
|
||||
/>
|
||||
|
||||
<!-- Welcome Screen -->
|
||||
<div v-if="currentView === 'editor' && !selectedFile" class="welcome-screen">
|
||||
<div class="welcome-content">
|
||||
@@ -719,4 +737,139 @@ body {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE DESIGN - Media Queries
|
||||
============================================ */
|
||||
|
||||
/* Pantallas grandes (1920px+) */
|
||||
@media (min-width: 1920px) {
|
||||
.welcome-content h1 {
|
||||
font-size: 64px;
|
||||
}
|
||||
|
||||
.welcome-content > p {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 56px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pantallas medianas-grandes (1366px - 1919px) */
|
||||
@media (min-width: 1366px) and (max-width: 1919px) {
|
||||
.welcome-content h1 {
|
||||
font-size: 52px;
|
||||
}
|
||||
|
||||
.welcome-stats {
|
||||
gap: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tabletas y pantallas pequeñas (768px - 1365px) */
|
||||
@media (max-width: 1365px) {
|
||||
.app-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.welcome-content h1 {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.welcome-content > p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.welcome-stats {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Móviles y pantallas muy pequeñas (< 768px) */
|
||||
@media (max-width: 767px) {
|
||||
.app-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.welcome-content h1 {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.welcome-content > p {
|
||||
font-size: 14px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.notification {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
font-size: 13px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.error-banner {
|
||||
font-size: 12px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ajustes para pantallas ultra-wide (2560px+) */
|
||||
@media (min-width: 2560px) {
|
||||
.welcome-content {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.welcome-content h1 {
|
||||
font-size: 72px;
|
||||
}
|
||||
|
||||
.welcome-content > p {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ajustes de altura para pantallas cortas */
|
||||
@media (max-height: 700px) {
|
||||
.welcome-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.welcome-content h1 {
|
||||
font-size: 36px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.welcome-content > p {
|
||||
font-size: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.welcome-stats {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
238
AEditor/src/components/GeminiSettings.vue
Normal file
238
AEditor/src/components/GeminiSettings.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="gemini-settings">
|
||||
<div class="settings-header">
|
||||
<h3>✨ Configuración de Gemini AI</h3>
|
||||
<p class="subtitle">Autocompletado inteligente con Google Gemini</p>
|
||||
<div class="info-box">
|
||||
<p><strong>💡 Cómo usar Gemini:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Inline Suggestions:</strong> Escribe código y aparecerán sugerencias en gris. Presiona <kbd>Tab</kbd> para aceptar.</li>
|
||||
<li><strong>Modo Thinking:</strong> Activa para código complejo. El modelo "piensa" antes de sugerir (más lento pero más preciso).</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div> <div class="settings-content">
|
||||
<div class="status-badge" :class="{ active: isConfigured, inactive: !isConfigured }">
|
||||
<span class="status-dot"></span>
|
||||
{{ isConfigured ? '✓ Configurado' : '○ No configurado' }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="model">Modelo de IA</label>
|
||||
<select id="model" v-model="selectedModel" class="model-select" @change="hasChanges = true">
|
||||
<option value="gemini-2.5-flash">⚡ Gemini 2.5 Flash (Rápido)</option>
|
||||
<option value="gemini-2.5-pro">🚀 Gemini 2.5 Pro (Potente)</option>
|
||||
<option value="gemini-1.5-flash">⚡ Gemini 1.5 Flash (Legacy)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="agentMode" @change="hasChanges = true" />
|
||||
<span class="checkbox-text">
|
||||
<strong>🧠 Modo Thinking (Experimental)</strong>
|
||||
<small>El modelo razona internamente antes de sugerir código (más tokens, más lento)</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label warning-label">
|
||||
<input type="checkbox" v-model="inlineSuggestionsEnabled" @change="hasChanges = true" />
|
||||
<span class="checkbox-text">
|
||||
<strong>⚡ Autocompletado Inline (Alto consumo de tokens)</strong>
|
||||
<small>
|
||||
<span class="warning-text">⚠️ ADVERTENCIA:</span> Genera sugerencias automáticamente mientras escribes.
|
||||
Cada pausa de 500ms hace una llamada a la API. Puede consumir muchos tokens rápidamente.
|
||||
<br><strong>Recomendación:</strong> Usa solo los botones de acción (Fix, Explain, etc.) para ahorrar tokens.
|
||||
</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="apiKey">API Key de Google</label>
|
||||
<div class="input-wrapper">
|
||||
<input id="apiKey" v-model="apiKey" :type="showApiKey ? 'text' : 'password'" placeholder="Ingresa tu API key" class="api-key-input" @input="hasChanges = true" />
|
||||
<button class="toggle-visibility-btn" @click="showApiKey = !showApiKey" type="button">{{ showApiKey ? '👁️' : '👁️🗨️' }}</button>
|
||||
</div>
|
||||
<span class="helper-text">
|
||||
<a href="https://aistudio.google.com/app/apikey" target="_blank" class="link">Obtén tu API key gratis aquí</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" :disabled="!hasChanges || !apiKey" @click="saveSettings">💾 Guardar</button>
|
||||
<button v-if="isConfigured" class="btn btn-test" @click="testConnection" :disabled="testing">{{ testing ? '⏳ Probando...' : '🧪 Probar' }}</button>
|
||||
</div>
|
||||
|
||||
<div v-if="message" class="message" :class="messageType">{{ message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { appDataDir } from '@tauri-apps/api/path';
|
||||
|
||||
const apiKey = ref('');
|
||||
const showApiKey = ref(false);
|
||||
const selectedModel = ref('gemini-2.5-flash');
|
||||
const agentMode = ref(false);
|
||||
const inlineSuggestionsEnabled = ref(false);
|
||||
const hasChanges = ref(false);
|
||||
const isConfigured = ref(false);
|
||||
const testing = ref(false);
|
||||
const message = ref('');
|
||||
const messageType = ref<'success' | 'error' | 'info'>('info');
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const dataDir = await appDataDir();
|
||||
const configJson = await invoke<string>('load_gemini_config', { appDataDir: dataDir });
|
||||
const config = JSON.parse(configJson);
|
||||
if (config.api_key) {
|
||||
apiKey.value = config.api_key;
|
||||
selectedModel.value = config.model || 'gemini-2.5-flash';
|
||||
agentMode.value = config.agent_mode || false;
|
||||
inlineSuggestionsEnabled.value = config.inline_suggestions_enabled || false;
|
||||
isConfigured.value = true;
|
||||
localStorage.setItem('gemini_api_key', config.api_key);
|
||||
localStorage.setItem('gemini_model', config.model);
|
||||
localStorage.setItem('gemini_agent_mode', config.agent_mode ? 'true' : 'false');
|
||||
localStorage.setItem('gemini_inline_suggestions', config.inline_suggestions_enabled ? 'true' : 'false');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('No hay configuración previa');
|
||||
}
|
||||
});
|
||||
|
||||
async function saveSettings() {
|
||||
if (!apiKey.value) {
|
||||
showMessage('Por favor ingresa una API key', 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const dataDir = await appDataDir();
|
||||
await invoke('save_gemini_config', {
|
||||
apiKey: apiKey.value,
|
||||
model: selectedModel.value,
|
||||
appDataDir: dataDir,
|
||||
agentMode: agentMode.value,
|
||||
inlineSuggestionsEnabled: inlineSuggestionsEnabled.value
|
||||
});
|
||||
localStorage.setItem('gemini_api_key', apiKey.value);
|
||||
localStorage.setItem('gemini_model', selectedModel.value);
|
||||
localStorage.setItem('gemini_agent_mode', agentMode.value ? 'true' : 'false');
|
||||
localStorage.setItem('gemini_inline_suggestions', inlineSuggestionsEnabled.value ? 'true' : 'false');
|
||||
isConfigured.value = true;
|
||||
hasChanges.value = false;
|
||||
showMessage('✅ Configuración guardada', 'success');
|
||||
} catch (error) {
|
||||
showMessage(`❌ Error: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function testConnection() {
|
||||
if (!apiKey.value) return;
|
||||
testing.value = true;
|
||||
showMessage('🔍 Probando conexión...', 'info');
|
||||
try {
|
||||
const result = await invoke<string[]>('get_gemini_completion', {
|
||||
text: 'function hello() {\n ',
|
||||
cursorPosition: 21,
|
||||
language: 'javascript',
|
||||
filePath: 'test.js',
|
||||
apiKey: apiKey.value,
|
||||
model: selectedModel.value,
|
||||
agentMode: false // Siempre desactivar thinking en el test para que sea rápido
|
||||
});
|
||||
if (result && result.length > 0) {
|
||||
showMessage(`✅ Funciona! Sugerencia: "${result[0].substring(0, 30)}..."`, 'success');
|
||||
} else {
|
||||
showMessage('⚠️ Sin respuesta. Verifica tu API key o aumenta max_output_tokens', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage(`❌ Error: ${error}`, 'error');
|
||||
} finally {
|
||||
testing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showMessage(msg: string, type: 'success' | 'error' | 'info') {
|
||||
message.value = msg;
|
||||
messageType.value = type;
|
||||
if (type === 'success') setTimeout(() => message.value = '', 5000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gemini-settings { padding: 30px; max-width: 700px; margin: 0 auto; color: #e0e0e0; background: #1e1e1e; min-height: 100vh; }
|
||||
.settings-header { margin-bottom: 30px; border-bottom: 2px solid rgba(66, 133, 244, 0.3); padding-bottom: 15px; }
|
||||
.settings-header h3 { margin: 0 0 8px 0; font-size: 28px; color: #fff; font-weight: 600; background: linear-gradient(135deg, #4285f4 0%, #34a853 50%, #fbbc04 75%, #ea4335 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
|
||||
.subtitle { margin: 0 0 15px 0; color: #b0b0b0; font-size: 15px; }
|
||||
.info-box { background: rgba(66, 133, 244, 0.1); border: 2px solid rgba(66, 133, 244, 0.3); border-radius: 8px; padding: 16px; margin-top: 15px; }
|
||||
.info-box p { margin: 0 0 10px 0; color: #fff; font-size: 14px; }
|
||||
.info-box ul { margin: 0; padding-left: 20px; list-style: none; }
|
||||
.info-box li { margin: 8px 0; color: #e0e0e0; font-size: 13px; line-height: 1.6; position: relative; padding-left: 8px; }
|
||||
.info-box li::before { content: "•"; position: absolute; left: -12px; color: #4285f4; font-weight: bold; }
|
||||
.info-box kbd { background: #2d2d30; padding: 2px 6px; border-radius: 4px; border: 1px solid #3e3e42; font-family: monospace; font-size: 12px; color: #4285f4; }
|
||||
.settings-content { display: flex; flex-direction: column; gap: 20px; }
|
||||
.status-badge { display: inline-flex; align-items: center; gap: 8px; padding: 10px 20px; border-radius: 20px; font-size: 14px; font-weight: 600; width: fit-content; border: 2px solid; }
|
||||
.status-badge.active { background: rgba(52, 168, 83, 0.25); color: #34a853; border-color: rgba(52, 168, 83, 0.5); }
|
||||
.status-badge.inactive { background: rgba(158, 158, 158, 0.15); color: #ccc; border-color: rgba(158, 158, 158, 0.3); }
|
||||
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
|
||||
.form-group { display: flex; flex-direction: column; gap: 8px; }
|
||||
.form-group label { font-size: 14px; font-weight: 600; color: #fff; margin-bottom: 8px; }
|
||||
.checkbox-label { display: flex; align-items: flex-start; gap: 12px; cursor: pointer; padding: 12px; background: #2d2d30; border-radius: 8px; border: 2px solid #3e3e42; transition: all 0.2s; }
|
||||
.checkbox-label:hover { border-color: #4285f4; background: #353538; }
|
||||
.checkbox-label.warning-label { border-color: #fbbc04; background: rgba(251, 188, 4, 0.05); }
|
||||
.checkbox-label.warning-label:hover { border-color: #ffc928; background: rgba(251, 188, 4, 0.1); }
|
||||
.checkbox-label input[type="checkbox"] { width: 20px; height: 20px; cursor: pointer; accent-color: #4285f4; margin-top: 2px; }
|
||||
.checkbox-text { display: flex; flex-direction: column; gap: 4px; flex: 1; }
|
||||
.checkbox-text strong { color: #fff; font-size: 14px; }
|
||||
.checkbox-text small { color: #b0b0b0; font-size: 12px; line-height: 1.4; }
|
||||
.checkbox-text .warning-text { color: #fbbc04; font-weight: 500; }
|
||||
.checkbox-text .warning-text::before { content: "⚠️ "; }
|
||||
.model-select, .api-key-input { padding: 12px 16px; background: #2d2d30; color: #e0e0e0; border: 2px solid #3e3e42; border-radius: 6px; font-size: 14px; transition: all 0.2s; }
|
||||
.model-select:focus, .api-key-input:focus { outline: none; border-color: #4285f4; box-shadow: 0 0 0 3px rgba(66, 133, 244, 0.1); }
|
||||
.input-wrapper { display: flex; gap: 10px; }
|
||||
.api-key-input { flex: 1; font-family: 'Consolas', monospace; }
|
||||
.toggle-visibility-btn { padding: 12px 16px; background: #3e3e42; color: #e0e0e0; border: 2px solid #3e3e42; border-radius: 6px; cursor: pointer; font-size: 18px; transition: all 0.2s; }
|
||||
.toggle-visibility-btn:hover { background: #4e4e52; border-color: #4285f4; transform: scale(1.05); }
|
||||
.helper-text { font-size: 13px; color: #b0b0b0; }
|
||||
.link { color: #4285f4; text-decoration: none; font-weight: 500; }
|
||||
.link:hover { color: #5a9dff; text-decoration: underline; }
|
||||
.actions { display: flex; gap: 12px; margin-top: 10px; }
|
||||
.btn { padding: 12px 24px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.2s; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); }
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.btn-primary { background: linear-gradient(135deg, #4285f4 0%, #34a853 100%); color: white; }
|
||||
.btn-primary:hover:not(:disabled) { background: linear-gradient(135deg, #5a9dff 0%, #46ba65 100%); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(66, 133, 244, 0.4); }
|
||||
.btn-test { background: #2d2d30; color: #e0e0e0; border: 2px solid #4285f4; }
|
||||
.btn-test:hover:not(:disabled) { background: #3e3e42; transform: translateY(-2px); }
|
||||
.message { padding: 14px 18px; border-radius: 8px; font-size: 14px; font-weight: 500; border: 2px solid; animation: slideIn 0.3s ease; }
|
||||
.message.success { background: rgba(52, 168, 83, 0.2); color: #34a853; border-color: rgba(52, 168, 83, 0.5); }
|
||||
.message.error { background: rgba(234, 67, 53, 0.2); color: #ea4335; border-color: rgba(234, 67, 53, 0.5); }
|
||||
.message.info { background: rgba(66, 133, 244, 0.2); color: #4285f4; border-color: rgba(66, 133, 244, 0.5); }
|
||||
@keyframes slideIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.gemini-settings { padding: 20px; max-width: 100%; }
|
||||
.settings-header h3 { font-size: 24px; }
|
||||
.subtitle { font-size: 14px; }
|
||||
.info-box { padding: 12px; }
|
||||
.info-box p { font-size: 13px; }
|
||||
.info-box li { font-size: 12px; }
|
||||
.actions { flex-direction: column; }
|
||||
.btn { width: 100%; }
|
||||
.input-wrapper { flex-direction: column; }
|
||||
.toggle-visibility-btn { width: 100%; }
|
||||
}
|
||||
|
||||
@media (min-width: 1920px) {
|
||||
.gemini-settings { max-width: 900px; padding: 40px; }
|
||||
.settings-header h3 { font-size: 32px; }
|
||||
.subtitle { font-size: 17px; }
|
||||
}
|
||||
</style>
|
||||
@@ -13,12 +13,31 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-content" ref="editorContainer"></div>
|
||||
|
||||
<!-- Widget de acciones de IA -->
|
||||
<Transition name="fade">
|
||||
<div v-if="showAIActions" class="ai-actions-widget" :style="aiWidgetPosition">
|
||||
<button @click="handleAIAction('fix')" class="ai-action-btn fix-btn" title="Corregir código">
|
||||
🔧 Fix
|
||||
</button>
|
||||
<button @click="handleAIAction('explain')" class="ai-action-btn explain-btn" title="Explicar código">
|
||||
💡 Explain
|
||||
</button>
|
||||
<button @click="handleAIAction('refactor')" class="ai-action-btn refactor-btn" title="Refactorizar código">
|
||||
♻️ Refactor
|
||||
</button>
|
||||
<button @click="handleAIAction('optimize')" class="ai-action-btn optimize-btn" title="Optimizar código">
|
||||
⚡ Optimize
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import type { FileInfo } from '../types/bot';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -34,6 +53,9 @@ const emit = defineEmits<{
|
||||
const editorContainer = ref<HTMLElement | null>(null);
|
||||
let editor: monaco.editor.IStandaloneCodeEditor | null = null;
|
||||
const hasChanges = ref(false);
|
||||
const showAIActions = ref(false);
|
||||
const aiWidgetPosition = ref({ top: '0px', left: '0px' });
|
||||
const selectedText = ref('');
|
||||
|
||||
function getFileIcon(): string {
|
||||
if (!props.fileInfo) return '📄';
|
||||
@@ -43,6 +65,95 @@ function getFileIcon(): string {
|
||||
return props.fileInfo.eventType === 'extra' ? '✨' : '🎯';
|
||||
}
|
||||
|
||||
async function handleAIAction(action: 'fix' | 'explain' | 'refactor' | 'optimize') {
|
||||
console.log('🎯 Acción de IA:', action);
|
||||
|
||||
if (!editor || !selectedText.value) {
|
||||
console.log('⚠️ No hay editor o texto seleccionado');
|
||||
return;
|
||||
}
|
||||
|
||||
const apiKey = localStorage.getItem('gemini_api_key');
|
||||
const model = localStorage.getItem('gemini_model') || 'gemini-2.5-flash';
|
||||
|
||||
if (!apiKey) {
|
||||
alert('⚠️ Configura tu API key de Gemini primero en la sección "✨ Gemini IA"');
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = editor.getSelection();
|
||||
if (!selection) {
|
||||
console.log('⚠️ No hay selección válida');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📝 Texto seleccionado:', selectedText.value.substring(0, 50) + '...');
|
||||
|
||||
const prompts = {
|
||||
fix: `Fix any errors or bugs in this code. Return ONLY the corrected code, no explanations:\n\n${selectedText.value}`,
|
||||
explain: `Explain what this code does in Spanish. Be concise and clear:\n\n${selectedText.value}`,
|
||||
refactor: `Refactor this code to make it cleaner and more maintainable. Return ONLY the refactored code:\n\n${selectedText.value}`,
|
||||
optimize: `Optimize this code for better performance. Return ONLY the optimized code:\n\n${selectedText.value}`
|
||||
};
|
||||
|
||||
try {
|
||||
showAIActions.value = false;
|
||||
console.log('🚀 Llamando a Gemini con ask_gemini...');
|
||||
console.log('📝 Prompt:', prompts[action].substring(0, 100) + '...');
|
||||
|
||||
const result = await invoke<string>('ask_gemini', {
|
||||
prompt: prompts[action],
|
||||
apiKey,
|
||||
model,
|
||||
useThinking: true // Usar thinking para mejores resultados
|
||||
});
|
||||
|
||||
console.log('✅ Respuesta recibida:', result.substring(0, 100) + '...');
|
||||
|
||||
if (result && result.length > 0) {
|
||||
if (action === 'explain') {
|
||||
// Mostrar explicación en un mensaje
|
||||
alert(`💡 Explicación:\n\n${result}`);
|
||||
} else {
|
||||
// Reemplazar código seleccionado
|
||||
const edit = {
|
||||
range: selection,
|
||||
text: result
|
||||
};
|
||||
editor.executeEdits('ai-action', [edit]);
|
||||
hasChanges.value = true;
|
||||
console.log('✅ Código reemplazado');
|
||||
}
|
||||
} else {
|
||||
console.log('⚠️ No se recibió respuesta');
|
||||
alert('⚠️ No se pudo obtener una respuesta de Gemini. Intenta de nuevo.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error en acción de IA:', error);
|
||||
alert(`❌ Error: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getLanguageFromFile(): string {
|
||||
const ext = props.fileInfo?.path?.split('.').pop() || 'txt';
|
||||
const langMap: Record<string, string> = {
|
||||
'ts': 'typescript',
|
||||
'js': 'javascript',
|
||||
'json': 'json',
|
||||
'md': 'markdown',
|
||||
'py': 'python',
|
||||
'rs': 'rust',
|
||||
'go': 'go',
|
||||
'java': 'java',
|
||||
'cpp': 'cpp',
|
||||
'c': 'c',
|
||||
'html': 'html',
|
||||
'css': 'css',
|
||||
'vue': 'vue'
|
||||
};
|
||||
return langMap[ext] || 'text';
|
||||
}
|
||||
|
||||
function saveFile() {
|
||||
if (editor && hasChanges.value) {
|
||||
const content = editor.getValue();
|
||||
@@ -668,8 +779,9 @@ onMounted(() => {
|
||||
// Sugerencias inline (como GitHub Copilot)
|
||||
inlineSuggest: {
|
||||
enabled: true,
|
||||
mode: 'prefix',
|
||||
showToolbar: 'onHover'
|
||||
mode: 'subwordSmart',
|
||||
showToolbar: 'always',
|
||||
suppressSuggestions: false
|
||||
},
|
||||
|
||||
// Peekable definition/references
|
||||
@@ -1499,12 +1611,168 @@ onMounted(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// CODEIUM INLINE COMPLETION PROVIDER
|
||||
// ============================================
|
||||
|
||||
let lastCompletionTimeout: number | null = null;
|
||||
|
||||
// Solo registrar si el usuario tiene activadas las sugerencias inline
|
||||
const inlineSuggestionsEnabled = localStorage.getItem('gemini_inline_suggestions') === 'true';
|
||||
|
||||
if (inlineSuggestionsEnabled) {
|
||||
console.log('✅ Sugerencias inline de Gemini habilitadas');
|
||||
|
||||
// Registrar proveedor de completación inline (estilo Copilot)
|
||||
monaco.languages.registerInlineCompletionsProvider('typescript', {
|
||||
provideInlineCompletions: async (model, position) => {
|
||||
// Cancelar solicitud anterior si existe
|
||||
if (lastCompletionTimeout) {
|
||||
clearTimeout(lastCompletionTimeout);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
lastCompletionTimeout = window.setTimeout(async () => {
|
||||
try {
|
||||
const text = model.getValue();
|
||||
const offset = model.getOffsetAt(position);
|
||||
const lineContent = model.getLineContent(position.lineNumber);
|
||||
|
||||
// Solo sugerir si hay contenido en la línea actual o si es inicio de línea
|
||||
if (lineContent.trim().length === 0 && position.column > 1) {
|
||||
resolve({ items: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🤖 Solicitando sugerencia de Gemini...');
|
||||
|
||||
// Llamar al backend para obtener sugerencias
|
||||
const suggestions = await invoke<string[]>('get_gemini_completion', {
|
||||
text,
|
||||
cursorPosition: offset,
|
||||
language: 'typescript',
|
||||
filePath: props.fileInfo?.path || 'untitled.ts',
|
||||
apiKey: localStorage.getItem('gemini_api_key') || '',
|
||||
model: localStorage.getItem('gemini_model') || 'gemini-2.5-flash',
|
||||
agentMode: localStorage.getItem('gemini_agent_mode') === 'true'
|
||||
});
|
||||
|
||||
if (suggestions && suggestions.length > 0) {
|
||||
console.log('✅ Sugerencias recibidas:', suggestions);
|
||||
const items = suggestions.map((suggestion: string) => ({
|
||||
insertText: suggestion,
|
||||
range: {
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn: position.column,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: position.column
|
||||
}
|
||||
}));
|
||||
|
||||
console.log('📤 Items a mostrar:', items);
|
||||
resolve({ items });
|
||||
} else {
|
||||
console.log('⚠️ No se recibieron sugerencias');
|
||||
resolve({ items: [] });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error obteniendo sugerencia:', error);
|
||||
resolve({ items: [] });
|
||||
}
|
||||
}, 500); // Debounce de 500ms
|
||||
});
|
||||
},
|
||||
// Método requerido por Monaco para liberar recursos
|
||||
disposeInlineCompletions: () => {}
|
||||
});
|
||||
|
||||
// También para JavaScript
|
||||
monaco.languages.registerInlineCompletionsProvider('javascript', {
|
||||
provideInlineCompletions: async (model, position) => {
|
||||
if (lastCompletionTimeout) {
|
||||
clearTimeout(lastCompletionTimeout);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
lastCompletionTimeout = window.setTimeout(async () => {
|
||||
try {
|
||||
const text = model.getValue();
|
||||
const offset = model.getOffsetAt(position);
|
||||
|
||||
const suggestions = await invoke<string[]>('get_gemini_completion', {
|
||||
text,
|
||||
cursorPosition: offset,
|
||||
language: 'javascript',
|
||||
filePath: props.fileInfo?.path || 'untitled.js',
|
||||
apiKey: localStorage.getItem('gemini_api_key') || '',
|
||||
model: localStorage.getItem('gemini_model') || 'gemini-2.5-flash',
|
||||
agentMode: localStorage.getItem('gemini_agent_mode') === 'true'
|
||||
});
|
||||
|
||||
if (suggestions && suggestions.length > 0) {
|
||||
const items = suggestions.map((suggestion: string) => ({
|
||||
insertText: suggestion,
|
||||
range: {
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn: position.column,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: position.column
|
||||
}
|
||||
}));
|
||||
|
||||
resolve({ items });
|
||||
} else {
|
||||
resolve({ items: [] });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error obteniendo sugerencia:', error);
|
||||
resolve({ items: [] });
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
disposeInlineCompletions: () => {}
|
||||
});
|
||||
} else {
|
||||
console.log('ℹ️ Sugerencias inline de Gemini deshabilitadas. Usa los botones de acción (Fix, Explain, etc.) para invocar la IA.');
|
||||
}
|
||||
|
||||
console.log('🤖 Gemini inline completion provider registrado');
|
||||
|
||||
// Detectar cambios
|
||||
editor.onDidChangeModelContent(() => {
|
||||
hasChanges.value = true;
|
||||
emit('change', editor!.getValue());
|
||||
});
|
||||
|
||||
// Detectar selección de texto para mostrar widget de IA
|
||||
editor.onDidChangeCursorSelection(() => {
|
||||
const selection = editor!.getSelection();
|
||||
if (selection && !selection.isEmpty()) {
|
||||
const text = editor!.getModel()!.getValueInRange(selection);
|
||||
if (text.trim().length > 0) {
|
||||
selectedText.value = text;
|
||||
console.log('✅ Texto seleccionado para IA:', text.substring(0, 50) + '...');
|
||||
|
||||
// Posicionar widget relativo al editor
|
||||
const layoutInfo = editor!.getLayoutInfo();
|
||||
const selectionStart = editor!.getScrolledVisiblePosition(selection.getStartPosition());
|
||||
|
||||
if (selectionStart) {
|
||||
aiWidgetPosition.value = {
|
||||
top: `${Math.max(0, selectionStart.top - 50)}px`,
|
||||
left: `${Math.min(selectionStart.left, layoutInfo.width - 400)}px`
|
||||
};
|
||||
showAIActions.value = true;
|
||||
console.log('🔧 Widget mostrado');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showAIActions.value = false;
|
||||
selectedText.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Atajos de teclado
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
||||
saveFile();
|
||||
@@ -1548,6 +1816,7 @@ onUnmounted(() => {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: #1e1e1e;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
@@ -1614,6 +1883,77 @@ onUnmounted(() => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Widget de acciones de IA */
|
||||
.ai-actions-widget {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding: 8px;
|
||||
background: #252526;
|
||||
border: 2px solid #007acc;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.6);
|
||||
z-index: 999999;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ai-action-btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ai-action-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.fix-btn {
|
||||
background: linear-gradient(135deg, #f48771 0%, #e74c3c 100%);
|
||||
}
|
||||
|
||||
.fix-btn:hover {
|
||||
background: linear-gradient(135deg, #ff9a85 0%, #ff5c4d 100%);
|
||||
}
|
||||
|
||||
.explain-btn {
|
||||
background: linear-gradient(135deg, #4fc3f7 0%, #2196f3 100%);
|
||||
}
|
||||
|
||||
.explain-btn:hover {
|
||||
background: linear-gradient(135deg, #6dd5f9 0%, #42a5f5 100%);
|
||||
}
|
||||
|
||||
.refactor-btn {
|
||||
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
|
||||
}
|
||||
|
||||
.refactor-btn:hover {
|
||||
background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%);
|
||||
}
|
||||
|
||||
.optimize-btn {
|
||||
background: linear-gradient(135deg, #ffb74d 0%, #ff9800 100%);
|
||||
}
|
||||
|
||||
.optimize-btn:hover {
|
||||
background: linear-gradient(135deg, #ffca64 0%, #ffa726 100%);
|
||||
}
|
||||
|
||||
/* Estilos para mejor visualización de errores y warnings */
|
||||
:deep(.monaco-editor .squiggly-error) {
|
||||
background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23f48771'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left;
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
<button @click="emit('toggle-env-manager')" class="action-btn env-manager">
|
||||
🔐 Variables ENV
|
||||
</button>
|
||||
<button @click="emit('toggle-gemini-settings')" class="action-btn gemini">
|
||||
✨ Gemini IA
|
||||
</button>
|
||||
<button @click="emit('refresh')" class="action-btn secondary">
|
||||
🔄 Refrescar
|
||||
</button>
|
||||
@@ -187,6 +190,7 @@ const emit = defineEmits<{
|
||||
'toggle-dev-ultra': [];
|
||||
'toggle-database': [];
|
||||
'toggle-env-manager': [];
|
||||
'toggle-gemini-settings': [];
|
||||
'notify': [message: string, type: 'success' | 'error' | 'info'];
|
||||
}>();
|
||||
|
||||
@@ -355,6 +359,19 @@ function truncatePath(path: string): string {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.action-btn.gemini {
|
||||
background: linear-gradient(135deg, #4285f4 0%, #34a853 50%, #fbbc04 75%, #ea4335 100%);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 8px rgba(66, 133, 244, 0.3);
|
||||
}
|
||||
|
||||
.action-btn.gemini:hover {
|
||||
background: linear-gradient(135deg, #5a9dff 0%, #46ba65 50%, #ffc825 75%, #f55a4e 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(66, 133, 244, 0.5);
|
||||
}
|
||||
|
||||
.files-section {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
Reference in New Issue
Block a user