mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
refactor: update UI components for better accessibility and styling consistency
This commit is contained in:
parent
2021f6c4b7
commit
a74ed014cc
11 changed files with 72 additions and 102 deletions
|
|
@ -27,10 +27,9 @@ export function ApiKeyContent() {
|
|||
|
||||
return (
|
||||
<div className="space-y-6 min-w-0 overflow-hidden">
|
||||
<Alert className="border-border/60 bg-muted/30 text-muted-foreground">
|
||||
<Info className="h-4 w-4 text-muted-foreground" />
|
||||
<AlertTitle className="text-muted-foreground">{t("api_key_warning_title")}</AlertTitle>
|
||||
<AlertDescription className="text-muted-foreground/60">
|
||||
<Alert className="bg-muted/50 py-3 md:py-4">
|
||||
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" />
|
||||
<AlertDescription className="text-xs md:text-sm">
|
||||
{t("api_key_warning_description")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
|
|
|||
|
|
@ -113,10 +113,10 @@ export function ProfileContent() {
|
|||
type="submit"
|
||||
variant="outline"
|
||||
disabled={isPending || !hasChanges}
|
||||
className="gap-2 bg-white text-black hover:bg-neutral-100 dark:bg-white dark:text-black dark:hover:bg-neutral-200"
|
||||
className="relative gap-2 bg-white text-black hover:bg-neutral-100 dark:bg-white dark:text-black dark:hover:bg-neutral-200"
|
||||
>
|
||||
{isPending && <Spinner size="sm" className="mr-2" />}
|
||||
{t("profile_save")}
|
||||
<span className={isPending ? "opacity-0" : ""}>{t("profile_save")}</span>
|
||||
{isPending && <Spinner size="sm" className="absolute" />}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -970,7 +970,7 @@ export function ModelSelector({
|
|||
{isAutoMode && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="text-[9px] px-1 py-0 h-3.5 bg-violet-800 text-white dark:bg-violet-800 dark:text-white border-0"
|
||||
className="text-[9px] px-1 py-0 h-3.5 bg-zinc-200 text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300 border-0"
|
||||
>
|
||||
Recommended
|
||||
</Badge>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
"use client";
|
||||
|
||||
import { Check, Copy, Dot, ExternalLink, MessageSquare, Trash2 } from "lucide-react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { Copy, Dot, ExternalLink, MessageSquare, MoreHorizontal, Trash2 } from "lucide-react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import type { PublicChatSnapshotDetail } from "@/contracts/types/chat-threads.types";
|
||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function getInitials(name: string): string {
|
||||
const parts = name.trim().split(/\s+/);
|
||||
|
|
@ -35,15 +43,12 @@ export function PublicChatSnapshotRow({
|
|||
isDeleting = false,
|
||||
memberMap,
|
||||
}: PublicChatSnapshotRowProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const copyTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||
|
||||
const handleCopyClick = useCallback(() => {
|
||||
onCopy(snapshot);
|
||||
setCopied(true);
|
||||
if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current);
|
||||
copyTimeoutRef.current = setTimeout(() => setCopied(false), 2000);
|
||||
toast.success("Link copied to clipboard");
|
||||
}, [onCopy, snapshot]);
|
||||
|
||||
const formattedDate = new Date(snapshot.created_at).toLocaleDateString(undefined, {
|
||||
|
|
@ -58,96 +63,66 @@ export function PublicChatSnapshotRow({
|
|||
<Card className="group relative overflow-hidden transition-all duration-200 border-border/60 hover:shadow-md h-full">
|
||||
<CardContent className="p-4 flex flex-col gap-3 h-full">
|
||||
{/* Header: Title + Actions */}
|
||||
<div className="relative">
|
||||
<div className="min-w-0 pr-16 sm:pr-0 sm:group-hover:pr-16">
|
||||
<h4
|
||||
className="text-sm font-semibold tracking-tight truncate"
|
||||
title={snapshot.thread_title}
|
||||
>
|
||||
{snapshot.thread_title}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5 shrink-0 sm:hidden sm:group-hover:flex absolute right-0 top-0">
|
||||
<TooltipProvider>
|
||||
<Tooltip open={isDesktop ? undefined : false}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
asChild
|
||||
className="h-7 w-7 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<a href={snapshot.public_url} target="_blank" rel="noopener noreferrer">
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Open link</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{canDelete && (
|
||||
<TooltipProvider>
|
||||
<Tooltip open={isDesktop ? undefined : false}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onDelete(snapshot)}
|
||||
disabled={isDeleting}
|
||||
className="h-7 w-7 text-muted-foreground hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Delete</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<div className="relative flex items-center">
|
||||
<h4
|
||||
className={cn(
|
||||
"text-sm font-semibold tracking-tight truncate",
|
||||
dropdownOpen ? "pr-8" : "sm:group-hover:pr-8"
|
||||
)}
|
||||
</div>
|
||||
title={snapshot.thread_title}
|
||||
>
|
||||
{snapshot.thread_title}
|
||||
</h4>
|
||||
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn(
|
||||
"absolute right-0 h-6 w-6 shrink-0 hover:bg-transparent",
|
||||
dropdownOpen
|
||||
? "opacity-100"
|
||||
: "sm:opacity-0 sm:group-hover:opacity-100"
|
||||
)}
|
||||
>
|
||||
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
<DropdownMenuItem onClick={handleCopyClick}>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Copy link
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<a href={snapshot.public_url} target="_blank" rel="noopener noreferrer">
|
||||
<ExternalLink className="mr-2 h-4 w-4" />
|
||||
Open link
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
{canDelete && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => onDelete(snapshot)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
{/* Message count badge */}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-[10px] px-1.5 py-0.5 border-muted-foreground/20 text-muted-foreground"
|
||||
variant="secondary"
|
||||
className="text-[10px] px-1.5 py-0.5 border-0 text-muted-foreground bg-muted"
|
||||
>
|
||||
<MessageSquare className="h-2.5 w-2.5 mr-1" />
|
||||
{snapshot.message_count} messages
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Public URL – selectable fallback for manual copy */}
|
||||
<div className="flex items-center gap-2 rounded-md border border-border/60 bg-muted/30 px-2.5 py-1.5">
|
||||
<div className="min-w-0 flex-1 overflow-x-auto scrollbar-hide">
|
||||
<p
|
||||
className="text-[10px] font-mono text-muted-foreground whitespace-nowrap select-all cursor-text"
|
||||
title={snapshot.public_url}
|
||||
>
|
||||
{snapshot.public_url}
|
||||
</p>
|
||||
</div>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={isDesktop ? undefined : false}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleCopyClick}
|
||||
className="h-6 w-6 shrink-0 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-3 w-3 text-green-500" />
|
||||
) : (
|
||||
<Copy className="h-3 w-3" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{copied ? "Copied!" : "Copy link"}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
|
||||
{/* Footer: Date + Creator */}
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-border/40 mt-auto">
|
||||
<span className="text-[11px] text-muted-foreground/60">{formattedDate}</span>
|
||||
|
|
|
|||
|
|
@ -181,10 +181,10 @@ export function GeneralSettingsManager({ searchSpaceId }: GeneralSettingsManager
|
|||
type="submit"
|
||||
variant="outline"
|
||||
disabled={!hasChanges || saving || !name.trim()}
|
||||
className="gap-2 bg-white text-black hover:bg-neutral-100 dark:bg-white dark:text-black dark:hover:bg-neutral-200"
|
||||
className="relative gap-2 bg-white text-black hover:bg-neutral-100 dark:bg-white dark:text-black dark:hover:bg-neutral-200"
|
||||
>
|
||||
{saving ? <Spinner size="sm" /> : null}
|
||||
{saving ? t("general_saving") : t("general_save")}
|
||||
<span className={saving ? "opacity-0" : ""}>{t("general_save")}</span>
|
||||
{saving && <Spinner size="sm" className="absolute" />}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -395,6 +395,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
key={config.id}
|
||||
value={config.id.toString()}
|
||||
className="text-xs md:text-sm py-1.5 md:py-2"
|
||||
textValue={config.name}
|
||||
>
|
||||
<div className="flex items-center gap-1 md:gap-1.5 flex-wrap min-w-0">
|
||||
<span className="truncate text-xs md:text-sm">
|
||||
|
|
@ -403,7 +404,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
{isAuto && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="text-[8px] md:text-[9px] shrink-0 bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300"
|
||||
className="text-[8px] md:text-[9px] shrink-0 bg-zinc-200 text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300 [[data-slot=select-trigger]_&]:hidden"
|
||||
>
|
||||
Recommended
|
||||
</Badge>
|
||||
|
|
|
|||
|
|
@ -123,7 +123,6 @@
|
|||
"api_key_nav_description": "Manage your API access token",
|
||||
"api_key_title": "API Key",
|
||||
"api_key_description": "Use this key to authenticate API requests",
|
||||
"api_key_warning_title": "Keep it secret",
|
||||
"api_key_warning_description": "Your API key grants full access to your account. Never share it publicly or commit it to version control.",
|
||||
"your_api_key": "Your API Key",
|
||||
"copied": "Copied!",
|
||||
|
|
|
|||
|
|
@ -123,7 +123,6 @@
|
|||
"api_key_nav_description": "Administra tu token de acceso a la API",
|
||||
"api_key_title": "Clave API",
|
||||
"api_key_description": "Usa esta clave para autenticar las solicitudes de la API",
|
||||
"api_key_warning_title": "Mantenla en secreto",
|
||||
"api_key_warning_description": "Tu clave API otorga acceso completo a tu cuenta. Nunca la compartas públicamente ni la incluyas en el control de versiones.",
|
||||
"your_api_key": "Tu clave API",
|
||||
"copied": "¡Copiado!",
|
||||
|
|
|
|||
|
|
@ -123,7 +123,6 @@
|
|||
"api_key_nav_description": "अपना API एक्सेस टोकन प्रबंधित करें",
|
||||
"api_key_title": "API कुंजी",
|
||||
"api_key_description": "API अनुरोधों को प्रमाणित करने के लिए इस कुंजी का उपयोग करें",
|
||||
"api_key_warning_title": "इसे गुप्त रखें",
|
||||
"api_key_warning_description": "आपकी API कुंजी आपके खाते तक पूर्ण पहुंच प्रदान करती है। इसे कभी सार्वजनिक रूप से साझा न करें या संस्करण नियंत्रण में शामिल न करें।",
|
||||
"your_api_key": "आपकी API कुंजी",
|
||||
"copied": "कॉपी किया गया!",
|
||||
|
|
|
|||
|
|
@ -123,7 +123,6 @@
|
|||
"api_key_nav_description": "Gerencie seu token de acesso à API",
|
||||
"api_key_title": "Chave API",
|
||||
"api_key_description": "Use esta chave para autenticar solicitações da API",
|
||||
"api_key_warning_title": "Mantenha em segredo",
|
||||
"api_key_warning_description": "Sua chave API concede acesso total à sua conta. Nunca a compartilhe publicamente nem a inclua no controle de versão.",
|
||||
"your_api_key": "Sua chave API",
|
||||
"copied": "Copiado!",
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@
|
|||
"api_key_nav_description": "管理您的API访问令牌",
|
||||
"api_key_title": "API密钥",
|
||||
"api_key_description": "使用此密钥验证API请求",
|
||||
"api_key_warning_title": "请保密",
|
||||
"api_key_warning_description": "您的API密钥可以完全访问您的账户。请勿公开分享或提交到版本控制。",
|
||||
"your_api_key": "您的API密钥",
|
||||
"copied": "已复制!",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue