Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion .github/workflows/release-tauri.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ jobs:
libxdo-dev \
patchelf \
rpm \
squashfs-tools \
wget

- name: Install npm deps
Expand Down Expand Up @@ -439,6 +440,8 @@ jobs:
mkdir -p "$GITHUB_WORKSPACE/openless-all/app/src-tauri/linux-fcitx5-plugin"
cp libopenless.so "$GITHUB_WORKSPACE/openless-all/app/src-tauri/linux-fcitx5-plugin/"
cp openless.conf "$GITHUB_WORKSPACE/openless-all/app/src-tauri/linux-fcitx5-plugin/"
test -s "$GITHUB_WORKSPACE/openless-all/app/src-tauri/linux-fcitx5-plugin/libopenless.so"
test -s "$GITHUB_WORKSPACE/openless-all/app/src-tauri/linux-fcitx5-plugin/openless.conf"

- name: Build (Linux)
if: matrix.platform == 'ubuntu-22.04'
Expand All @@ -453,10 +456,15 @@ jobs:
# ensure_plugin_installed() 自动安装到 ~/.local/ 下。
# 插件 .so + .conf 由上一步 Build fcitx5 plugin 生成并复制到
# src-tauri/linux-fcitx5-plugin/ 下。
test -s src-tauri/linux-fcitx5-plugin/libopenless.so
test -s src-tauri/linux-fcitx5-plugin/openless.conf
cat > /tmp/tauri-linux-config.json << CONFIG_EOF
{
"bundle": {
"resources": ["linux-fcitx5-plugin/libopenless.so"],
"resources": [
"linux-fcitx5-plugin/libopenless.so",
"linux-fcitx5-plugin/openless.conf"
],
"linux": {
"deb": {
"depends": ["fcitx5", "fcitx5-module-dbus", "libdbus-1-3"],
Expand Down Expand Up @@ -484,6 +492,21 @@ jobs:
fi
npm run tauri -- build --bundles deb,rpm,appimage --config "$CONFIG_FILE"

APPIMAGE_PATH=$(find src-tauri/target/release/bundle/appimage -maxdepth 1 -name '*.AppImage' -print -quit)
if [ -z "$APPIMAGE_PATH" ]; then
echo "::error::AppImage bundle was not produced"
exit 1
fi
APPIMAGE_CONTENTS=$(unsquashfs -l "$APPIMAGE_PATH")
if ! grep -Eq '(^|/)usr/lib/OpenLess/linux-fcitx5-plugin/libopenless\.so$' <<< "$APPIMAGE_CONTENTS"; then
echo "::error::AppImage is missing usr/lib/OpenLess/linux-fcitx5-plugin/libopenless.so"
exit 1
fi
if ! grep -Eq '(^|/)usr/lib/OpenLess/linux-fcitx5-plugin/openless\.conf$' <<< "$APPIMAGE_CONTENTS"; then
echo "::error::AppImage is missing usr/lib/OpenLess/linux-fcitx5-plugin/openless.conf"
exit 1
fi

- name: Disambiguate macOS updater bundle filename
if: startsWith(matrix.platform, 'macos') && env.TAURI_SIGNING_PRIVATE_KEY != ''
shell: bash
Expand Down
20 changes: 19 additions & 1 deletion openless-all/app/src-tauri/src/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ enum CapsuleShowStrategy {
FallbackShow,
}

/// 是否在回答期间显示「处理中 / 润色中」胶囊反馈。
///
/// 语音 / 听写路径显示(用户熟悉的小录音条状态机;Linux 下映射到 fcitx5
/// auxDown,显示在候选词栏下方);打字提问路径不显示(回答在 QA 面板内
/// 流式可见,不应在输入法候选栏闪「✨ 润色中...」)。
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum CapsuleFeedback {
Show,
Hide,
}

fn capsule_show_strategy_for_platform() -> CapsuleShowStrategy {
// ⚠️ 如果改下面的 cfg 列表,**必须**同步更新单元测试
// `capsule_show_strategy_matches_platform_activation_contract` 的两组 cfg —
Expand Down Expand Up @@ -2174,7 +2185,14 @@ impl Coordinator {
// callback (SIGABRT). Tauri's runtime handle is safe from either thread.
tauri::async_runtime::spawn(async move {
let session_id = crate::coordinator_state::new_session_id();
if let Err(e) = dictation::run_voice_agent_transcript(&inner, session_id, text, 0).await
if let Err(e) = dictation::run_voice_agent_transcript(
&inner,
session_id,
text,
0,
CapsuleFeedback::Hide,
)
.await
{
log::warn!("[less-computer] text submit run failed: {e}");
}
Expand Down
42 changes: 31 additions & 11 deletions openless-all/app/src-tauri/src/coordinator/dictation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1333,20 +1333,27 @@ pub(super) async fn run_voice_agent_transcript(
_session_id: SessionId,
transcript: String,
elapsed: u64,
// 语音路径 Show:显示胶囊「处理中」反馈(既有行为);打字路径
// (less_computer_submit_text)Hide —— 对话在浮窗里已可见,不应在输入法
// auxDown 闪「润色中」,用户已确认。
capsule_feedback: super::CapsuleFeedback,
) -> Result<(), String> {
log::info!(
"[coord] Cloud Agent 语音:指令 {} 字",
transcript.chars().count()
);
// 胶囊保留「处理中」反馈(用户熟悉的小录音条状态机);聊天浮窗承载完整对话。
emit_capsule(
inner,
CapsuleState::Polishing,
0.0,
elapsed,
Some("Agent 处理中…".to_string()),
None,
);
// Linux 下会映射到 fcitx5 auxDown("✨ 润色中...")显示在候选词栏下方。
if capsule_feedback == super::CapsuleFeedback::Show {
emit_capsule(
inner,
CapsuleState::Polishing,
0.0,
elapsed,
Some("Agent 处理中…".to_string()),
None,
);
}

// 聊天浮窗:显示窗口 + 落用户气泡(语音指令转写)。macOS only(helper 内部 gating)。
if let Some(app) = inner.app.lock().clone() {
Expand Down Expand Up @@ -4083,8 +4090,14 @@ pub(super) async fn end_session(inner: &Arc<Inner>) -> Result<(), String> {

// Cloud Agent 语音分流:长按升级的会话不走润色/插入,转写交给 Claude 跑任务、结果弹胶囊。
if inner.state.lock().voice_agent {
return run_voice_agent_transcript(inner, current_session_id, raw.text.clone(), elapsed)
.await;
return run_voice_agent_transcript(
inner,
current_session_id,
raw.text.clone(),
elapsed,
super::CapsuleFeedback::Show,
)
.await;
}

emit_capsule(inner, CapsuleState::Polishing, 0.0, elapsed, None, None);
Expand Down Expand Up @@ -4675,7 +4688,14 @@ async fn finish_dictation_multimodal(

// Less Computer:转写文本交给 CLI agent,不走插入/历史(agent 流程自己收尾)。
if voice_agent {
return run_voice_agent_transcript(inner, current_session_id, output, elapsed).await;
return run_voice_agent_transcript(
inner,
current_session_id,
output,
elapsed,
super::CapsuleFeedback::Show,
)
.await;
}

let correction_rules = match inner.correction_rules.list() {
Expand Down
1 change: 0 additions & 1 deletion openless-all/app/src-tauri/src/coordinator/qa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ pub(super) fn open_qa_panel(inner: &Arc<Inner>) {
serde_json::json!({
"kind": "idle",
"session_id": session_id,
"selection_warning": null,
"messages": Vec::<crate::types::QaChatMessage>::new(),
}),
);
Expand Down
57 changes: 44 additions & 13 deletions openless-all/app/src-tauri/src/coordinator/qa_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ pub(super) async fn finalize_dictation_as_qa_question(inner: &Arc<Inner>) -> Res
log::info!("[coord] QA finalize from overlay: capturing selection before opening panel");
let capture = crate::selection::capture_selection_with_status();
let selection = capture.selection;
let selection_warning = capture.warning_code;
let selection_preview_text = selection.as_ref().map(|s| s.text.clone());

log::info!("[coord] QA finalize from overlay: opening panel and waiting for ASR result");
Expand Down Expand Up @@ -173,7 +172,6 @@ pub(super) async fn finalize_dictation_as_qa_question(inner: &Arc<Inner>) -> Res
"kind": "loading",
"session_id": session_id,
"selection_preview": selection_preview_text,
"selection_warning": selection_warning,
"messages": state.messages.clone(),
}),
);
Expand Down Expand Up @@ -208,6 +206,7 @@ pub(super) async fn finalize_dictation_as_qa_question(inner: &Arc<Inner>) -> Res
raw.duration_ms,
session_id,
None,
super::CapsuleFeedback::Show,
)
.await
}
Expand Down Expand Up @@ -245,7 +244,6 @@ pub(super) async fn submit_qa_text_question(
.selection
.as_ref()
.map(|selection| selection.text.clone());
let selection_warning = capture.warning_code;
{
let mut state = inner.qa_state.lock();
if !qa_turn_can_continue(&state, session_id) {
Expand All @@ -263,14 +261,21 @@ pub(super) async fn submit_qa_text_question(
"kind": "thinking",
"session_id": session_id,
"selection_preview": selection_preview_text,
"selection_warning": selection_warning,
"messages": state.messages.clone(),
}),
);
}
}

answer_qa_question_text(inner, question, 0, session_id, None).await
answer_qa_question_text(
inner,
question,
0,
session_id,
None,
super::CapsuleFeedback::Hide,
)
.await
}

pub(super) async fn take_current_dictation_transcript_for_qa(
Expand Down Expand Up @@ -310,8 +315,15 @@ pub(super) async fn take_current_dictation_transcript_for_qa(
state.phase = SessionPhase::Idle;
state.focus_target = None;
}
answer_qa_question_text(inner, String::new(), duration_ms, qa_session_id, Some(wav))
.await?;
answer_qa_question_text(
inner,
String::new(),
duration_ms,
qa_session_id,
Some(wav),
super::CapsuleFeedback::Show,
)
.await?;
return Ok(None);
}

Expand Down Expand Up @@ -655,6 +667,10 @@ pub(super) async fn answer_qa_question_text(
duration_ms: u64,
session_id: SessionId,
audio_wav: Option<Vec<u8>>,
// QA 面板打字提问传 Hide:回答在面板内流式可见,不应在输入法 auxDown
// 闪「✨ 润色中...」(Linux 下 Polishing 会映射到候选词栏)。
// 语音/听写路径保持 Show(用户熟悉的小录音条反馈)。
capsule_feedback: super::CapsuleFeedback,
) -> Result<(), String> {
{
let state = inner.qa_state.lock();
Expand Down Expand Up @@ -706,7 +722,9 @@ pub(super) async fn answer_qa_question_text(
}
}

emit_capsule(inner, CapsuleState::Polishing, 0.0, 0, None, None);
if capsule_feedback == super::CapsuleFeedback::Show {
emit_capsule(inner, CapsuleState::Polishing, 0.0, 0, None, None);
}

let prefs = inner.prefs.get();
let working_languages = prefs.working_languages.clone();
Expand Down Expand Up @@ -880,7 +898,6 @@ pub(super) async fn begin_qa_session(inner: &Arc<Inner>) -> Result<(), String> {
// 每轮按 Option 都重新抓一次:用户多轮提问中可以重新选别处文字。
let capture = capture_qa_turn_selection(inner);
let selection = capture.selection;
let selection_warning = capture.warning_code;
let selection_preview_text = selection.as_ref().map(|s| s.text.clone());
{
let mut state = inner.qa_state.lock();
Expand All @@ -897,7 +914,6 @@ pub(super) async fn begin_qa_session(inner: &Arc<Inner>) -> Result<(), String> {
"kind": "recording",
"session_id": session_id,
"selection_preview": selection_preview_text,
"selection_warning": selection_warning,
"messages": state.messages.clone(),
}),
);
Expand Down Expand Up @@ -1115,8 +1131,15 @@ pub(super) async fn end_qa_session(inner: &Arc<Inner>) -> Result<(), String> {
};
let duration_ms = pcm_consumer.duration_ms();
let wav = pcm_bytes_to_wav(&pcm_consumer.pcm());
return answer_qa_question_text(inner, String::new(), duration_ms, session_id, Some(wav))
.await;
return answer_qa_question_text(
inner,
String::new(),
duration_ms,
session_id,
Some(wav),
super::CapsuleFeedback::Show,
)
.await;
}

let asr = match take_qa_asr_for_session(inner, session_id) {
Expand Down Expand Up @@ -1538,7 +1561,15 @@ pub(super) async fn end_qa_session(inner: &Arc<Inner>) -> Result<(), String> {
return Ok(());
}

answer_qa_question_text(inner, question, raw.duration_ms, session_id, None).await
answer_qa_question_text(
inner,
question,
raw.duration_ms,
session_id,
None,
super::CapsuleFeedback::Show,
)
.await
}

/// 静默收尾:发 idle 事件给前端,phase 复位。**不关浮窗**(v2:浮窗只在用户
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,13 @@ pub(super) async fn run_selection_polish(inner: &Arc<Inner>) -> Result<(), Strin
let insertion_target = crate::selection::capture_selection_insertion_target();
let capture = crate::selection::capture_selection_with_status();
if selection_polish_plan(capture.selection.as_ref()) == SelectionPolishPlan::NoSelection {
let code = capture
.warning_code
.unwrap_or("selectionPolishNoSelection")
.to_string();
let code = "selectionPolishNoSelection";
finish_selection_polish_capsule(
inner,
CapsuleState::Cancelled,
selection_polish_feedback_message(&code),
selection_polish_feedback_message(code),
);
return Err(code);
return Err(code.to_string());
}
let selection = capture.selection.expect("selection plan checked above");
if !crate::selection::selection_insertion_target_is_captured(&insertion_target) {
Expand Down
Loading
Loading