feat: 优化排版,完善获取git提交记录以及预览功能
This commit is contained in:
3
bun.lock
3
bun.lock
@@ -6,6 +6,7 @@
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "^2",
|
||||
"@tauri-apps/plugin-http": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"antd": "^5.24.8",
|
||||
"react": "^18.3.1",
|
||||
@@ -277,6 +278,8 @@
|
||||
|
||||
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-wZmCouo4PgTosh/UoejPw9DPs6RllS5Pp3fuOV2JobCu36mR5AXU2MzU9NZiVaFi/5Zfc8RN0IhcZHnksJ1o8A=="],
|
||||
|
||||
"@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.4.3", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-Us8X+FikzpaZRNr4kH4HLwyXascHbM42p6LxAqRTQnHPrrqp1usaH4vxWAZalPvTbHJ3gBEMJPHusFJgtjGJjA=="],
|
||||
|
||||
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.2.6", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-bSdkuP71ZQRepPOn8BOEdBKYJQvl6+jb160QtJX/i2H9BF6ZySY/kYljh76N2Ne5fJMQRge7rlKoStYQY5Jq1w=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"dev": "tauri dev",
|
||||
"vite:dev": "vite",
|
||||
"tauri": "tauri",
|
||||
"td": "tauri dev",
|
||||
"lint": "bunx @biomejs/biome lint ./src",
|
||||
"format": "bunx @biomejs/biome format --write ./src",
|
||||
"check": "bunx @biomejs/biome check --apply ./src"
|
||||
@@ -16,6 +14,7 @@
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "^2",
|
||||
"@tauri-apps/plugin-http": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"antd": "^5.24.8",
|
||||
"react": "^18.3.1",
|
||||
|
||||
372
src-tauri/Cargo.lock
generated
372
src-tauri/Cargo.lock
generated
@@ -549,10 +549,39 @@ version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie_store"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
|
||||
dependencies = [
|
||||
"cookie",
|
||||
"document-features",
|
||||
"idna",
|
||||
"log",
|
||||
"publicsuffix",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[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.0"
|
||||
@@ -576,7 +605,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
@@ -589,7 +618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -646,6 +675,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-http",
|
||||
"tauri-plugin-opener",
|
||||
]
|
||||
|
||||
@@ -721,6 +751,12 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
@@ -837,6 +873,15 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
|
||||
dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dpi"
|
||||
version = "0.1.1"
|
||||
@@ -893,6 +938,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"
|
||||
@@ -1275,8 +1329,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1286,9 +1342,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1460,6 +1518,25 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.9.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -1559,6 +1636,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
@@ -1569,6 +1647,24 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.11"
|
||||
@@ -2061,6 +2157,12 @@ version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
@@ -2890,6 +2992,22 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psl-types"
|
||||
version = "2.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
|
||||
|
||||
[[package]]
|
||||
name = "publicsuffix"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
|
||||
dependencies = [
|
||||
"idna",
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.32.0"
|
||||
@@ -2899,6 +3017,60 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.3.2",
|
||||
"rand 0.9.1",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.12",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
@@ -3087,12 +3259,17 @@ checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"cookie_store",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
@@ -3101,11 +3278,17 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-service",
|
||||
@@ -3114,6 +3297,7 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
@@ -3142,12 +3326,32 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.16",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
@@ -3183,6 +3387,49 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
@@ -3574,6 +3821,12 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.7"
|
||||
@@ -3627,6 +3880,27 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.2"
|
||||
@@ -3647,7 +3921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dispatch",
|
||||
@@ -3868,6 +4142,30 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-http"
|
||||
version = "2.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40dcd6c922a1885e1f0bcebc6768fec6e005bd4b9001c5d90a2f5d4cab297729"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cookie_store",
|
||||
"data-url",
|
||||
"http",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"url",
|
||||
"urlpattern",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.2.6"
|
||||
@@ -4098,6 +4396,21 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.44.2"
|
||||
@@ -4111,10 +4424,32 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.14"
|
||||
@@ -4346,6 +4681,12 @@ version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
@@ -4570,6 +4911,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "2.0.1"
|
||||
@@ -4614,6 +4965,15 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.37.0"
|
||||
@@ -5423,6 +5783,12 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.10.4"
|
||||
|
||||
@@ -24,4 +24,5 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
git2 = "0.18"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-http = "2"
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"dialog:default"
|
||||
"dialog:default",
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [{ "url": "https://api.deepseek.com" }],
|
||||
"deny": [{ "url": "https://private.tauri.app" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -19,7 +19,12 @@ pub fn validate_repo_path(path: &str) -> Result<bool, String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_commits(repo_path: &str, repo_name: &str) -> Result<Vec<CommitInfo>, String> {
|
||||
pub fn get_commits(
|
||||
repo_path: &str,
|
||||
repo_name: &str,
|
||||
author_filter: Option<String>,
|
||||
time_range: Option<(i64, i64)>,
|
||||
) -> Result<Vec<CommitInfo>, String> {
|
||||
let repo = match Repository::open(repo_path) {
|
||||
Ok(repo) => repo,
|
||||
Err(e) => return Err(format!("无法打开仓库: {}", e)),
|
||||
@@ -37,6 +42,20 @@ pub fn get_commits(repo_path: &str, repo_name: &str) -> Result<Vec<CommitInfo>,
|
||||
let commits: Result<Vec<CommitInfo>, _> = revwalk
|
||||
.filter_map(|id| id.ok())
|
||||
.filter_map(|id| repo.find_commit(id).ok())
|
||||
.filter(|commit| {
|
||||
if let Some(author) = &author_filter {
|
||||
if commit.author().email().unwrap_or("") != author {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some((start, end)) = time_range {
|
||||
let commit_time = commit.time().seconds();
|
||||
if commit_time < start || commit_time > end {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
.map(|commit: Commit| {
|
||||
Ok(CommitInfo {
|
||||
id: commit.id().to_string(),
|
||||
|
||||
@@ -7,6 +7,7 @@ use git::{get_commits, validate_repo_path};
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.invoke_handler(tauri::generate_handler![validate_repo_path, get_commits])
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.cs-auto-report.app",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run dev",
|
||||
"beforeDevCommand": "bun run vite:dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"beforeBuildCommand": "bun run build",
|
||||
"frontendDist": "../dist"
|
||||
|
||||
78
src/components/GitRepo/AddRepoModal.tsx
Normal file
78
src/components/GitRepo/AddRepoModal.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { Input, Button, Space, Modal, message } from "antd";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
|
||||
interface AddRepoModalProps {
|
||||
isOpen: boolean;
|
||||
onCancel: () => void;
|
||||
onSubmit: (repoPath: string, name: string) => void;
|
||||
}
|
||||
|
||||
const AddRepoModal: React.FC<AddRepoModalProps> = ({
|
||||
isOpen,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [name, setName] = useState("");
|
||||
const [repoPath, setRepoPath] = useState("");
|
||||
|
||||
const handleSelectDirectory = async () => {
|
||||
try {
|
||||
const selected = await open({
|
||||
directory: true,
|
||||
multiple: false,
|
||||
title: "选择Git仓库目录",
|
||||
});
|
||||
console.log("选择的目录:", selected);
|
||||
|
||||
if (selected) {
|
||||
setRepoPath(selected);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("选择目录失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!repoPath) {
|
||||
message.warning("请选择仓库路径");
|
||||
return;
|
||||
}
|
||||
if (!name) {
|
||||
message.warning("请输入仓库名称");
|
||||
return;
|
||||
}
|
||||
onSubmit(repoPath, name);
|
||||
setName("");
|
||||
setRepoPath("");
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="添加Git仓库"
|
||||
open={isOpen}
|
||||
onCancel={onCancel}
|
||||
footer={
|
||||
<Button type="primary" onClick={handleSubmit}>
|
||||
添加仓库
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="repo-inputs">
|
||||
<Space.Compact style={{ width: "100%", marginBottom: 16 }}>
|
||||
<Input placeholder="选择Git仓库本地路径" value={repoPath} readOnly />
|
||||
<Button onClick={handleSelectDirectory}>选择目录</Button>
|
||||
</Space.Compact>
|
||||
<Input
|
||||
placeholder="请输入仓库名称"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddRepoModal;
|
||||
66
src/components/GitRepo/GenerateReportModal.tsx
Normal file
66
src/components/GitRepo/GenerateReportModal.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Modal, Switch, Space, Select } from "antd";
|
||||
|
||||
import { ReportType } from "../../types/types";
|
||||
|
||||
interface GenerateReportModalProps {
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
onSubmit: (reportType: ReportType, useAI: boolean) => void;
|
||||
}
|
||||
|
||||
const GenerateReportModal: React.FC<GenerateReportModalProps> = ({
|
||||
open,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [reportType, setReportType] = useState<ReportType>(
|
||||
ReportType.WEEKLY_REPORT,
|
||||
);
|
||||
const [useAI, setUseAI] = useState(false);
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSubmit(reportType, useAI);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="生成报告"
|
||||
open={open}
|
||||
onCancel={onCancel}
|
||||
onOk={handleSubmit}
|
||||
okText="生成预览"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Space direction="vertical">
|
||||
<div className="flex gap-x-2 items-center">
|
||||
<div>报告类型</div>
|
||||
<Select
|
||||
value={reportType}
|
||||
style={{ width: 120 }}
|
||||
onChange={(value) => setReportType(value)}
|
||||
options={[
|
||||
{ value: ReportType.WEEKLY_REPORT, label: "本周周报" },
|
||||
{ value: ReportType.LAST_WEEK_REPORT, label: "上周周报" },
|
||||
{ value: ReportType.DAILY_REPORT, label: "本日日报" },
|
||||
{ value: ReportType.YESTERDAY_REPORT, label: "昨日日报" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-x-2">
|
||||
<div style={{ marginBottom: 8 }}>AI自动整理</div>
|
||||
<Switch
|
||||
checked={useAI}
|
||||
onChange={setUseAI}
|
||||
checkedChildren="开启"
|
||||
unCheckedChildren="关闭"
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default GenerateReportModal;
|
||||
@@ -1,17 +1,9 @@
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Input,
|
||||
Button,
|
||||
List,
|
||||
Space,
|
||||
Modal,
|
||||
Checkbox,
|
||||
message,
|
||||
Tooltip,
|
||||
} from "antd";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { Card, Button, List, Checkbox, Tooltip } from "antd";
|
||||
import AddRepoModal from "./AddRepoModal";
|
||||
import GenerateReportModal from "./GenerateReportModal";
|
||||
import type { ReportType } from "../../types/types";
|
||||
|
||||
interface GitRepoFormData {
|
||||
repoPath: string;
|
||||
@@ -22,53 +14,33 @@ interface GitRepoProps {
|
||||
onAddRepo: (repo: GitRepoFormData) => void;
|
||||
repos: GitRepoFormData[];
|
||||
className?: string;
|
||||
onGenerateReport: (reportType: ReportType, useAI: boolean) => void;
|
||||
onReposSelect?: (selectedRepos: string[]) => void;
|
||||
}
|
||||
|
||||
const GitRepo: React.FC<GitRepoProps> = ({ onAddRepo, repos: initialRepos, className }) => {
|
||||
const [name, setName] = useState("");
|
||||
const [repoPath, setRepoPath] = useState("");
|
||||
const GitRepo: React.FC<GitRepoProps> = ({
|
||||
onAddRepo,
|
||||
repos: initialRepos,
|
||||
className,
|
||||
onGenerateReport,
|
||||
onReposSelect,
|
||||
}) => {
|
||||
const [repos, setRepos] = useState<GitRepoFormData[]>(() => {
|
||||
const savedRepos = localStorage.getItem('gitRepos');
|
||||
const savedRepos = localStorage.getItem("gitRepos");
|
||||
return savedRepos ? JSON.parse(savedRepos) : initialRepos;
|
||||
});
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!repoPath) {
|
||||
message.warning("请选择仓库路径");
|
||||
return;
|
||||
}
|
||||
if (!name) {
|
||||
message.warning("请输入仓库名称");
|
||||
return;
|
||||
}
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const handleModalSubmit = (repoPath: string, name: string) => {
|
||||
const newRepo = { name, repoPath };
|
||||
const updatedRepos = [...repos, newRepo];
|
||||
localStorage.setItem('gitRepos', JSON.stringify(updatedRepos));
|
||||
localStorage.setItem("gitRepos", JSON.stringify(updatedRepos));
|
||||
setRepos(updatedRepos);
|
||||
onAddRepo(newRepo);
|
||||
setName("");
|
||||
setRepoPath("");
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
const handleSelectDirectory = async () => {
|
||||
try {
|
||||
const selected = await open({
|
||||
directory: true,
|
||||
multiple: false,
|
||||
title: "选择Git仓库目录",
|
||||
});
|
||||
console.log("选择的目录:", selected);
|
||||
|
||||
if (selected) {
|
||||
setRepoPath(selected);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("选择目录失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const showModal = () => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
@@ -78,41 +50,33 @@ const GitRepo: React.FC<GitRepoProps> = ({ onAddRepo, repos: initialRepos, class
|
||||
};
|
||||
|
||||
const [selectedRepos, setSelectedRepos] = useState<string[]>([]);
|
||||
const [isGenerateModalOpen, setIsGenerateModalOpen] = useState(false);
|
||||
|
||||
const handleCheckboxChange = (checkedValues: string[]) => {
|
||||
setSelectedRepos(checkedValues);
|
||||
onReposSelect?.(checkedValues);
|
||||
console.log("选中的仓库:", checkedValues);
|
||||
};
|
||||
|
||||
const handleGenerateReport = (reportType: ReportType, useAI: boolean) => {
|
||||
console.log("生成报告:", { reportType, useAI, selectedRepos });
|
||||
setIsGenerateModalOpen(false);
|
||||
onGenerateReport(reportType, useAI);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Card title="Git仓库" className="size-full">
|
||||
<Modal
|
||||
title="添加Git仓库"
|
||||
open={isModalOpen}
|
||||
<AddRepoModal
|
||||
isOpen={isModalOpen}
|
||||
onCancel={handleCancel}
|
||||
footer={
|
||||
<Button type="primary" onClick={handleSubmit}>
|
||||
添加仓库
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="repo-inputs">
|
||||
<Space.Compact style={{ width: "100%", marginBottom: 16 }}>
|
||||
<Input
|
||||
placeholder="选择Git仓库本地路径"
|
||||
value={repoPath}
|
||||
readOnly
|
||||
/>
|
||||
<Button onClick={handleSelectDirectory}>选择目录</Button>
|
||||
</Space.Compact>
|
||||
<Input
|
||||
placeholder="请输入仓库名称"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
onSubmit={handleModalSubmit}
|
||||
/>
|
||||
<GenerateReportModal
|
||||
open={isGenerateModalOpen}
|
||||
onCancel={() => setIsGenerateModalOpen(false)}
|
||||
onSubmit={handleGenerateReport}
|
||||
/>
|
||||
|
||||
<Checkbox.Group
|
||||
className="size-full"
|
||||
@@ -141,7 +105,12 @@ const GitRepo: React.FC<GitRepoProps> = ({ onAddRepo, repos: initialRepos, class
|
||||
footer={
|
||||
selectedRepos.length > 0 && (
|
||||
<div className="flex justify-end items-center">
|
||||
<Button type="primary">同步</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => setIsGenerateModalOpen(true)}
|
||||
>
|
||||
生成预览
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Card, Form, Select, Button, Input, message } from 'antd';
|
||||
|
||||
interface CommitRecord {
|
||||
id: string;
|
||||
message: string;
|
||||
author: string;
|
||||
date: string;
|
||||
repository: string;
|
||||
}
|
||||
|
||||
interface ReportGeneratorProps {
|
||||
commits: CommitRecord[];
|
||||
onGenerateReport: (reportData: ReportData) => void;
|
||||
}
|
||||
|
||||
interface ReportData {
|
||||
type: 'daily' | 'weekly';
|
||||
content: string;
|
||||
dateRange: [string, string];
|
||||
}
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
|
||||
const ReportGenerator: React.FC<ReportGeneratorProps> = ({ commits, onGenerateReport }) => {
|
||||
const [form] = Form.useForm();
|
||||
const [reportType, setReportType] = useState<'daily' | 'weekly'>('daily');
|
||||
|
||||
const generateReportContent = (type: 'daily' | 'weekly') => {
|
||||
if (!commits.length) {
|
||||
message.warning('没有可用的提交记录');
|
||||
return '';
|
||||
}
|
||||
|
||||
const groupedCommits = commits.reduce((acc, commit) => {
|
||||
const repo = commit.repository;
|
||||
if (!acc[repo]) {
|
||||
acc[repo] = [];
|
||||
}
|
||||
acc[repo].push(commit);
|
||||
return acc;
|
||||
}, {} as Record<string, CommitRecord[]>);
|
||||
|
||||
let content = `${type === 'daily' ? '日报' : '周报'}\n\n`;
|
||||
|
||||
for (const [repo, repoCommits] of Object.entries(groupedCommits)) {
|
||||
content += `## ${repo}\n`;
|
||||
for (const commit of repoCommits) {
|
||||
content += `- ${commit.message}\n`;
|
||||
};
|
||||
content += '\n';
|
||||
};
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
const content = generateReportContent(reportType);
|
||||
const now = new Date();
|
||||
const reportData: ReportData = {
|
||||
type: reportType,
|
||||
content,
|
||||
dateRange: [now.toISOString(), now.toISOString()]
|
||||
};
|
||||
onGenerateReport(reportData);
|
||||
};
|
||||
|
||||
const handleTypeChange = (value: 'daily' | 'weekly') => {
|
||||
setReportType(value);
|
||||
const content = generateReportContent(value);
|
||||
// @ts-ignore
|
||||
form.setFieldsValue({ content });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title="报告生成器" className="size-full overflow-y-auto">
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleSubmit}
|
||||
>
|
||||
<Form.Item
|
||||
name="type"
|
||||
label="报告类型"
|
||||
initialValue="daily"
|
||||
>
|
||||
<Select onChange={handleTypeChange}>
|
||||
<Option value="daily">日报</Option>
|
||||
<Option value="weekly">周报</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="content"
|
||||
label="报告内容"
|
||||
>
|
||||
<TextArea
|
||||
rows={10}
|
||||
placeholder="报告内容将根据提交记录自动生成"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
生成报告
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportGenerator;
|
||||
45
src/components/ReportPreview/CommitList.tsx
Normal file
45
src/components/ReportPreview/CommitList.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import type React from 'react';
|
||||
import { List } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
interface CommitInfo {
|
||||
id: string;
|
||||
message: string;
|
||||
author: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface CommitListProps {
|
||||
commits: CommitInfo[];
|
||||
}
|
||||
|
||||
const CommitList: React.FC<CommitListProps> = ({ commits }) => {
|
||||
return (
|
||||
<div className="size-full overflow-y-auto">
|
||||
<List
|
||||
dataSource={commits}
|
||||
renderItem={(commit) => (
|
||||
<List.Item>
|
||||
<List.Item.Meta
|
||||
title={commit.message}
|
||||
description={
|
||||
<>
|
||||
<div>提交者: {commit.author}</div>
|
||||
<div>
|
||||
提交时间:{" "}
|
||||
{dayjs(
|
||||
new Date(Number.parseInt(commit.date) * 1000),
|
||||
).format("YYYY-MM-DD HH:mm")}
|
||||
</div>
|
||||
<div>提交ID: {commit.id}</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommitList;
|
||||
88
src/components/ReportPreview/index.tsx
Normal file
88
src/components/ReportPreview/index.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Card, Tabs, Typography, message, Button } from "antd";
|
||||
import { processWithDeepSeek } from '../../utils/deepseekApi';
|
||||
import CommitList from './CommitList';
|
||||
|
||||
interface CommitInfo {
|
||||
id: string;
|
||||
message: string;
|
||||
author: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface ReportPreviewProps {
|
||||
selectedRepos: string[];
|
||||
commits?: CommitInfo[];
|
||||
reportContent?: string;
|
||||
}
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
const { Title, Paragraph } = Typography;
|
||||
|
||||
const ReportPreview: React.FC<ReportPreviewProps> = ({
|
||||
selectedRepos,
|
||||
commits = [],
|
||||
reportContent = '',
|
||||
}) => {
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
|
||||
const handleAICleanup = async () => {
|
||||
if (!reportContent) return;
|
||||
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const apiKey = localStorage.getItem("userKey") || "";
|
||||
const aiResponse = await processWithDeepSeek(reportContent, apiKey);
|
||||
message.success("AI整理完成");
|
||||
return aiResponse;
|
||||
} catch (error) {
|
||||
message.error("AI整理失败");
|
||||
console.error("DeepSeek处理失败:", error);
|
||||
return reportContent;
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (selectedRepos.length === 0) {
|
||||
return (
|
||||
<Card className="size-full flex items-center justify-center">
|
||||
<Typography.Text type="secondary">请选择Git仓库以预览报告内容</Typography.Text>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="size-full">
|
||||
<Tabs defaultActiveKey="commits">
|
||||
<TabPane
|
||||
tab="提交记录"
|
||||
key="commits"
|
||||
className="h-full"
|
||||
>
|
||||
<div className="h-[calc(100vh-100px)] overflow-auto">
|
||||
<CommitList commits={commits} />
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane tab="报告预览" key="preview" className="h-full">
|
||||
<div className="h-[calc(100vh-100px)] overflow-auto">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title level={4}>报告内容</Title>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={isProcessing}
|
||||
onClick={handleAICleanup}
|
||||
>
|
||||
AI整理
|
||||
</Button>
|
||||
</div>
|
||||
<Paragraph>{reportContent || "暂无报告内容"}</Paragraph>
|
||||
</div>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportPreview;
|
||||
@@ -1,14 +1,57 @@
|
||||
import type React from "react";
|
||||
import { Card, Input } from "antd";
|
||||
import { Card, Input, Space } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface SettingProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Setting: React.FC<SettingProps> = ({ className }) => {
|
||||
const [email, setEmail] = useState<string>('');
|
||||
const [key, setKey] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const savedEmail = localStorage.getItem('userEmail');
|
||||
const savedKey = localStorage.getItem('userKey');
|
||||
if (savedEmail) {
|
||||
setEmail(savedEmail);
|
||||
}
|
||||
if (savedKey) {
|
||||
setKey(savedKey);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setEmail(value);
|
||||
localStorage.setItem('userEmail', value);
|
||||
};
|
||||
|
||||
const handleKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setKey(value);
|
||||
localStorage.setItem('userKey', value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title="设置" className={`${className} size-full`}>
|
||||
<Input addonBefore="Key" defaultValue="" />
|
||||
<Card
|
||||
title="设置"
|
||||
className={`${className} size-full flex flex-col gap-y-2`}
|
||||
>
|
||||
<Space direction="vertical">
|
||||
<Input
|
||||
addonBefore="邮箱"
|
||||
value={email}
|
||||
onChange={handleEmailChange}
|
||||
placeholder="请输入您的Git邮箱"
|
||||
/>
|
||||
<Input.Password
|
||||
addonBefore="Key"
|
||||
value={key}
|
||||
onChange={handleKeyChange}
|
||||
placeholder="请输入您的Key"
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { message } from 'antd';
|
||||
import GitRepo from '../../components/GitRepo';
|
||||
import ReportGenerator from '../../components/ReportGenerator';
|
||||
import Setting from '../../components/Setting';
|
||||
import ReportPreview from '../../components/ReportPreview';
|
||||
import type { ReportType } from "../../types/types";
|
||||
import { getTimeRange } from '../../utils/timeUtils';
|
||||
|
||||
interface GitRepoData {
|
||||
repoPath: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface CommitRecord {
|
||||
interface CommitInfo {
|
||||
id: string;
|
||||
message: string;
|
||||
author: string;
|
||||
date: string;
|
||||
repository: string;
|
||||
}
|
||||
|
||||
interface ReportData {
|
||||
type: 'daily' | 'weekly';
|
||||
content: string;
|
||||
dateRange: [string, string];
|
||||
}
|
||||
|
||||
const Home: React.FC = () => {
|
||||
const [repos, setRepos] = useState<GitRepoData[]>([]);
|
||||
const [commits, setCommits] = useState<CommitRecord[]>([]);
|
||||
const [selectedRepos, setSelectedRepos] = useState<string[]>([]);
|
||||
const [commits, setCommits] = useState<CommitInfo[]>([]);
|
||||
const [reportContent, setReportContent] = useState<string>('');
|
||||
|
||||
const handleAddRepo = async (repo: GitRepoData) => {
|
||||
try {
|
||||
@@ -38,27 +36,85 @@ const Home: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateReport = (reportData: ReportData) => {
|
||||
const handleGenerateReport = async (reportType: ReportType, useAI: boolean) => {
|
||||
if (selectedRepos.length === 0) {
|
||||
message.warning('请先选择仓库');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: 调用Tauri后端API保存报告
|
||||
message.success('报告生成成功');
|
||||
const commits = await Promise.all(
|
||||
selectedRepos.map(async (repoPath) => {
|
||||
const repoName = repos.find(r => r.repoPath === repoPath)?.name || '';
|
||||
|
||||
const userEmail = localStorage.getItem('userEmail');
|
||||
const result = await invoke<CommitInfo[]>("get_commits", {
|
||||
repoPath,
|
||||
repoName,
|
||||
authorFilter: userEmail || null,
|
||||
timeRange: reportType? getTimeRange(reportType): null,
|
||||
});
|
||||
return result;
|
||||
})
|
||||
);
|
||||
console.log(commits);
|
||||
|
||||
const flatCommits = commits.flat();
|
||||
setCommits(flatCommits);
|
||||
|
||||
let reportText = `已生成 ${selectedRepos.length} 个仓库的报告内容\n\n` +
|
||||
flatCommits.map(commit =>
|
||||
`- ${commit.message} (${commit.author} 于 ${new Date(parseInt(commit.date) * 1000).toLocaleDateString()})`
|
||||
).join('\n');
|
||||
|
||||
if (useAI) {
|
||||
try {
|
||||
const aiResponse = await invoke<string>("process_with_deepseek", {
|
||||
content: reportText
|
||||
});
|
||||
reportText = aiResponse;
|
||||
} catch (error) {
|
||||
console.error("DeepSeek处理失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setReportContent(reportText);
|
||||
message.success("报告生成成功");
|
||||
} catch (error) {
|
||||
message.error('报告生成失败');
|
||||
message.error("报告生成失败");
|
||||
setCommits([]);
|
||||
setReportContent('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleReposSelect = (selected: string[]) => {
|
||||
setSelectedRepos(selected);
|
||||
if (selected.length === 0) {
|
||||
setCommits([]);
|
||||
setReportContent('');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex size-full overflow-hidden">
|
||||
<div className="h-full w-80 flex flex-col">
|
||||
<div>
|
||||
<Setting />
|
||||
</div>
|
||||
<GitRepo className="flex-1" onAddRepo={handleAddRepo} repos={repos} />
|
||||
</div>
|
||||
<div className="flex-1 h-full overflow-auto">
|
||||
<ReportGenerator
|
||||
commits={commits}
|
||||
<GitRepo
|
||||
className="flex-1"
|
||||
onAddRepo={handleAddRepo}
|
||||
repos={repos}
|
||||
onGenerateReport={handleGenerateReport}
|
||||
onReposSelect={handleReposSelect}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 h-full overflow-hidden">
|
||||
<ReportPreview
|
||||
selectedRepos={selectedRepos}
|
||||
commits={commits}
|
||||
reportContent={reportContent}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
6
src/types/types.ts
Normal file
6
src/types/types.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum ReportType {
|
||||
WEEKLY_REPORT = 'weekly_report',
|
||||
LAST_WEEK_REPORT = 'last_week_report',
|
||||
DAILY_REPORT = 'daily_report',
|
||||
YESTERDAY_REPORT = 'yesterday_report'
|
||||
}
|
||||
47
src/utils/deepseekApi.ts
Normal file
47
src/utils/deepseekApi.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { message } from 'antd';
|
||||
import { fetch } from "@tauri-apps/plugin-http";
|
||||
|
||||
export interface DeepSeekResponse {
|
||||
result: string;
|
||||
}
|
||||
|
||||
export const processWithDeepSeek = async (content: string, apiKey: string): Promise<string> => {
|
||||
console.log(apiKey);
|
||||
try {
|
||||
const response = await fetch<DeepSeekResponse>(
|
||||
"https://api.deepseek.com/v1/chat/completions",
|
||||
{
|
||||
method: "POST",
|
||||
body: {
|
||||
model: "deepseek-chat",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a helpful assistant.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: content,
|
||||
},
|
||||
],
|
||||
temperature: 0.7,
|
||||
max_tokens: 2000,
|
||||
stream: false,
|
||||
},
|
||||
response: "json",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
console.log(response)
|
||||
return response.data.result;
|
||||
} catch (error) {
|
||||
console.error('DeepSeek API请求失败:', error);
|
||||
message.error('DeepSeek API请求失败');
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
59
src/utils/timeUtils.ts
Normal file
59
src/utils/timeUtils.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { ReportType } from "../types/types";
|
||||
|
||||
export const getTimeRange = (reportType: ReportType): [number, number] => {
|
||||
const today = new Date();
|
||||
let startTimestamp: number;
|
||||
let endTimestamp: number;
|
||||
|
||||
switch (reportType) {
|
||||
case ReportType.WEEKLY_REPORT:
|
||||
{
|
||||
// 本周周报
|
||||
const startOfWeek = new Date(
|
||||
today.setDate(
|
||||
today.getDate() - today.getDay() + (today.getDay() === 0 ? -6 : 1),
|
||||
),
|
||||
);
|
||||
startOfWeek.setHours(0, 0, 0, 0);
|
||||
startTimestamp = Math.floor(startOfWeek.getTime() / 1000);
|
||||
endTimestamp = Math.floor(Date.now() / 1000);
|
||||
}
|
||||
break;
|
||||
case ReportType.LAST_WEEK_REPORT: // 上周周报
|
||||
{
|
||||
const lastWeek = new Date(today.setDate(today.getDate() - 7));
|
||||
const startOfLastWeek = new Date(
|
||||
lastWeek.setDate(
|
||||
lastWeek.getDate() -
|
||||
lastWeek.getDay() +
|
||||
(lastWeek.getDay() === 0 ? -6 : 1),
|
||||
),
|
||||
);
|
||||
startOfLastWeek.setHours(0, 0, 0, 0);
|
||||
const endOfLastWeek = new Date(startOfLastWeek);
|
||||
endOfLastWeek.setDate(startOfLastWeek.getDate() + 6);
|
||||
startTimestamp = Math.floor(startOfLastWeek.getTime() / 1000);
|
||||
endTimestamp = Math.floor(endOfLastWeek.getTime() / 1000);
|
||||
}
|
||||
break;
|
||||
case ReportType.DAILY_REPORT: // 日报
|
||||
{
|
||||
const startOfDay = new Date(today.setHours(0, 0, 0, 0));
|
||||
startTimestamp = Math.floor(startOfDay.getTime() / 1000);
|
||||
endTimestamp = Math.floor(Date.now() / 1000);
|
||||
}
|
||||
break;
|
||||
|
||||
case ReportType.YESTERDAY_REPORT: // 昨日日报
|
||||
{
|
||||
const yesterday = new Date(today.setDate(today.getDate() - 1));
|
||||
const startOfYesterday = new Date(yesterday.setHours(0, 0, 0, 0));
|
||||
const endOfYesterday = new Date(yesterday.setHours(23, 59, 59, 999));
|
||||
startTimestamp = Math.floor(startOfYesterday.getTime() / 1000);
|
||||
endTimestamp = Math.floor(endOfYesterday.getTime() / 1000);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return [startTimestamp, endTimestamp];
|
||||
};
|
||||
Reference in New Issue
Block a user