Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
2 changes: 2 additions & 0 deletions docs/content.en/docs/release-notes/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Information about release notes of Coco App is provided here.

- feat: support app search even if Spotlight is disabled #1028
- feat: read localized names from root InfoPlist.strings #1029
- feat: view extension supports detachable #1042

### 🐛 Bug fix

Expand All @@ -23,6 +24,7 @@ Information about release notes of Coco App is provided here.
- fix: fix arrow keys not working after closing the context menu #1035
- fix: fix some filter fields not working #1037
- fix: apply local results weight to scores generated by rerank() #1036
- fix: prevent window collapse when chat history is open #1039

### ✈️ Improvements

Expand Down
2 changes: 1 addition & 1 deletion src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main", "chat", "settings", "check", "selection"],
"windows": ["main", "chat", "settings", "check", "selection", "view_extension", "view_extension_*"],
"permissions": [
"core:default",
"core:event:allow-emit",
Expand Down
11 changes: 8 additions & 3 deletions src-tauri/src/common/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,14 @@ pub(crate) async fn open(
to_value(permission).unwrap(),
to_value(ui).unwrap(),
];
tauri_app_handle
.emit("open_view_extension", view_extension_opened)
.unwrap();
use crate::common::MAIN_WINDOW_LABEL;
if let Err(e) = tauri_app_handle.emit_to(
MAIN_WINDOW_LABEL,
"open_view_extension",
view_extension_opened,
) {
log::error!("Failed to emit open_view_extension event: {}", e);
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ pub mod traits;
pub static MAIN_WINDOW_LABEL: &str = "main";
pub static SETTINGS_WINDOW_LABEL: &str = "settings";
pub static CHECK_WINDOW_LABEL: &str = "check";
pub static VIEW_EXTENSION_WINDOW_LABEL: &str = "view_extension";
60 changes: 58 additions & 2 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ mod shortcut;
pub mod util;

use crate::common::register::SearchSourceRegistry;
use crate::common::{CHECK_WINDOW_LABEL, MAIN_WINDOW_LABEL, SETTINGS_WINDOW_LABEL};
use crate::common::{
CHECK_WINDOW_LABEL, MAIN_WINDOW_LABEL, SETTINGS_WINDOW_LABEL, VIEW_EXTENSION_WINDOW_LABEL,
};
use crate::server::servers::{
load_or_insert_default_server, load_servers_token, start_bg_heartbeat_worker,
};
Expand All @@ -23,7 +25,8 @@ use lazy_static::lazy_static;
use std::sync::Mutex;
use std::sync::OnceLock;
use tauri::{
AppHandle, Emitter, LogicalPosition, Manager, PhysicalPosition, WebviewWindow, WindowEvent,
AppHandle, Emitter, LogicalPosition, Manager, PhysicalPosition, WebviewUrl, WebviewWindow,
WebviewWindowBuilder, WindowEvent,
};
use tauri_plugin_autostart::MacosLauncher;

Expand Down Expand Up @@ -98,6 +101,7 @@ pub fn run() {
show_coco,
hide_coco,
show_settings,
show_view_extension,
show_check,
hide_check,
server::servers::add_coco_server,
Expand Down Expand Up @@ -394,6 +398,58 @@ async fn show_settings(app_handle: AppHandle) {
window.set_focus().unwrap();
}

#[tauri::command]
async fn show_view_extension(
app_handle: AppHandle,
label: Option<String>,
query: Option<String>,
width: Option<f64>,
height: Option<f64>,
title: Option<String>,
) -> Result<(), String> {
log::debug!("view extension menu item was clicked");
if query
.as_ref()
.map_or(true, |q| !(q.contains("manual=1") && q.contains("ext=")))
{
return Ok(());
}
let window_label = label.unwrap_or_else(|| VIEW_EXTENSION_WINDOW_LABEL.to_string());

if let Some(window) = app_handle.get_webview_window(&window_label) {
window.show().unwrap();
window.unminimize().unwrap();
window.set_focus().unwrap();
return Ok(());
}

// If window doesn't exist (e.g. was closed), create it
let url_suffix = query.unwrap_or_else(|| "".to_string());
let url = WebviewUrl::App(format!("/ui/view-extension{}", url_suffix).into());
let w = width.unwrap_or(1000.0);
let h = height.unwrap_or(800.0);

let build_result = WebviewWindowBuilder::new(&app_handle, &window_label, url)
.title(title.unwrap_or_else(|| "View Extension".to_string()))
.inner_size(w, h)
.min_inner_size(800.0, 600.0)
.resizable(true)
.center()
.visible(false)
.build();

match build_result {
Ok(win) => {
let _ = win.set_focus();
Ok(())
}
Err(e) => {
log::error!("Failed to create view extension window: {}", e);
Err(e.to_string())
}
}
}

#[tauri::command]
async fn show_check(app_handle: AppHandle) {
log::debug!("check menu item was clicked");
Expand Down
18 changes: 18 additions & 0 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@
"radius": 7
}
},
{
"label": "view_extension",
"title": "View Extension",
"url": "/ui/view-extension",
"width": 1000,
"minWidth": 800,
"height": 800,
"minHeight": 600,
"center": true,
"decorations": true,
"transparent": false,
"maximizable": true,
"skipTaskbar": false,
"dragDropEnabled": false,
"hiddenTitle": true,
"visible": false,
"resizable": true
},
{
"label": "selection",
"title": "Selection",
Expand Down
9 changes: 5 additions & 4 deletions src/components/Search/InputBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ import {
canNavigateBack,
getUploadedAttachmentsId,
isDefaultServer,
visibleFilterBar,
visibleSearchBar,
} from "@/utils";
import { useVisibleSearchBar, useVisibleFilterBar } from "@/hooks/useViewExtensionUI";
import { useTauriFocus } from "@/hooks/useTauriFocus";
import { SendMessageParams } from "../Assistant/Chat";
import { isEmpty } from "lodash-es";
Expand Down Expand Up @@ -92,6 +91,8 @@ export default function ChatInput({
getFileIcon,
}: ChatInputProps) {
const { t } = useTranslation();
const isVisibleSearchBar = useVisibleSearchBar();
const isVisibleFilterBar = useVisibleFilterBar();

const { currentAssistant } = useConnectStore();

Expand Down Expand Up @@ -311,7 +312,7 @@ export default function ChatInput({
className={`flex items-center dark:text-[#D8D8D8] rounded-md transition-all relative overflow-hidden`}
>
{lineCount === 1 && renderSearchIcon()}
{visibleSearchBar() && (
{isVisibleSearchBar && (
<div
className={clsx(
"min-h-10 w-full p-[7px] bg-[#ededed] dark:bg-[#202126]",
Expand All @@ -335,7 +336,7 @@ export default function ChatInput({
)}
</div>

{visibleFilterBar() && (
{isVisibleFilterBar && (
<InputControls
isChatMode={isChatMode}
isChatPage={isChatPage}
Expand Down
14 changes: 9 additions & 5 deletions src/components/Search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import clsx from "clsx";
import DropdownList from "./DropdownList";
import { SearchResults } from "@/components/Search/SearchResults";
import { useSearchStore } from "@/stores/searchStore";
import { useExtensionStore } from "@/stores/extensionStore";
import ContextMenu from "./ContextMenu";
import { NoResults } from "@/components/Common/UI/NoResults";
import Footer from "@/components/Common/UI/Footer";
Expand All @@ -12,7 +13,7 @@ import { useSearch } from "@/hooks/useSearch";
import ExtensionStore from "./ExtensionStore";
import platformAdapter from "@/utils/platformAdapter";
import ViewExtension from "./ViewExtension";
import { visibleFooterBar } from "@/utils";
import { useVisibleFooterBar } from "@/hooks/useViewExtensionUI";

const SearchResultsPanel = memo<{
input: string;
Expand Down Expand Up @@ -52,9 +53,11 @@ const SearchResultsPanel = memo<{
const {
setSelectedAssistant,
selectedSearchContent,
visibleExtensionStore,
viewExtensionOpened,
visibleExtensionStore
} = useSearchStore();
const viewExtensionOpened = useExtensionStore((state) =>
state.viewExtensions.length > 0 ? state.viewExtensions[state.viewExtensions.length - 1] : undefined
);

useEffect(() => {
if (selectedSearchContent?.type === "AI Assistant") {
Expand Down Expand Up @@ -173,12 +176,13 @@ function Search({
formatUrl,
}: SearchProps) {
const mainWindowRef = useRef<HTMLDivElement>(null);
const isVisibleFooterBar = useVisibleFooterBar();

return (
<div
ref={mainWindowRef}
className={clsx("h-full w-full relative", {
"pb-8": visibleFooterBar(),
"pb-8": isVisibleFooterBar,
})}
>
<SearchResultsPanel
Expand All @@ -189,7 +193,7 @@ function Search({
formatUrl={formatUrl}
/>

{visibleFooterBar() && <Footer setIsPinnedWeb={setIsPinned} />}
{isVisibleFooterBar && <Footer setIsPinnedWeb={setIsPinned} />}

<ContextMenu formatUrl={formatUrl} />
</div>
Expand Down
18 changes: 12 additions & 6 deletions src/components/Search/SearchIcons.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useSearchStore } from "@/stores/searchStore";
import { ChevronLeft, Search } from "lucide-react";
import { FC } from "react";
import clsx from "clsx";
Expand All @@ -7,8 +6,11 @@ import FontIcon from "@/components/Common/Icons/FontIcon";
import lightDefaultIcon from "@/assets/images/source_default.png";
import darkDefaultIcon from "@/assets/images/source_default_dark.png";
import { useThemeStore } from "@/stores/themeStore";
import { useSearchStore } from "@/stores/searchStore";
import { useExtensionStore } from "@/stores/extensionStore";
import platformAdapter from "@/utils/platformAdapter";
import { navigateBack, visibleSearchBar } from "@/utils";
import { navigateBack } from "@/utils";
import { useVisibleSearchBar } from "@/hooks/useViewExtensionUI";
import VisibleKey from "../Common/VisibleKey";
import { cn } from "@/lib/utils";

Expand All @@ -20,6 +22,7 @@ interface MultilevelWrapperProps {
const MultilevelWrapper: FC<MultilevelWrapperProps> = (props) => {
const { icon, title = "" } = props;
const { isDark } = useThemeStore();
const isVisibleSearchBar = useVisibleSearchBar();

const renderIcon = () => {
if (!icon) {
Expand All @@ -39,8 +42,8 @@ const MultilevelWrapper: FC<MultilevelWrapperProps> = (props) => {
className={clsx(
"flex items-center h-10 gap-1 px-2 border border-(--border) rounded-l-lg",
{
"justify-center": visibleSearchBar(),
"w-[calc(100vw-16px)] rounded-r-lg": !visibleSearchBar(),
"justify-center": isVisibleSearchBar,
"w-[calc(100vw-16px)] rounded-r-lg": !isVisibleSearchBar,
}
)}
>
Expand Down Expand Up @@ -74,9 +77,12 @@ export default function SearchIcons({
goAskAi,
visibleExtensionStore,
visibleExtensionDetail,
selectedExtension,
viewExtensionOpened,
selectedExtension
} = useSearchStore();

const viewExtensionOpened = useExtensionStore((state) =>
state.viewExtensions.length > 0 ? state.viewExtensions[state.viewExtensions.length - 1] : undefined
);

if (isChatMode) {
return null;
Expand Down
Loading