mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
feat: advanced settings search debounce & local query source weight (#950)
* wip * wip * wip * feat: add search delay * refactor: update * refactor: update * refactor: update * refactor: update * docs: update changelog --------- Co-authored-by: ayang <473033518@qq.com> Co-authored-by: ayangweb <75017711+ayangweb@users.noreply.github.com>
This commit is contained in:
@@ -22,10 +22,12 @@ feat(View Extension): page field now accepts HTTP(s) links #925
|
||||
feat: return sub-exts when extension type exts themselves are matched #928
|
||||
feat: open quick ai with modifier key + enter #939
|
||||
feat: allow navigate back when cursor is at the beginning #940
|
||||
feat: add compact mode for window #947
|
||||
feat(extension compatibility): minimum_coco_version #946
|
||||
feat: add compact mode for window #947
|
||||
feat: advanced settings search debounce & local query source weight #950
|
||||
feat: add window opacity configuration option #963
|
||||
|
||||
|
||||
### 🐛 Bug fix
|
||||
|
||||
fix: automatic update of service list #913
|
||||
|
||||
@@ -100,7 +100,7 @@ impl SearchQuery {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, Hash, PartialEq, Eq)]
|
||||
pub struct QuerySource {
|
||||
pub r#type: String, //coco-server/local/ etc.
|
||||
pub id: String, //coco server's id
|
||||
|
||||
@@ -193,6 +193,8 @@ pub fn run() {
|
||||
extension::api::fs::read_dir,
|
||||
settings::set_allow_self_signature,
|
||||
settings::get_allow_self_signature,
|
||||
settings::set_local_query_source_weight,
|
||||
settings::get_local_query_source_weight,
|
||||
assistant::ask_ai,
|
||||
crate::common::document::open,
|
||||
extension::built_in::file_search::config::get_file_system_config,
|
||||
|
||||
@@ -4,8 +4,10 @@ use crate::common::search::{
|
||||
FailedRequest, MultiSourceQueryResponse, QueryHits, QuerySource, SearchQuery,
|
||||
};
|
||||
use crate::common::traits::SearchSource;
|
||||
use crate::extension::LOCAL_QUERY_SOURCE_TYPE;
|
||||
use crate::server::servers::logout_coco_server;
|
||||
use crate::server::servers::mark_server_as_offline;
|
||||
use crate::settings::get_local_query_source_weight;
|
||||
use function_name::named;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::FuturesUnordered;
|
||||
@@ -205,7 +207,7 @@ async fn query_coco_fusion_multi_query_sources(
|
||||
|
||||
let mut total_hits = 0;
|
||||
let mut failed_requests = Vec::new();
|
||||
let mut all_hits_grouped_by_source_id: HashMap<String, Vec<QueryHits>> = HashMap::new();
|
||||
let mut all_hits_grouped_by_query_source: HashMap<QuerySource, Vec<QueryHits>> = HashMap::new();
|
||||
|
||||
while let Some((query_source, timeout_result)) = futures.next().await {
|
||||
match timeout_result {
|
||||
@@ -219,7 +221,6 @@ async fn query_coco_fusion_multi_query_sources(
|
||||
Ok(query_result) => match query_result {
|
||||
Ok(response) => {
|
||||
total_hits += response.total_hits;
|
||||
let source_id = response.source.id.clone();
|
||||
|
||||
for (document, score) in response.hits {
|
||||
log::debug!(
|
||||
@@ -236,8 +237,8 @@ async fn query_coco_fusion_multi_query_sources(
|
||||
document,
|
||||
};
|
||||
|
||||
all_hits_grouped_by_source_id
|
||||
.entry(source_id.clone())
|
||||
all_hits_grouped_by_query_source
|
||||
.entry(query_source.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(query_hit);
|
||||
}
|
||||
@@ -255,7 +256,7 @@ async fn query_coco_fusion_multi_query_sources(
|
||||
}
|
||||
}
|
||||
|
||||
let n_sources = all_hits_grouped_by_source_id.len();
|
||||
let n_sources = all_hits_grouped_by_query_source.len();
|
||||
|
||||
if n_sources == 0 {
|
||||
return Ok(MultiSourceQueryResponse {
|
||||
@@ -265,11 +266,25 @@ async fn query_coco_fusion_multi_query_sources(
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply settings: local query source weight
|
||||
*/
|
||||
let local_query_source_weight: f64 = get_local_query_source_weight(tauri_app_handle);
|
||||
// Scores remain unchanged if it is 1.0
|
||||
if local_query_source_weight != 1.0 {
|
||||
for (query_source, hits) in all_hits_grouped_by_query_source.iter_mut() {
|
||||
if query_source.r#type == LOCAL_QUERY_SOURCE_TYPE {
|
||||
hits.iter_mut()
|
||||
.for_each(|hit| hit.score = hit.score * local_query_source_weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sort hits within each source by score (descending) in case data sources
|
||||
* do not sort them
|
||||
*/
|
||||
for hits in all_hits_grouped_by_source_id.values_mut() {
|
||||
for hits in all_hits_grouped_by_query_source.values_mut() {
|
||||
hits.sort_by(|a, b| {
|
||||
b.score
|
||||
.partial_cmp(&a.score)
|
||||
@@ -288,15 +303,15 @@ async fn query_coco_fusion_multi_query_sources(
|
||||
|
||||
// Include at least 2 hits from each query source
|
||||
let max_hits_per_source = (size as usize / n_sources).max(2);
|
||||
for (source_id, hits) in all_hits_grouped_by_source_id.iter() {
|
||||
for (query_source, hits) in all_hits_grouped_by_query_source.iter() {
|
||||
let hits_taken = if hits.len() > max_hits_per_source {
|
||||
pruned.insert(&source_id, &hits[max_hits_per_source..]);
|
||||
pruned.insert(&query_source.id, &hits[max_hits_per_source..]);
|
||||
hits[0..max_hits_per_source].to_vec()
|
||||
} else {
|
||||
hits.clone()
|
||||
};
|
||||
|
||||
final_hits_grouped_by_source_id.insert(source_id.clone(), hits_taken);
|
||||
final_hits_grouped_by_source_id.insert(query_source.id.clone(), hits_taken);
|
||||
}
|
||||
|
||||
let final_hits_len = final_hits_grouped_by_source_id
|
||||
|
||||
@@ -4,6 +4,7 @@ use tauri::AppHandle;
|
||||
use tauri_plugin_store::StoreExt;
|
||||
|
||||
const SETTINGS_ALLOW_SELF_SIGNATURE: &str = "settings_allow_self_signature";
|
||||
const LOCAL_QUERY_SOURCE_WEIGHT: &str = "local_query_source_weight";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_allow_self_signature(tauri_app_handle: AppHandle, value: bool) {
|
||||
@@ -70,3 +71,45 @@ pub fn _get_allow_self_signature(tauri_app_handle: AppHandle) -> bool {
|
||||
pub async fn get_allow_self_signature(tauri_app_handle: AppHandle) -> bool {
|
||||
_get_allow_self_signature(tauri_app_handle)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_local_query_source_weight(tauri_app_handle: AppHandle, value: f64) {
|
||||
let store = tauri_app_handle
|
||||
.store(COCO_TAURI_STORE)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"store [{}] not found/loaded, error [{}]",
|
||||
COCO_TAURI_STORE, e
|
||||
)
|
||||
});
|
||||
|
||||
store.set(LOCAL_QUERY_SOURCE_WEIGHT, value);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_local_query_source_weight(tauri_app_handle: AppHandle) -> f64 {
|
||||
// default to 1.0
|
||||
const DEFAULT: f64 = 1.0;
|
||||
|
||||
let store = tauri_app_handle
|
||||
.store(COCO_TAURI_STORE)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"store [{}] not found/loaded, error [{}]",
|
||||
COCO_TAURI_STORE, e
|
||||
)
|
||||
});
|
||||
if !store.has(LOCAL_QUERY_SOURCE_WEIGHT) {
|
||||
store.set(LOCAL_QUERY_SOURCE_WEIGHT, DEFAULT);
|
||||
}
|
||||
|
||||
match store
|
||||
.get(LOCAL_QUERY_SOURCE_WEIGHT)
|
||||
.expect("should be Some")
|
||||
{
|
||||
Json::Number(n) => n
|
||||
.as_f64()
|
||||
.unwrap_or_else(|| panic!("setting [{}] should be a f64", LOCAL_QUERY_SOURCE_WEIGHT)),
|
||||
_ => unreachable!("{} should be stored as a number", LOCAL_QUERY_SOURCE_WEIGHT),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
AppWindowMac,
|
||||
ArrowUpWideNarrow,
|
||||
MessageSquareMore,
|
||||
Search,
|
||||
ShieldCheck,
|
||||
@@ -18,6 +19,7 @@ import SettingsInput from "@/components//Settings/SettingsInput";
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
import UpdateSettings from "./components/UpdateSettings";
|
||||
import SettingsToggle from "../SettingsToggle";
|
||||
import { isNil } from "lodash-es";
|
||||
|
||||
const Advanced = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -57,6 +59,9 @@ const Advanced = () => {
|
||||
const setAllowSelfSignature = useConnectStore((state) => {
|
||||
return state.setAllowSelfSignature;
|
||||
});
|
||||
const { searchDelay, setSearchDelay } = useConnectStore();
|
||||
|
||||
const [localSearchResultWeight, setLocalSearchResultWeight] = useState(1);
|
||||
|
||||
useMount(async () => {
|
||||
const allowSelfSignature = await platformAdapter.invokeBackend<boolean>(
|
||||
@@ -64,6 +69,12 @@ const Advanced = () => {
|
||||
);
|
||||
|
||||
setAllowSelfSignature(allowSelfSignature);
|
||||
|
||||
const weight = await platformAdapter.invokeBackend<number>(
|
||||
"get_local_query_source_weight"
|
||||
);
|
||||
|
||||
setLocalSearchResultWeight(weight);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -174,16 +185,20 @@ const Advanced = () => {
|
||||
|
||||
<Shortcuts />
|
||||
|
||||
<Appearance />
|
||||
|
||||
<UpdateSettings />
|
||||
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
{t("settings.advanced.connect.title")}
|
||||
{t("settings.advanced.other.title")}
|
||||
</h2>
|
||||
|
||||
<div className="space-y-6">
|
||||
<SettingsItem
|
||||
icon={Unplug}
|
||||
title={t("settings.advanced.connect.connectionTimeout.title")}
|
||||
title={t("settings.advanced.other.connectionTimeout.title")}
|
||||
description={t(
|
||||
"settings.advanced.connect.connectionTimeout.description"
|
||||
"settings.advanced.other.connectionTimeout.description"
|
||||
)}
|
||||
>
|
||||
<SettingsInput
|
||||
@@ -198,8 +213,8 @@ const Advanced = () => {
|
||||
|
||||
<SettingsItem
|
||||
icon={Unplug}
|
||||
title={t("settings.advanced.connect.queryTimeout.title")}
|
||||
description={t("settings.advanced.connect.queryTimeout.description")}
|
||||
title={t("settings.advanced.other.queryTimeout.title")}
|
||||
description={t("settings.advanced.other.queryTimeout.description")}
|
||||
>
|
||||
<SettingsInput
|
||||
type="number"
|
||||
@@ -211,15 +226,30 @@ const Advanced = () => {
|
||||
/>
|
||||
</SettingsItem>
|
||||
|
||||
<SettingsItem
|
||||
icon={Unplug}
|
||||
title={t("settings.advanced.other.searchDelay.title")}
|
||||
description={t("settings.advanced.other.searchDelay.description")}
|
||||
>
|
||||
<SettingsInput
|
||||
type="number"
|
||||
min={0}
|
||||
value={searchDelay}
|
||||
onChange={(value) => {
|
||||
setSearchDelay(isNil(value) ? 0 : Number(value));
|
||||
}}
|
||||
/>
|
||||
</SettingsItem>
|
||||
|
||||
<SettingsItem
|
||||
icon={ShieldCheck}
|
||||
title={t("settings.advanced.connect.allowSelfSignature.title")}
|
||||
title={t("settings.advanced.other.allowSelfSignature.title")}
|
||||
description={t(
|
||||
"settings.advanced.connect.allowSelfSignature.description"
|
||||
"settings.advanced.other.allowSelfSignature.description"
|
||||
)}
|
||||
>
|
||||
<SettingsToggle
|
||||
label={t("settings.advanced.connect.allowSelfSignature.title")}
|
||||
label={t("settings.advanced.other.allowSelfSignature.title")}
|
||||
checked={allowSelfSignature}
|
||||
onChange={(value) => {
|
||||
setAllowSelfSignature(value);
|
||||
@@ -230,11 +260,43 @@ const Advanced = () => {
|
||||
}}
|
||||
/>
|
||||
</SettingsItem>
|
||||
|
||||
<SettingsItem
|
||||
icon={ArrowUpWideNarrow}
|
||||
title={t("settings.advanced.other.localSearchResultWeight.title")}
|
||||
description={t(
|
||||
"settings.advanced.other.localSearchResultWeight.description"
|
||||
)}
|
||||
>
|
||||
<select
|
||||
value={localSearchResultWeight}
|
||||
onChange={(event) => {
|
||||
const weight = Number(event.target.value);
|
||||
|
||||
setLocalSearchResultWeight(weight);
|
||||
|
||||
platformAdapter.invokeBackend("set_local_query_source_weight", {
|
||||
value: weight,
|
||||
});
|
||||
}}
|
||||
className="px-3 py-1.5 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="0.5">
|
||||
{t("settings.advanced.other.localSearchResultWeight.options.low")}
|
||||
</option>
|
||||
<option value="1">
|
||||
{t(
|
||||
"settings.advanced.other.localSearchResultWeight.options.medium"
|
||||
)}
|
||||
</option>
|
||||
<option value="2">
|
||||
{t(
|
||||
"settings.advanced.other.localSearchResultWeight.options.high"
|
||||
)}
|
||||
</option>
|
||||
</select>
|
||||
</SettingsItem>
|
||||
</div>
|
||||
|
||||
<Appearance />
|
||||
|
||||
<UpdateSettings />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ export function useSearch() {
|
||||
return state.aiOverviewMinQuantity;
|
||||
});
|
||||
|
||||
const { querySourceTimeout } = useConnectStore();
|
||||
const { querySourceTimeout, searchDelay } = useConnectStore();
|
||||
|
||||
const [searchState, setSearchState] = useState<SearchState>({
|
||||
isError: [],
|
||||
@@ -159,6 +159,8 @@ export function useSearch() {
|
||||
|
||||
const performSearch = useCallback(
|
||||
async (searchInput: string) => {
|
||||
console.log(123);
|
||||
|
||||
if (!searchInput) {
|
||||
setSearchState((prev) => ({ ...prev, suggests: [] }));
|
||||
return;
|
||||
@@ -219,10 +221,11 @@ export function useSearch() {
|
||||
]
|
||||
);
|
||||
|
||||
const debouncedSearch = useMemo(
|
||||
() => debounce(performSearch, 300),
|
||||
[performSearch]
|
||||
);
|
||||
const debouncedSearch = useMemo(() => {
|
||||
console.log("searchDelay", searchDelay);
|
||||
|
||||
return debounce(performSearch, searchDelay);
|
||||
}, [performSearch, searchDelay]);
|
||||
|
||||
return {
|
||||
...searchState,
|
||||
|
||||
@@ -118,6 +118,7 @@ export const useSyncStore = () => {
|
||||
const setEndpoint = useAppStore((state) => state.setEndpoint);
|
||||
const setLanguage = useAppStore((state) => state.setLanguage);
|
||||
const { setWindowMode } = useAppearanceStore();
|
||||
const { setSearchDelay } = useConnectStore();
|
||||
|
||||
const setServerListSilently = useConnectStore(
|
||||
(state) => state.setServerListSilently
|
||||
@@ -185,14 +186,19 @@ export const useSyncStore = () => {
|
||||
}),
|
||||
|
||||
platformAdapter.listenEvent("change-connect-store", ({ payload }) => {
|
||||
const { connectionTimeout, querySourceTimeout, allowSelfSignature } =
|
||||
payload;
|
||||
const {
|
||||
connectionTimeout,
|
||||
querySourceTimeout,
|
||||
searchDelay,
|
||||
allowSelfSignature,
|
||||
} = payload;
|
||||
if (isNumber(connectionTimeout)) {
|
||||
setConnectionTimeout(connectionTimeout);
|
||||
}
|
||||
if (isNumber(querySourceTimeout)) {
|
||||
setQueryTimeout(querySourceTimeout);
|
||||
}
|
||||
setSearchDelay(searchDelay);
|
||||
setAllowSelfSignature(allowSelfSignature);
|
||||
}),
|
||||
|
||||
|
||||
@@ -164,21 +164,6 @@
|
||||
"description": "Shortcut button to enable AI Overview in chat mode."
|
||||
}
|
||||
},
|
||||
"connect": {
|
||||
"title": "Connection Settings",
|
||||
"connectionTimeout": {
|
||||
"title": "Connection Timeout",
|
||||
"description": "Retries the connection if no response is received within this time. Default: 120s."
|
||||
},
|
||||
"queryTimeout": {
|
||||
"title": "Query Timeout",
|
||||
"description": "Terminates the query if no search results are returned within this time. Default: 500ms."
|
||||
},
|
||||
"allowSelfSignature": {
|
||||
"title": "Allow Self-Signed Certificates",
|
||||
"description": "Allow connections to servers using self-signed certificates. Enable only if you trust the source."
|
||||
}
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Appearance Settings",
|
||||
"normalOpacity": {
|
||||
@@ -196,6 +181,34 @@
|
||||
"title": "Snapshot Updates",
|
||||
"description": "Get early access to new features. May be unstable."
|
||||
}
|
||||
},
|
||||
"other": {
|
||||
"title": "Other Settings",
|
||||
"connectionTimeout": {
|
||||
"title": "Connection Timeout",
|
||||
"description": "Retries the connection if no response is received within this time. Default: 120s."
|
||||
},
|
||||
"queryTimeout": {
|
||||
"title": "Query Timeout",
|
||||
"description": "Terminates the query if no search results are returned within this time. Default: 500ms."
|
||||
},
|
||||
"searchDelay": {
|
||||
"title": "Search Delay",
|
||||
"description": "Delay before search is triggered after user stops typing. Default: 300 ms."
|
||||
},
|
||||
"allowSelfSignature": {
|
||||
"title": "Allow Self-Signed Certificates",
|
||||
"description": "Allow connections to servers using self-signed certificates. Enable only if you trust the source."
|
||||
},
|
||||
"localSearchResultWeight": {
|
||||
"title": "Local Search Result Weight",
|
||||
"description": "Adjusts how local results (files, extensions, commands, etc.) are ranked. Higher values place them closer to the top.",
|
||||
"options": {
|
||||
"high": "High",
|
||||
"medium": "Medium",
|
||||
"low": "Low"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
|
||||
@@ -164,21 +164,6 @@
|
||||
"description": "在搜索模式下启用 AI 总结的快捷按键。"
|
||||
}
|
||||
},
|
||||
"connect": {
|
||||
"title": "连接设置",
|
||||
"connectionTimeout": {
|
||||
"title": "连接超时",
|
||||
"description": "如果在此时间内未收到响应,则重试连接。默认值:120 秒。"
|
||||
},
|
||||
"queryTimeout": {
|
||||
"title": "查询超时",
|
||||
"description": "在此时间内未返回搜索结果,则终止查询。默认值:500 毫秒。"
|
||||
},
|
||||
"allowSelfSignature": {
|
||||
"title": "允许自签名证书",
|
||||
"description": "允许连接使用自签名证书的服务器。仅在信任来源的情况下启用。"
|
||||
}
|
||||
},
|
||||
"appearance": {
|
||||
"title": "外观设置",
|
||||
"normalOpacity": {
|
||||
@@ -196,6 +181,34 @@
|
||||
"title": "快照版更新",
|
||||
"description": "抢先体验新功能,可能不稳定。"
|
||||
}
|
||||
},
|
||||
"other": {
|
||||
"title": "其它设置",
|
||||
"connectionTimeout": {
|
||||
"title": "连接超时",
|
||||
"description": "如果在此时间内未收到响应,则重试连接。默认值:120 秒。"
|
||||
},
|
||||
"queryTimeout": {
|
||||
"title": "查询超时",
|
||||
"description": "在此时间内未返回搜索结果,则终止查询。默认值:500 毫秒。"
|
||||
},
|
||||
"searchDelay": {
|
||||
"title": "搜索延迟",
|
||||
"description": "停止输入后触发搜索的延迟时间。默认值:300 毫秒。 "
|
||||
},
|
||||
"allowSelfSignature": {
|
||||
"title": "允许自签名证书",
|
||||
"description": "允许连接使用自签名证书的服务器。仅在信任来源的情况下启用。"
|
||||
},
|
||||
"localSearchResultWeight": {
|
||||
"title": "本地搜索结果展示权重",
|
||||
"description": "调整本地结果(文件、扩展名、命令等)的排名。值越高,它们越接近顶部。",
|
||||
"options": {
|
||||
"high": "高",
|
||||
"medium": "中",
|
||||
"low": "低"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
|
||||
@@ -38,6 +38,8 @@ export type IConnectStore = {
|
||||
setVisibleStartPage: (visibleStartPage: boolean) => void;
|
||||
allowSelfSignature: boolean;
|
||||
setAllowSelfSignature: (allowSelfSignature: boolean) => void;
|
||||
searchDelay: number;
|
||||
setSearchDelay: (searchDelay: number) => void;
|
||||
};
|
||||
|
||||
export const useConnectStore = create<IConnectStore>()(
|
||||
@@ -143,6 +145,10 @@ export const useConnectStore = create<IConnectStore>()(
|
||||
setAllowSelfSignature: (allowSelfSignature: boolean) => {
|
||||
return set(() => ({ allowSelfSignature }));
|
||||
},
|
||||
searchDelay: 300,
|
||||
setSearchDelay(searchDelay) {
|
||||
return set(() => ({ searchDelay }));
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "connect-store",
|
||||
|
||||
Reference in New Issue
Block a user