merge upstream main into windows local asr
This commit is contained in:
commit
0bb8d3ebcd
2 files changed
+159
-7
No files matched your search
@@ -0,0 +1,68 @@
|
||||
name: Claude Code
|
||||
|
||||
# 触发策略:
|
||||
# - 默认不主动 review;任何 PR 打开 / 同步都不会自动调用模型,避免烧 OAuth token 额度。
|
||||
# - 评论 / review / issue 中出现 `@claude` 才触发,且发起者必须是仓库 OWNER(appergb)。
|
||||
# - 模型默认 claude-sonnet-4-6(量大、成本低);评论里写 `--opus` 或包含 `claude-opus`
|
||||
# 字样会切到 claude-opus-4-7(用于需要更深推理的任务)。
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
# 双重门禁:(1) 内容含 @claude (2) 触发者是 OWNER。
|
||||
# author_association == 'OWNER' 是 GitHub 内建字段,免一次 API 调用。
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.author_association == 'OWNER') ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.author_association == 'OWNER') ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && github.event.review.author_association == 'OWNER') ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && github.event.issue.author_association == 'OWNER')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
actions: read # Required for Claude to read CI results on PRs
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Pick model (default sonnet, opt-in opus)
|
||||
id: pick_model
|
||||
env:
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
REVIEW_BODY: ${{ github.event.review.body }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
run: |
|
||||
set -eu
|
||||
body="${COMMENT_BODY}${REVIEW_BODY}${ISSUE_BODY}${ISSUE_TITLE}"
|
||||
if [[ "$body" == *"--opus"* ]] || [[ "$body" == *"claude-opus"* ]]; then
|
||||
echo "model=claude-opus-4-7" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "model=claude-sonnet-4-6" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
|
||||
# 让 Claude 能看 CI 结果
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
|
||||
# 模型策略:默认 sonnet,评论里 --opus 才升级到 opus
|
||||
claude_args: '--model ${{ steps.pick_model.outputs.model }}'
|
||||
@@ -38,6 +38,8 @@ use std::sync::Arc;
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::time::Duration;
|
||||
|
||||
const LOG_ROTATE_LIMIT_BYTES: u64 = 10 * 1024 * 1024;
|
||||
|
||||
/// 第一次 show 时把 QA 浮窗摆到屏幕底部居中;之后的 show 不再 reposition,
|
||||
/// 让用户拖动后的位置在 hide → show 之间得以保持。详见 issue #118 v2。
|
||||
static QA_WINDOW_POSITIONED: AtomicBool = AtomicBool::new(false);
|
||||
@@ -47,14 +49,11 @@ use tauri::{AppHandle, Emitter, LogicalPosition, LogicalSize, Manager, RunEvent,
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
init_file_logger();
|
||||
log::info!("=== OpenLess 启动 ===");
|
||||
|
||||
let foundry_local_runtime = Arc::new(asr::local::FoundryLocalRuntime::new());
|
||||
#[cfg(target_os = "windows")]
|
||||
let coordinator = Arc::new(coordinator::Coordinator::new_with_foundry_runtime(Arc::clone(
|
||||
&foundry_local_runtime,
|
||||
)));
|
||||
let coordinator = Arc::new(coordinator::Coordinator::new_with_foundry_runtime(
|
||||
Arc::clone(&foundry_local_runtime),
|
||||
));
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let coordinator = Arc::new(coordinator::Coordinator::new());
|
||||
let local_asr_download_manager = Arc::new(asr::local::DownloadManager::new());
|
||||
@@ -83,6 +82,9 @@ pub fn run() {
|
||||
.manage(local_asr_download_manager.clone())
|
||||
.manage(foundry_local_runtime.clone())
|
||||
.setup(move |app| {
|
||||
init_file_logger();
|
||||
log::info!("=== OpenLess 启动 ===");
|
||||
|
||||
// Capsule 启动时定位到屏幕底部居中并隐藏;coordinator 按需显示。
|
||||
// 与 Swift `CapsuleWindowController.repositionToBottomCenter` 同语义。
|
||||
if let Some(capsule) = app.get_webview_window("capsule") {
|
||||
@@ -444,6 +446,9 @@ fn init_file_logger() {
|
||||
let log_dir = log_dir_path();
|
||||
let _ = std::fs::create_dir_all(&log_dir);
|
||||
let log_file = log_dir.join("openless.log");
|
||||
if let Err(e) = rotate_log_if_too_large(&log_file) {
|
||||
eprintln!("[logger] WARN 日志轮转失败: {e}");
|
||||
}
|
||||
let config = ConfigBuilder::new().set_time_format_rfc3339().build();
|
||||
let mut loggers: Vec<Box<dyn simplelog::SharedLogger>> = vec![TermLogger::new(
|
||||
LevelFilter::Info,
|
||||
@@ -461,6 +466,23 @@ fn init_file_logger() {
|
||||
let _ = CombinedLogger::init(loggers);
|
||||
}
|
||||
|
||||
fn rotate_log_if_too_large(path: &std::path::Path) -> std::io::Result<()> {
|
||||
let Ok(metadata) = std::fs::metadata(path) else {
|
||||
return Ok(());
|
||||
};
|
||||
if metadata.len() <= LOG_ROTATE_LIMIT_BYTES {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let archive = path.with_file_name("openless.log.1");
|
||||
match std::fs::remove_file(&archive) {
|
||||
Ok(()) => {}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
std::fs::rename(path, archive)
|
||||
}
|
||||
|
||||
pub fn log_dir_path() -> std::path::PathBuf {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
@@ -851,7 +873,11 @@ fn capsule_height_for_qa() -> f64 {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{capsule_height_for_qa, capsule_visual_height, capsule_window_bounds};
|
||||
use super::{
|
||||
capsule_height_for_qa, capsule_visual_height, capsule_window_bounds,
|
||||
rotate_log_if_too_large, LOG_ROTATE_LIMIT_BYTES,
|
||||
};
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn capsule_window_bounds_leave_room_for_windows_shadow() {
|
||||
@@ -902,4 +928,62 @@ mod tests {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
assert_eq!(capsule_height_for_qa(), 96.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oversized_log_rotates_to_single_archive() {
|
||||
let dir = std::env::temp_dir().join(format!("openless-log-rotate-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let log = dir.join("openless.log");
|
||||
let archive = dir.join("openless.log.1");
|
||||
|
||||
{
|
||||
let mut file = std::fs::File::create(&log).unwrap();
|
||||
file.set_len(LOG_ROTATE_LIMIT_BYTES + 1).unwrap();
|
||||
file.write_all(b"x").unwrap();
|
||||
}
|
||||
std::fs::write(&archive, b"old").unwrap();
|
||||
|
||||
rotate_log_if_too_large(&log).unwrap();
|
||||
|
||||
assert!(!log.exists());
|
||||
assert!(archive.exists());
|
||||
assert!(std::fs::metadata(&archive).unwrap().len() > LOG_ROTATE_LIMIT_BYTES);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_log_does_not_rotate() {
|
||||
let dir = std::env::temp_dir().join(format!("openless-log-small-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let log = dir.join("openless.log");
|
||||
let archive = dir.join("openless.log.1");
|
||||
std::fs::write(&log, b"small").unwrap();
|
||||
|
||||
rotate_log_if_too_large(&log).unwrap();
|
||||
|
||||
assert!(log.exists());
|
||||
assert!(!archive.exists());
|
||||
assert_eq!(std::fs::read(&log).unwrap(), b"small");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_log_does_not_rotate() {
|
||||
let dir = std::env::temp_dir().join(format!("openless-log-missing-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let log = dir.join("openless.log");
|
||||
let archive = dir.join("openless.log.1");
|
||||
|
||||
rotate_log_if_too_large(&log).unwrap();
|
||||
|
||||
assert!(!log.exists());
|
||||
assert!(!archive.exists());
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user