style: search data display (#380)

* style: search date display

* style: adjust style

* style: search detail display

* docs: update notes

* build: build error
This commit is contained in:
BiggerRain
2025-04-17 17:34:42 +08:00
committed by GitHub
parent 542fd5b233
commit 9f04fb1e0f
17 changed files with 119 additions and 132 deletions

View File

@@ -29,14 +29,13 @@ Information about release notes of Coco Server is provided here.
- fix: active shadow setting #354
- fix: chat history was not show up #377
### Improvements
- refactor: web components #331
- refactor: refactoring login callback, receive access_token from coco-server
- chore: adjust web component styles #362
- style: modify the style #370
- style: search list details display #378
- style: search list details display #378 #380
## 0.3.0 (2025-03-31)

View File

@@ -54,7 +54,7 @@ export function ChatHeader({
reconnect,
setIsLogin,
isChatPage = false,
showChatHistory,
showChatHistory = true,
}: ChatHeaderProps) {
const { t } = useTranslation();
@@ -199,20 +199,20 @@ export function ChatHeader({
data-tauri-drag-region
>
<div className="flex items-center gap-2">
{isTauri && (
{showChatHistory && (
<button
data-sidebar-button
onClick={(e) => {
e.stopPropagation();
setIsSidebarOpen();
}}
className="py-1 px-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
className="p-2 py-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
>
<VisibleKey
shortcut={historicalRecords}
onKeypress={setIsSidebarOpen}
>
<HistoryIcon />
<HistoryIcon className="h-4 w-4" />
</VisibleKey>
</button>
)}
@@ -250,13 +250,13 @@ export function ChatHeader({
) : null}
</Menu>
{showChatHistory && isTauri ? (
{showChatHistory ? (
<button
onClick={onCreateNewChat}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
className="p-2 py-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
>
<VisibleKey shortcut={newSession} onKeypress={onCreateNewChat}>
<MessageSquarePlus className="h-4 w-4" />
<MessageSquarePlus className="h-4 w-4 relative top-0.5" />
</VisibleKey>
</button>
) : null}

View File

@@ -10,7 +10,6 @@ import { useTranslation } from "react-i18next";
import { OpenURLWithBrowser } from "@/utils/index";
import type { IChunkData } from "@/components/Assistant/types";
import RetrieveIcon from "@/icons/Retrieve";
import { useAppStore } from "@/stores/appStore";
interface FetchSourceProps {
Detail?: any;
@@ -41,7 +40,6 @@ export const FetchSource = ({
loading,
}: FetchSourceProps) => {
const { t } = useTranslation();
const isTauri = useAppStore((state) => state.isTauri);
const [isSourceExpanded, setIsSourceExpanded] = useState(false);
@@ -138,9 +136,7 @@ export const FetchSource = ({
</div>
</div>
<div
className={`${
isTauri ? "flex" : "hidden md:flex"
} w-[25%] items-center justify-end gap-2`}
className={`flex mobile:hidden w-[25%] items-center justify-end gap-2`}
>
<span className="text-xs text-[#999999] dark:text-[#999999] truncate">
{item.source?.name}

View File

@@ -100,7 +100,7 @@ export default function Footer({
)}
<div
className={`${isTauri ? "flex" : "hidden md:flex"} items-center gap-3`}
className={`flex mobile:hidden items-center gap-3`}
>
<div className="gap-1 flex items-center text-[#666] dark:text-[#666] text-xs">
<span className="mr-1.5">{t("search.footer.select")}:</span>

View File

@@ -4,12 +4,10 @@ import { useTranslation } from "react-i18next";
import { isMac } from "@/utils/platform";
import { useShortcutsStore } from "@/stores/shortcutsStore";
import noDataImg from "@/assets/coconut-tree.png";
import { useAppStore } from "@/stores/appStore";
export const NoResults = () => {
const { t } = useTranslation();
const isTauri = useAppStore((state) => state.isTauri);
const modeSwitch = useShortcutsStore((state) => state.modeSwitch);
return (
@@ -21,7 +19,7 @@ export const NoResults = () => {
<div className="mt-4 text-sm text-[#999] dark:text-[#666]">
{t("search.main.noResults")}
</div>
<div className={`${isTauri ? 'flex' : 'hidden md:flex'} mt-10 text-sm text-[#333] dark:text-[#D8D8D8]`}>
<div className={`flex mobile:hidden mt-10 text-sm text-[#333] dark:text-[#D8D8D8]`}>
{t("search.main.askCoco")}
{isMac ? (
<span className="ml-3 w-5 h-5 rounded-[6px] border border-[#D8D8D8] flex justify-center items-center">

View File

@@ -1,8 +1,9 @@
import { POPOVER_PANEL_SELECTOR } from "@/constants";
import { useShortcutsStore } from "@/stores/shortcutsStore";
import { FC, ReactNode, useEffect, useRef, useState } from "react";
import { useKeyPress } from "ahooks";
import clsx from "clsx";
import { FC, ReactNode, useEffect, useRef, useState } from "react";
import { POPOVER_PANEL_SELECTOR } from "@/constants";
import { useShortcutsStore } from "@/stores/shortcutsStore";
interface VisibleKeyProps {
shortcut: string;
@@ -64,17 +65,23 @@ const VisibleKey: FC<VisibleKeyProps> = (props) => {
return shortcut;
};
return visibleShortcut ? (
return (
<div
className={clsx(
"size-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]",
className
)}
ref={childrenRef}
className={clsx("relative inline-block")}
>
{renderShortcut()}
{children}
{visibleShortcut ? (
<div
className={clsx(
"size-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2",
className
)}
>
{renderShortcut()}
</div>
) : null}
</div>
) : (
<div ref={childrenRef}>{children}</div>
);
};

View File

@@ -3,6 +3,8 @@ import { useTranslation } from "react-i18next";
import { formatter } from "@/utils/index";
import TypeIcon from "@/components/Common/Icons/TypeIcon";
import defaultThumbnail from "@/assets/coconut-tree.png";
import ItemIcon from "@/components/Common/Icons/ItemIcon";
interface DocumentDetailProps {
document: any;
@@ -16,9 +18,9 @@ interface DetailItemProps {
}
const DetailItem: React.FC<DetailItemProps> = ({ label, value, icon }) => (
<div className="flex justify-between flex-wrap font-normal text-xs mb-2.5">
<div className="text-[#666]">{label}</div>
<div className="text-[#333] dark:text-[#D8D8D8] flex justify-end text-right w-56 break-words">
<div className="flex justify-between flex-wrap font-normal text-xs mb-2.5 border-t border-[rgba(238,240,243,1)] dark:border-[#272626] pt-2.5">
<div className="text-[rgba(153,153,153,1)] dark:text-[#666]">{label}</div>
<div className="text-[rgba(51,51,51,1);] dark:text-[#D8D8D8] flex justify-end text-right w-56 break-words">
{icon}
{value}
</div>
@@ -30,17 +32,52 @@ export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
const { t } = useTranslation();
return (
<div className="p-4">
<div className="font-normal text-xs text-[#666] dark:text-[#999] mb-2">
<div className="p-3">
{/* <div className="font-normal text-xs text-[#666] dark:text-[#999] mb-2">
{t("search.document.details")}
</div> */}
<div className="text-xs font-normal text-[rgba(51,51,51,1)] dark:text-[#D8D8D8]">
{document?.title || "-"}
</div>
<div className="py-4 mt-4">
{/* Basic Information */}
<DetailItem
label={t("search.document.name")}
value={document?.title || "-"}
/>
<div className="py-4">
{/* Document Thumbnail */}
<div className="mb-4 h-[140px] rounded-lg bg-[rgba(243,244,246,1)] dark:bg-[#202126] flex justify-center items-center">
{document.thumbnail ? (
<img
src={document.thumbnail}
alt="thumbnail"
className="max-w-[200px] max-h-[120px] object-contain"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = defaultThumbnail;
}}
/>
) : (
<ItemIcon item={document} className="w-16 h-16"/>
)}
</div>
{/* Document Summary */}
{document?.summary && (
<div className="mb-4 text-xs text-[rgba(153,153,153,1)] dark:text-[#D8D8D8] whitespace-pre-wrap break-words">
{document.summary}
</div>
)}
{/* Document Tags */}
{document?.tags && document.tags.length > 0 && (
<div className="mb-4 flex flex-wrap gap-1">
{document.tags.map((tag: string, index: number) => (
<span
key={index}
className="px-2 py-0.5 text-xs rounded-full bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-300"
>
{tag}
</span>
))}
</div>
)}
<DetailItem
label={t("search.document.source")}
@@ -66,20 +103,6 @@ export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
/>
)}
{/* Document Thumbnail */}
{document?.thumbnail && (
<DetailItem
label={t("search.document.thumbnail")}
value={
<img
src={document.thumbnail}
alt="thumbnail"
className="max-w-[200px] max-h-[120px] object-contain"
/>
}
/>
)}
{/* Document Identifier */}
{document?.id && (
<DetailItem label={t("search.document.id")} value={document.id} />
@@ -117,30 +140,6 @@ export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
/>
)}
{/* Document Tags */}
{document?.tags && document.tags.length > 0 && (
<DetailItem
label={t("search.document.tags")}
value={
<div className="text-right whitespace-pre-wrap break-words w-full">
{document.tags.join(", ")}
</div>
}
/>
)}
{/* Document Summary */}
{document?.summary && (
<DetailItem
label={t("search.document.summary")}
value={
<div className="text-right whitespace-pre-wrap break-words w-full">
{document.summary}
</div>
}
/>
)}
{/* Last Update Time */}
{document?.updated && (
<DetailItem

View File

@@ -10,6 +10,7 @@ import TypeIcon from "@/components/Common/Icons/TypeIcon";
import SearchListItem from "./SearchListItem";
import { metaOrCtrlKey, isMetaOrCtrlKey } from "@/utils/keyboardUtils";
import { OpenURLWithBrowser } from "@/utils/index";
import VisibleKey from "@/components/Common/VisibleKey";
type ISearchData = Record<string, any[]>;
@@ -190,7 +191,10 @@ function DropdownList({
<ThemedIcon component={ArrowBigRight} className="w-4 h-4" />
</IconWrapper>
{showIndex && sourceName === selectedName ? (
<div className={`bg-[#ccc] dark:bg-[#6B6B6B] `}></div>
<div className="absolute top-0 right-4">
<VisibleKey shortcut="→" />
</div>
) : null}
</div>
) : null}

View File

@@ -18,7 +18,7 @@ import { DataSource } from "@/types/commands";
// import { useConnectStore } from "@/stores/connectStore";
import { useShortcutsStore } from "@/stores/shortcutsStore";
import Copyright from "@/components/Common/Copyright";
import VisibleKey from "../Common/VisibleKey";
import VisibleKey from "@/components/Common/VisibleKey";
interface ChatInputProps {
onSend: (message: string) => void;
@@ -335,15 +335,18 @@ export default function ChatInput({
/>
)}
{showTooltip && !isChatMode && sourceData && (
<VisibleKey shortcut="←" className="absolute left-0" />
<div className="absolute -top-[5px] left-2">
<VisibleKey shortcut="←" />
</div>
)}
{showTooltip && (
<VisibleKey
shortcut={returnToInput}
className={clsx("absolute", {
"left-7": !isChatMode && sourceData,
<div
className={clsx("absolute -top-[5px] left-2", {
"left-8": !isChatMode && sourceData,
})}
/>
>
<VisibleKey shortcut={returnToInput} />
</div>
)}
</div>
@@ -390,7 +393,9 @@ export default function ChatInput({
) : null} */}
{showTooltip && isChatMode && (
<VisibleKey shortcut="↩︎" className="absolute right-3" />
<div className="absolute top-[2px] right-[18px]">
<VisibleKey shortcut="↩︎" />
</div>
)}
{!connected && isChatMode ? (
@@ -441,11 +446,7 @@ export default function ChatInput({
)}
onClick={DeepThinkClick}
>
<VisibleKey
shortcut={deepThinking}
onKeypress={DeepThinkClick}
className="!size-3"
>
<VisibleKey shortcut={deepThinking} onKeypress={DeepThinkClick}>
<Brain
className={`size-3 ${
isDeepThinkActive
@@ -474,7 +475,7 @@ export default function ChatInput({
/>
)}
{!hasFeature.includes("search") && !hasFeature.includes("think") ? (
<div className="px-2">
<div className="px-[9px]">
<Copyright />
</div>
) : null}
@@ -489,10 +490,9 @@ export default function ChatInput({
{isChatPage || hasModules?.length !== 2 ? null : (
<div className="relative w-16 flex justify-end items-center">
{showTooltip && (
<VisibleKey
shortcut={modeSwitch}
className="absolute left-1 z-10"
/>
<div className="absolute right-[52px] -top-2 z-10">
<VisibleKey shortcut={modeSwitch} />
</div>
)}
<ChatSwitch

View File

@@ -1,6 +1,5 @@
import TypeIcon from "@/components/Common/Icons/TypeIcon";
import RichIcon from "@/components/Common/Icons/RichIcon";
import { useAppStore } from "@/stores/appStore";
interface ListRightProps {
item: any;
@@ -17,21 +16,14 @@ export default function ListRight({
currentIndex,
goToTwoPage,
}: ListRightProps) {
const isTauri = useAppStore((state) => state.isTauri);
return (
<div
className={`flex flex-1 ${
isTauri
? "text-right min-w-[160px] pl-5 justify-end"
: "md:text-right text-left md:min-w-[160px] md:pl-5 md:justify-end justify-start"
} w-full h-full text-[12px] gap-2 items-center relative`}
className={`flex flex-1 text-right min-w-[160px] pl-5 justify-end w-full h-full text-[12px] gap-2 items-center relative`}
>
{item?.rich_categories ? null : (
<div
className={`${
isTauri ? "inline-block" : "hidden md:inline-block"
} w-4 h-4 cursor-pointer`}
className={`w-4 h-4 cursor-pointer`}
onClick={(e) => {
e.stopPropagation();
goToTwoPage && goToTwoPage(item);
@@ -52,9 +44,7 @@ export default function ListRight({
<div className="flex items-center justify-end max-w-[calc(100%-20px)] whitespace-nowrap">
<RichIcon
item={item}
className={`${
isTauri ? "inline-block" : "hidden md:inline-block"
} w-4 h-4 mr-2 cursor-pointer`}
className={`w-4 h-4 mr-2 cursor-pointer`}
onClick={(e) => {
e.stopPropagation();
goToTwoPage && goToTwoPage(item);

View File

@@ -2,8 +2,6 @@ import React from "react";
import { AlignLeft, Columns2 } from "lucide-react";
import { useTranslation } from "react-i18next";
import { useAppStore } from "@/stores/appStore";
interface SearchHeaderProps {
total: number;
viewMode: "detail" | "list";
@@ -17,8 +15,6 @@ export const SearchHeader: React.FC<SearchHeaderProps> = ({
}) => {
const { t } = useTranslation();
const isTauri = useAppStore((state) => state.isTauri);
return (
<div className="flex items-center justify-between py-1">
<div className="text-xs text-gray-600 dark:text-gray-400">
@@ -28,7 +24,7 @@ export const SearchHeader: React.FC<SearchHeaderProps> = ({
</span>
{t('search.header.results')}
</div>
<div className={`${isTauri ? 'flex' : 'hidden md:flex'} gap-2`}>
<div className={`flex mobile:hidden gap-2`}>
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5">
<button
onClick={() => setViewMode("list")}

View File

@@ -49,22 +49,18 @@ const SearchListItem: React.FC<SearchListItemProps> = React.memo(
ref={itemRef}
onMouseEnter={onMouseEnter}
onClick={onItemClick}
className={`w-full px-2 py-2.5 text-sm flex ${
isTauri
? "mb-0 flex-row items-center"
: "md:mb-0 mb-2 md:flex-row flex-col md:items-center items-start"
} justify-between rounded-lg transition-colors cursor-pointer ${
className={`w-full px-2 py-2.5 text-sm flex mb-0 flex-row items-center mobile:mb-2 mobile:flex-col mobile:items-start justify-between rounded-lg transition-colors cursor-pointer ${
isSelected
? "text-white bg-[var(--coco-primary-color)] hover:bg-[var(--coco-primary-color)]"
: isTauri
? "text-[#333] dark:text-[#d8d8d8]"
: "text-[#333] dark:text-[#d8d8d8] md:bg-transparent md:dark:bg-transparent bg-gray-200/80 dark:bg-gray-700/50"
} ${showListRight ? (isTauri ? "gap-7" : "md:gap-7 gap-1") : ""}`}
: "text-[#333] dark:text-[#d8d8d8] mobile:bg-gray-200/80 mobile:dark:bg-gray-700/50"
} ${showListRight ? "gap-7 mobile:gap-1" : ""}`}
onContextMenu={onContextMenu}
>
<div
className={`${
showListRight ? "md:max-w-[450px] w-full" : "flex-1"
showListRight
? "max-w-[450px] mobile:w-full"
: "flex-1"
} min-w-0 flex gap-2 items-center justify-start `}
>
<ItemIcon item={item} />

View File

@@ -195,7 +195,7 @@ export default function SearchPopover({
)}
onClick={setIsSearchActive}
>
<VisibleKey shortcut={internetSearch} onKeypress={setIsSearchActive} className="!size-3">
<VisibleKey shortcut={internetSearch} onKeypress={setIsSearchActive}>
<Globe
className={`size-3 ${
isSearchActive
@@ -229,7 +229,6 @@ export default function SearchPopover({
onKeypress={() => {
buttonRef.current?.click();
}}
className="!size-3"
>
<ChevronDownIcon
className={clsx("size-3", [

View File

@@ -291,8 +291,8 @@ function SearchChat({
data-tauri-drag-region={isTauri}
className={`p-2 absolute w-full flex justify-center transition-all duration-500 ${
isTransitioned
? "top-[calc(100%-84px)] h-[84px] border-t"
: "top-0 h-[84px] border-b"
? "top-[calc(100%-82px)] h-[82px] border-t"
: "top-0 h-[82px] border-b"
} border-[#E6E6E6] dark:border-[#272626]`}
>
<InputBox
@@ -331,7 +331,7 @@ function SearchChat({
data-tauri-drag-region={isTauri}
className={`absolute w-full transition-opacity duration-500 ${
isTransitioned ? "opacity-0 pointer-events-none" : "opacity-100"
} bottom-0 h-[calc(100%-84px)] `}
} bottom-0 h-[calc(100%-82px)] `}
>
<Suspense fallback={<LoadingFallback />}>
<Search

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
export const useIsMobile = (breakpoint: number = 768) => {
export const useIsMobile = (breakpoint: number = 679) => {
const [isMobile, setIsMobile] = useState(window.innerWidth <= breakpoint);
useEffect(() => {

View File

@@ -42,7 +42,7 @@ function WebApp({
hideCoco = () => {},
hasModules = ["search", "chat"],
defaultModule = "search",
hasFeature = ["search", "think", "think_active", "search_active"],
hasFeature = ["think_active", "search_active"],
theme = "dark",
searchPlaceholder = "",
chatPlaceholder = "",

View File

@@ -55,6 +55,9 @@ export default {
1000: "1000",
2000: "2000",
},
screens: {
'mobile': {'max': '679px'},
},
},
},
plugins: [],