mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
chore: ran linting
This commit is contained in:
parent
d072ca60bb
commit
8455451ce1
20 changed files with 256 additions and 200 deletions
|
|
@ -79,7 +79,9 @@ def upgrade() -> None:
|
|||
sa.Column("description", sa.String(500), nullable=True),
|
||||
sa.Column(
|
||||
"provider",
|
||||
PG_ENUM(*VISION_PROVIDER_VALUES, name="visionprovider", create_type=False),
|
||||
PG_ENUM(
|
||||
*VISION_PROVIDER_VALUES, name="visionprovider", create_type=False
|
||||
),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("custom_provider", sa.String(100), nullable=True),
|
||||
|
|
@ -100,9 +102,7 @@ def upgrade() -> None:
|
|||
sa.ForeignKeyConstraint(
|
||||
["search_space_id"], ["searchspaces.id"], ondelete="CASCADE"
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"], ["user.id"], ondelete="CASCADE"
|
||||
),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"),
|
||||
)
|
||||
op.execute(
|
||||
"CREATE INDEX IF NOT EXISTS ix_vision_llm_configs_name "
|
||||
|
|
@ -117,12 +117,19 @@ def upgrade() -> None:
|
|||
existing_columns = [
|
||||
col["name"] for col in sa.inspect(connection).get_columns("searchspaces")
|
||||
]
|
||||
if "vision_llm_id" in existing_columns and "vision_llm_config_id" not in existing_columns:
|
||||
op.alter_column("searchspaces", "vision_llm_id", new_column_name="vision_llm_config_id")
|
||||
if (
|
||||
"vision_llm_id" in existing_columns
|
||||
and "vision_llm_config_id" not in existing_columns
|
||||
):
|
||||
op.alter_column(
|
||||
"searchspaces", "vision_llm_id", new_column_name="vision_llm_config_id"
|
||||
)
|
||||
elif "vision_llm_config_id" not in existing_columns:
|
||||
op.add_column(
|
||||
"searchspaces",
|
||||
sa.Column("vision_llm_config_id", sa.Integer(), nullable=True, server_default="0"),
|
||||
sa.Column(
|
||||
"vision_llm_config_id", sa.Integer(), nullable=True, server_default="0"
|
||||
),
|
||||
)
|
||||
|
||||
# 4. Add vision config permissions to existing system roles
|
||||
|
|
@ -181,7 +188,9 @@ def downgrade() -> None:
|
|||
col["name"] for col in sa.inspect(connection).get_columns("searchspaces")
|
||||
]
|
||||
if "vision_llm_config_id" in existing_columns:
|
||||
op.alter_column("searchspaces", "vision_llm_config_id", new_column_name="vision_llm_id")
|
||||
op.alter_column(
|
||||
"searchspaces", "vision_llm_config_id", new_column_name="vision_llm_id"
|
||||
)
|
||||
|
||||
# Drop table and enum
|
||||
op.execute("DROP INDEX IF EXISTS ix_vision_llm_configs_search_space_id")
|
||||
|
|
|
|||
|
|
@ -452,9 +452,7 @@ async def stream_autocomplete_agent(
|
|||
raw_text = "".join(text_buffer)
|
||||
suggestions = _parse_suggestions(raw_text)
|
||||
|
||||
yield streaming_service.format_data(
|
||||
"suggestions", {"options": suggestions}
|
||||
)
|
||||
yield streaming_service.format_data("suggestions", {"options": suggestions})
|
||||
|
||||
yield streaming_service.format_finish()
|
||||
yield streaming_service.format_done()
|
||||
|
|
|
|||
|
|
@ -1310,9 +1310,7 @@ class VisionLLMConfig(BaseModel, TimestampMixin):
|
|||
search_space_id = Column(
|
||||
Integer, ForeignKey("searchspaces.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
search_space = relationship(
|
||||
"SearchSpace", back_populates="vision_llm_configs"
|
||||
)
|
||||
search_space = relationship("SearchSpace", back_populates="vision_llm_configs")
|
||||
|
||||
user_id = Column(
|
||||
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=False
|
||||
|
|
|
|||
|
|
@ -122,6 +122,4 @@ class EtlPipelineService:
|
|||
|
||||
from app.etl_pipeline.parsers.llamacloud import parse_with_llamacloud
|
||||
|
||||
return await parse_with_llamacloud(
|
||||
request.file_path, request.estimated_pages
|
||||
)
|
||||
return await parse_with_llamacloud(request.file_path, request.estimated_pages)
|
||||
|
|
|
|||
|
|
@ -180,9 +180,7 @@ async def list_vision_llm_configs(
|
|||
) from e
|
||||
|
||||
|
||||
@router.get(
|
||||
"/vision-llm-configs/{config_id}", response_model=VisionLLMConfigRead
|
||||
)
|
||||
@router.get("/vision-llm-configs/{config_id}", response_model=VisionLLMConfigRead)
|
||||
async def get_vision_llm_config(
|
||||
config_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
|
|
@ -214,9 +212,7 @@ async def get_vision_llm_config(
|
|||
) from e
|
||||
|
||||
|
||||
@router.put(
|
||||
"/vision-llm-configs/{config_id}", response_model=VisionLLMConfigRead
|
||||
)
|
||||
@router.put("/vision-llm-configs/{config_id}", response_model=VisionLLMConfigRead)
|
||||
async def update_vision_llm_config(
|
||||
config_id: int,
|
||||
update_data: VisionLLMConfigUpdate,
|
||||
|
|
|
|||
|
|
@ -183,7 +183,8 @@ class LLMPreferencesRead(BaseModel):
|
|||
None, description="ID of the image generation config to use"
|
||||
)
|
||||
vision_llm_config_id: int | None = Field(
|
||||
None, description="ID of the vision LLM config to use for vision/screenshot analysis"
|
||||
None,
|
||||
description="ID of the vision LLM config to use for vision/screenshot analysis",
|
||||
)
|
||||
agent_llm: dict[str, Any] | None = Field(
|
||||
None, description="Full config for agent LLM"
|
||||
|
|
@ -214,5 +215,6 @@ class LLMPreferencesUpdate(BaseModel):
|
|||
None, description="ID of the image generation config to use"
|
||||
)
|
||||
vision_llm_config_id: int | None = Field(
|
||||
None, description="ID of the vision LLM config to use for vision/screenshot analysis"
|
||||
None,
|
||||
description="ID of the vision LLM config to use for vision/screenshot analysis",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -434,9 +434,7 @@ async def get_vision_llm(
|
|||
|
||||
config_id = search_space.vision_llm_config_id
|
||||
if config_id is None:
|
||||
logger.error(
|
||||
f"No vision LLM configured for search space {search_space_id}"
|
||||
)
|
||||
logger.error(f"No vision LLM configured for search space {search_space_id}")
|
||||
return None
|
||||
|
||||
if is_vision_auto_mode(config_id):
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ import httpx
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
OPENROUTER_API_URL = "https://openrouter.ai/api/v1/models"
|
||||
FALLBACK_FILE = Path(__file__).parent.parent / "config" / "vision_model_list_fallback.json"
|
||||
FALLBACK_FILE = (
|
||||
Path(__file__).parent.parent / "config" / "vision_model_list_fallback.json"
|
||||
)
|
||||
CACHE_TTL_SECONDS = 86400 # 24 hours
|
||||
|
||||
_cache: list[dict] | None = None
|
||||
|
|
|
|||
|
|
@ -319,7 +319,11 @@ async def test_llamacloud_with_azure_di_uses_azure_for_pdf(tmp_path, mocker):
|
|||
|
||||
mocker.patch("app.config.config.ETL_SERVICE", "LLAMACLOUD")
|
||||
mocker.patch("app.config.config.LLAMA_CLOUD_API_KEY", "fake-key", create=True)
|
||||
mocker.patch("app.config.config.AZURE_DI_ENDPOINT", "https://fake.cognitiveservices.azure.com/", create=True)
|
||||
mocker.patch(
|
||||
"app.config.config.AZURE_DI_ENDPOINT",
|
||||
"https://fake.cognitiveservices.azure.com/",
|
||||
create=True,
|
||||
)
|
||||
mocker.patch("app.config.config.AZURE_DI_KEY", "fake-key", create=True)
|
||||
|
||||
fake_client = _mock_azure_di(mocker, "# Azure DI parsed")
|
||||
|
|
@ -343,7 +347,11 @@ async def test_llamacloud_azure_di_fallback_on_failure(tmp_path, mocker):
|
|||
|
||||
mocker.patch("app.config.config.ETL_SERVICE", "LLAMACLOUD")
|
||||
mocker.patch("app.config.config.LLAMA_CLOUD_API_KEY", "fake-key", create=True)
|
||||
mocker.patch("app.config.config.AZURE_DI_ENDPOINT", "https://fake.cognitiveservices.azure.com/", create=True)
|
||||
mocker.patch(
|
||||
"app.config.config.AZURE_DI_ENDPOINT",
|
||||
"https://fake.cognitiveservices.azure.com/",
|
||||
create=True,
|
||||
)
|
||||
mocker.patch("app.config.config.AZURE_DI_KEY", "fake-key", create=True)
|
||||
|
||||
mocker.patch(
|
||||
|
|
@ -369,7 +377,11 @@ async def test_llamacloud_skips_azure_di_for_unsupported_ext(tmp_path, mocker):
|
|||
|
||||
mocker.patch("app.config.config.ETL_SERVICE", "LLAMACLOUD")
|
||||
mocker.patch("app.config.config.LLAMA_CLOUD_API_KEY", "fake-key", create=True)
|
||||
mocker.patch("app.config.config.AZURE_DI_ENDPOINT", "https://fake.cognitiveservices.azure.com/", create=True)
|
||||
mocker.patch(
|
||||
"app.config.config.AZURE_DI_ENDPOINT",
|
||||
"https://fake.cognitiveservices.azure.com/",
|
||||
create=True,
|
||||
)
|
||||
mocker.patch("app.config.config.AZURE_DI_KEY", "fake-key", create=True)
|
||||
|
||||
fake_client = _mock_azure_di(mocker)
|
||||
|
|
@ -424,7 +436,10 @@ async def test_llamacloud_heif_accepted_only_with_azure_di(tmp_path, mocker):
|
|||
EtlRequest(file_path=str(heif_file), filename="photo.heif")
|
||||
)
|
||||
|
||||
mocker.patch("app.config.config.AZURE_DI_ENDPOINT", "https://fake.cognitiveservices.azure.com/")
|
||||
mocker.patch(
|
||||
"app.config.config.AZURE_DI_ENDPOINT",
|
||||
"https://fake.cognitiveservices.azure.com/",
|
||||
)
|
||||
mocker.patch("app.config.config.AZURE_DI_KEY", "fake-key")
|
||||
|
||||
fake_client = _mock_azure_di(mocker, "# HEIF from Azure DI")
|
||||
|
|
|
|||
|
|
@ -92,8 +92,9 @@ def test_non_document_extensions_are_not_supported(filename):
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_union_equals_all_three_sets():
|
||||
def test_union_includes_all_parser_extension_sets():
|
||||
from app.utils.file_extensions import (
|
||||
AZURE_DI_DOCUMENT_EXTENSIONS,
|
||||
DOCLING_DOCUMENT_EXTENSIONS,
|
||||
DOCUMENT_EXTENSIONS,
|
||||
LLAMAPARSE_DOCUMENT_EXTENSIONS,
|
||||
|
|
@ -104,6 +105,7 @@ def test_union_equals_all_three_sets():
|
|||
DOCLING_DOCUMENT_EXTENSIONS
|
||||
| LLAMAPARSE_DOCUMENT_EXTENSIONS
|
||||
| UNSTRUCTURED_DOCUMENT_EXTENSIONS
|
||||
| AZURE_DI_DOCUMENT_EXTENSIONS
|
||||
)
|
||||
assert expected == DOCUMENT_EXTENSIONS
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,9 @@ body:has(.suggestion-body) {
|
|||
padding: 2px 4px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
transition:
|
||||
color 0.15s,
|
||||
background 0.15s;
|
||||
}
|
||||
|
||||
.setup-dismiss:hover {
|
||||
|
|
|
|||
|
|
@ -191,9 +191,9 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
<AlertCircle className="h-3.5 w-3.5 text-destructive" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
{doc.status?.reason || "Processing failed"}
|
||||
</TooltipContent>
|
||||
<TooltipContent side="top">
|
||||
{doc.status?.reason || "Processing failed"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
|
@ -222,79 +222,83 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<span className="relative shrink-0 flex items-center justify-center h-6 w-6">
|
||||
{getDocumentTypeIcon(
|
||||
doc.document_type as DocumentTypeEnum,
|
||||
"h-3.5 w-3.5 text-muted-foreground"
|
||||
) && (
|
||||
<span
|
||||
className={cn(
|
||||
"absolute inset-0 flex items-center justify-center transition-opacity pointer-events-none",
|
||||
dropdownOpen ? "opacity-0" : "group-hover:opacity-0"
|
||||
)}
|
||||
>
|
||||
{getDocumentTypeIcon(
|
||||
doc.document_type as DocumentTypeEnum,
|
||||
"h-3.5 w-3.5 text-muted-foreground"
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
<span className="relative shrink-0 flex items-center justify-center h-6 w-6">
|
||||
{getDocumentTypeIcon(
|
||||
doc.document_type as DocumentTypeEnum,
|
||||
"h-3.5 w-3.5 text-muted-foreground"
|
||||
) && (
|
||||
<span
|
||||
className={cn(
|
||||
"hidden sm:inline-flex h-6 w-6 shrink-0 hover:bg-transparent",
|
||||
dropdownOpen
|
||||
? "opacity-100 bg-accent hover:bg-accent"
|
||||
: "opacity-0 group-hover:opacity-100"
|
||||
"absolute inset-0 flex items-center justify-center transition-opacity pointer-events-none",
|
||||
dropdownOpen ? "opacity-0" : "group-hover:opacity-0"
|
||||
)}
|
||||
>
|
||||
{getDocumentTypeIcon(
|
||||
doc.document_type as DocumentTypeEnum,
|
||||
"h-3.5 w-3.5 text-muted-foreground"
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn(
|
||||
"hidden sm:inline-flex h-6 w-6 shrink-0 hover:bg-transparent",
|
||||
dropdownOpen
|
||||
? "opacity-100 bg-accent hover:bg-accent"
|
||||
: "opacity-0 group-hover:opacity-100"
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="w-40"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-40" onClick={(e) => e.stopPropagation()}>
|
||||
<DropdownMenuItem onClick={() => onPreview(doc)} disabled={isProcessing}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
Open
|
||||
</DropdownMenuItem>
|
||||
{isEditable && (
|
||||
<DropdownMenuItem onClick={() => onEdit(doc)}>
|
||||
<PenLine className="mr-2 h-4 w-4" />
|
||||
Edit
|
||||
<DropdownMenuItem onClick={() => onPreview(doc)} disabled={isProcessing}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
Open
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={() => onMove(doc)}>
|
||||
<Move className="mr-2 h-4 w-4" />
|
||||
Move to...
|
||||
</DropdownMenuItem>
|
||||
{onExport && (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger disabled={isProcessing}>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Export
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="min-w-[180px]">
|
||||
<ExportDropdownItems onExport={handleExport} exporting={exporting} />
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
)}
|
||||
{onVersionHistory && isVersionableType(doc.document_type) && (
|
||||
<DropdownMenuItem disabled={isProcessing} onClick={() => onVersionHistory(doc)}>
|
||||
<History className="mr-2 h-4 w-4" />
|
||||
Versions
|
||||
{isEditable && (
|
||||
<DropdownMenuItem onClick={() => onEdit(doc)}>
|
||||
<PenLine className="mr-2 h-4 w-4" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={() => onMove(doc)}>
|
||||
<Move className="mr-2 h-4 w-4" />
|
||||
Move to...
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem disabled={isProcessing} onClick={() => onDelete(doc)}>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</span>
|
||||
{onExport && (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger disabled={isProcessing}>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Export
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="min-w-[180px]">
|
||||
<ExportDropdownItems onExport={handleExport} exporting={exporting} />
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
)}
|
||||
{onVersionHistory && isVersionableType(doc.document_type) && (
|
||||
<DropdownMenuItem disabled={isProcessing} onClick={() => onVersionHistory(doc)}>
|
||||
<History className="mr-2 h-4 w-4" />
|
||||
Versions
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem disabled={isProcessing} onClick={() => onDelete(doc)}>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</span>
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
|
||||
|
|
|
|||
|
|
@ -358,17 +358,17 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
{isWatched && onRescan && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRescan();
|
||||
}}
|
||||
>
|
||||
<RefreshCw className={cn("mr-2 h-4 w-4", isRescanning && "animate-spin")} />
|
||||
Re-scan
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{isWatched && onRescan && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRescan();
|
||||
}}
|
||||
>
|
||||
<RefreshCw className={cn("mr-2 h-4 w-4", isRescanning && "animate-spin")} />
|
||||
Re-scan
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{isWatched && onStopWatching && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
|
|
@ -407,15 +407,15 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
<Move className="mr-2 h-4 w-4" />
|
||||
Move to...
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(folder);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(folder);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
|
|
@ -424,12 +424,12 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
|
||||
{!isRenaming && contextMenuOpen && (
|
||||
<ContextMenuContent className="w-40">
|
||||
{isWatched && onRescan && (
|
||||
<ContextMenuItem onClick={() => handleRescan()}>
|
||||
<RefreshCw className={cn("mr-2 h-4 w-4", isRescanning && "animate-spin")} />
|
||||
Re-scan
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
{isWatched && onRescan && (
|
||||
<ContextMenuItem onClick={() => handleRescan()}>
|
||||
<RefreshCw className={cn("mr-2 h-4 w-4", isRescanning && "animate-spin")} />
|
||||
Re-scan
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
{isWatched && onStopWatching && (
|
||||
<ContextMenuItem onClick={() => onStopWatching(folder)}>
|
||||
<EyeOff className="mr-2 h-4 w-4" />
|
||||
|
|
@ -448,10 +448,10 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
<Move className="mr-2 h-4 w-4" />
|
||||
Move to...
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={() => onDelete(folder)}>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={() => onDelete(folder)}>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
)}
|
||||
</ContextMenu>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { useParams } from "next/navigation";
|
|||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { DocumentsFilters } from "@/components/documents/DocumentsFilters";
|
||||
import { sidebarSelectedDocumentsAtom } from "@/atoms/chat/mentioned-documents.atom";
|
||||
import { connectorDialogOpenAtom } from "@/atoms/connector-dialog/connector-dialog.atoms";
|
||||
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
|
||||
|
|
@ -18,12 +17,13 @@ import { openEditorPanelAtom } from "@/atoms/editor/editor-panel.atom";
|
|||
import { rightPanelCollapsedAtom } from "@/atoms/layout/right-panel.atom";
|
||||
import { CreateFolderDialog } from "@/components/documents/CreateFolderDialog";
|
||||
import type { DocumentNodeDoc } from "@/components/documents/DocumentNode";
|
||||
import { DocumentsFilters } from "@/components/documents/DocumentsFilters";
|
||||
import type { FolderDisplay } from "@/components/documents/FolderNode";
|
||||
import { FolderPickerDialog } from "@/components/documents/FolderPickerDialog";
|
||||
import { FolderWatchDialog, type SelectedFolder } from "@/components/sources/FolderWatchDialog";
|
||||
import { FolderTreeView } from "@/components/documents/FolderTreeView";
|
||||
import { VersionHistoryDialog } from "@/components/documents/version-history";
|
||||
import { EXPORT_FILE_EXTENSIONS } from "@/components/shared/ExportMenuItems";
|
||||
import { FolderWatchDialog, type SelectedFolder } from "@/components/sources/FolderWatchDialog";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
|
|
@ -109,8 +109,7 @@ export function DocumentsSidebar({
|
|||
const folderPath = await api.selectFolder();
|
||||
if (!folderPath) return;
|
||||
|
||||
const folderName =
|
||||
folderPath.split("/").pop() || folderPath.split("\\").pop() || folderPath;
|
||||
const folderName = folderPath.split("/").pop() || folderPath.split("\\").pop() || folderPath;
|
||||
setWatchInitialFolder({ path: folderPath, name: folderName });
|
||||
setFolderWatchOpen(true);
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ import {
|
|||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { getDocumentTypeLabel } from "@/components/documents/DocumentTypeIcon";
|
||||
import { setTargetCommentIdAtom } from "@/atoms/chat/current-thread.atom";
|
||||
import { convertRenderedToDisplay } from "@/components/chat-comments/comment-item/comment-item";
|
||||
import { getDocumentTypeLabel } from "@/components/documents/DocumentTypeIcon";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/animated-tabs";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
|
|||
|
|
@ -345,9 +345,7 @@ export function VisionConfigDialog({
|
|||
<CommandInput
|
||||
placeholder={selectedProvider?.example || "Search model name"}
|
||||
value={formData.model_name}
|
||||
onValueChange={(val) =>
|
||||
setFormData((p) => ({ ...p, model_name: val }))
|
||||
}
|
||||
onValueChange={(val) => setFormData((p) => ({ ...p, model_name: val }))}
|
||||
/>
|
||||
<CommandList className="max-h-[300px]">
|
||||
<CommandEmpty>
|
||||
|
|
|
|||
|
|
@ -26,12 +26,16 @@ import { Progress } from "@/components/ui/progress";
|
|||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { useElectronAPI } from "@/hooks/use-platform";
|
||||
import { getAcceptedFileTypes, getSupportedExtensions, getSupportedExtensionsSet } from "@/lib/supported-extensions";
|
||||
import {
|
||||
trackDocumentUploadFailure,
|
||||
trackDocumentUploadStarted,
|
||||
trackDocumentUploadSuccess,
|
||||
} from "@/lib/posthog/events";
|
||||
import {
|
||||
getAcceptedFileTypes,
|
||||
getSupportedExtensions,
|
||||
getSupportedExtensionsSet,
|
||||
} from "@/lib/supported-extensions";
|
||||
|
||||
interface DocumentUploadTabProps {
|
||||
searchSpaceId: string;
|
||||
|
|
@ -137,20 +141,24 @@ export function DocumentUploadTab({
|
|||
if (!paths || paths.length === 0) return;
|
||||
|
||||
const fileDataList = await electronAPI.readLocalFiles(paths);
|
||||
const filtered = fileDataList.filter((fd: { name: string; data: ArrayBuffer; mimeType: string }) => {
|
||||
const ext = fd.name.includes(".") ? `.${fd.name.split(".").pop()?.toLowerCase()}` : "";
|
||||
return ext !== "" && supportedExtensionsSet.has(ext);
|
||||
});
|
||||
const filtered = fileDataList.filter(
|
||||
(fd: { name: string; data: ArrayBuffer; mimeType: string }) => {
|
||||
const ext = fd.name.includes(".") ? `.${fd.name.split(".").pop()?.toLowerCase()}` : "";
|
||||
return ext !== "" && supportedExtensionsSet.has(ext);
|
||||
}
|
||||
);
|
||||
|
||||
if (filtered.length === 0) {
|
||||
toast.error(t("no_supported_files_in_folder"));
|
||||
return;
|
||||
}
|
||||
|
||||
const newFiles: FileWithId[] = filtered.map((fd: { name: string; data: ArrayBuffer; mimeType: string }) => ({
|
||||
id: crypto.randomUUID?.() ?? `file-${Date.now()}-${Math.random().toString(36)}`,
|
||||
file: new File([fd.data], fd.name, { type: fd.mimeType }),
|
||||
}));
|
||||
const newFiles: FileWithId[] = filtered.map(
|
||||
(fd: { name: string; data: ArrayBuffer; mimeType: string }) => ({
|
||||
id: crypto.randomUUID?.() ?? `file-${Date.now()}-${Math.random().toString(36)}`,
|
||||
file: new File([fd.data], fd.name, { type: fd.mimeType }),
|
||||
})
|
||||
);
|
||||
setFiles((prev) => [...prev, ...newFiles]);
|
||||
}, [electronAPI, supportedExtensionsSet, t]);
|
||||
|
||||
|
|
@ -255,14 +263,14 @@ export function DocumentUploadTab({
|
|||
className="dark:bg-neutral-800"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenuItem onClick={handleBrowseFiles}>
|
||||
<FileIcon className="h-4 w-4 mr-2" />
|
||||
Files
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => folderInputRef.current?.click()}>
|
||||
<FolderOpen className="h-4 w-4 mr-2" />
|
||||
Folder
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleBrowseFiles}>
|
||||
<FileIcon className="h-4 w-4 mr-2" />
|
||||
Files
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => folderInputRef.current?.click()}>
|
||||
<FolderOpen className="h-4 w-4 mr-2" />
|
||||
Folder
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
|
@ -320,8 +328,8 @@ export function DocumentUploadTab({
|
|||
|
||||
{/* MOBILE DROP ZONE */}
|
||||
<div className="sm:hidden">
|
||||
{hasContent ? (
|
||||
isElectron ? (
|
||||
{hasContent ? (
|
||||
isElectron ? (
|
||||
<div className="w-full">{renderBrowseButton({ compact: true, fullWidth: true })}</div>
|
||||
) : (
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -55,10 +55,7 @@ export function FolderWatchDialog({
|
|||
}
|
||||
}, [open, initialFolder]);
|
||||
|
||||
const supportedExtensions = useMemo(
|
||||
() => Array.from(getSupportedExtensionsSet()),
|
||||
[]
|
||||
);
|
||||
const supportedExtensions = useMemo(() => Array.from(getSupportedExtensionsSet()), []);
|
||||
|
||||
const handleSelectFolder = useCallback(async () => {
|
||||
const api = window.electronAPI;
|
||||
|
|
@ -67,8 +64,7 @@ export function FolderWatchDialog({
|
|||
const folderPath = await api.selectFolder();
|
||||
if (!folderPath) return;
|
||||
|
||||
const folderName =
|
||||
folderPath.split("/").pop() || folderPath.split("\\").pop() || folderPath;
|
||||
const folderName = folderPath.split("/").pop() || folderPath.split("\\").pop() || folderPath;
|
||||
setSelectedFolder({ path: folderPath, name: folderName });
|
||||
}, []);
|
||||
|
||||
|
|
@ -87,8 +83,7 @@ export function FolderWatchDialog({
|
|||
file_extensions: supportedExtensions,
|
||||
});
|
||||
|
||||
const rootFolderId =
|
||||
(result as { root_folder_id?: number })?.root_folder_id ?? null;
|
||||
const rootFolderId = (result as { root_folder_id?: number })?.root_folder_id ?? null;
|
||||
|
||||
await api.addWatchedFolder({
|
||||
path: selectedFolder.path,
|
||||
|
|
@ -110,7 +105,14 @@ export function FolderWatchDialog({
|
|||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}, [selectedFolder, searchSpaceId, shouldSummarize, supportedExtensions, onOpenChange, onSuccess]);
|
||||
}, [
|
||||
selectedFolder,
|
||||
searchSpaceId,
|
||||
shouldSummarize,
|
||||
supportedExtensions,
|
||||
onOpenChange,
|
||||
onSuccess,
|
||||
]);
|
||||
|
||||
const handleOpenChange = useCallback(
|
||||
(nextOpen: boolean) => {
|
||||
|
|
@ -128,18 +130,14 @@ export function FolderWatchDialog({
|
|||
<DialogContent className="sm:max-w-md select-none">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Watch Local Folder</DialogTitle>
|
||||
<DialogDescription>
|
||||
Select a folder to sync and watch for changes.
|
||||
</DialogDescription>
|
||||
<DialogDescription>Select a folder to sync and watch for changes.</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3 pt-2">
|
||||
{selectedFolder ? (
|
||||
<div className="flex items-center gap-2 py-1.5 pl-4 pr-2 rounded-md bg-slate-400/5 dark:bg-white/5 overflow-hidden">
|
||||
<div className="min-w-0 flex-1 select-text">
|
||||
<p className="text-sm font-medium break-all line-clamp-2">
|
||||
{selectedFolder.name}
|
||||
</p>
|
||||
<p className="text-sm font-medium break-all line-clamp-2">{selectedFolder.name}</p>
|
||||
<p className="text-xs text-muted-foreground break-all line-clamp-2">
|
||||
{selectedFolder.path}
|
||||
</p>
|
||||
|
|
@ -173,20 +171,11 @@ export function FolderWatchDialog({
|
|||
Improves search quality but adds latency
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={shouldSummarize}
|
||||
onCheckedChange={setShouldSummarize}
|
||||
/>
|
||||
<Switch checked={shouldSummarize} onCheckedChange={setShouldSummarize} />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="w-full relative"
|
||||
onClick={handleSubmit}
|
||||
disabled={submitting}
|
||||
>
|
||||
<span className={submitting ? "invisible" : ""}>
|
||||
Start Folder Sync
|
||||
</span>
|
||||
<Button className="w-full relative" onClick={handleSubmit} disabled={submitting}>
|
||||
<span className={submitting ? "invisible" : ""}>Start Folder Sync</span>
|
||||
{submitting && (
|
||||
<span className="absolute inset-0 flex items-center justify-center">
|
||||
<Spinner size="sm" />
|
||||
|
|
|
|||
|
|
@ -107,22 +107,62 @@ export const VISION_MODELS: LLMModel[] = [
|
|||
{ value: "gpt-4o", label: "GPT-4o", provider: "OPENAI", contextWindow: "128K" },
|
||||
{ value: "gpt-4o-mini", label: "GPT-4o Mini", provider: "OPENAI", contextWindow: "128K" },
|
||||
{ value: "gpt-4-turbo", label: "GPT-4 Turbo", provider: "OPENAI", contextWindow: "128K" },
|
||||
{ value: "claude-sonnet-4-20250514", label: "Claude Sonnet 4", provider: "ANTHROPIC", contextWindow: "200K" },
|
||||
{ value: "claude-3-7-sonnet-20250219", label: "Claude 3.7 Sonnet", provider: "ANTHROPIC", contextWindow: "200K" },
|
||||
{ value: "claude-3-5-sonnet-20241022", label: "Claude 3.5 Sonnet", provider: "ANTHROPIC", contextWindow: "200K" },
|
||||
{ value: "claude-3-opus-20240229", label: "Claude 3 Opus", provider: "ANTHROPIC", contextWindow: "200K" },
|
||||
{ value: "claude-3-haiku-20240307", label: "Claude 3 Haiku", provider: "ANTHROPIC", contextWindow: "200K" },
|
||||
{
|
||||
value: "claude-sonnet-4-20250514",
|
||||
label: "Claude Sonnet 4",
|
||||
provider: "ANTHROPIC",
|
||||
contextWindow: "200K",
|
||||
},
|
||||
{
|
||||
value: "claude-3-7-sonnet-20250219",
|
||||
label: "Claude 3.7 Sonnet",
|
||||
provider: "ANTHROPIC",
|
||||
contextWindow: "200K",
|
||||
},
|
||||
{
|
||||
value: "claude-3-5-sonnet-20241022",
|
||||
label: "Claude 3.5 Sonnet",
|
||||
provider: "ANTHROPIC",
|
||||
contextWindow: "200K",
|
||||
},
|
||||
{
|
||||
value: "claude-3-opus-20240229",
|
||||
label: "Claude 3 Opus",
|
||||
provider: "ANTHROPIC",
|
||||
contextWindow: "200K",
|
||||
},
|
||||
{
|
||||
value: "claude-3-haiku-20240307",
|
||||
label: "Claude 3 Haiku",
|
||||
provider: "ANTHROPIC",
|
||||
contextWindow: "200K",
|
||||
},
|
||||
{ value: "gemini-2.5-flash", label: "Gemini 2.5 Flash", provider: "GOOGLE", contextWindow: "1M" },
|
||||
{ value: "gemini-2.5-pro", label: "Gemini 2.5 Pro", provider: "GOOGLE", contextWindow: "1M" },
|
||||
{ value: "gemini-2.0-flash", label: "Gemini 2.0 Flash", provider: "GOOGLE", contextWindow: "1M" },
|
||||
{ value: "gemini-1.5-pro", label: "Gemini 1.5 Pro", provider: "GOOGLE", contextWindow: "1M" },
|
||||
{ value: "gemini-1.5-flash", label: "Gemini 1.5 Flash", provider: "GOOGLE", contextWindow: "1M" },
|
||||
{ value: "pixtral-large-latest", label: "Pixtral Large", provider: "MISTRAL", contextWindow: "128K" },
|
||||
{
|
||||
value: "pixtral-large-latest",
|
||||
label: "Pixtral Large",
|
||||
provider: "MISTRAL",
|
||||
contextWindow: "128K",
|
||||
},
|
||||
{ value: "pixtral-12b-2409", label: "Pixtral 12B", provider: "MISTRAL", contextWindow: "128K" },
|
||||
{ value: "grok-2-vision-1212", label: "Grok 2 Vision", provider: "XAI", contextWindow: "32K" },
|
||||
{ value: "llava", label: "LLaVA", provider: "OLLAMA" },
|
||||
{ value: "bakllava", label: "BakLLaVA", provider: "OLLAMA" },
|
||||
{ value: "llava-llama3", label: "LLaVA Llama 3", provider: "OLLAMA" },
|
||||
{ value: "llama-4-scout-17b-16e-instruct", label: "Llama 4 Scout 17B", provider: "GROQ", contextWindow: "128K" },
|
||||
{ value: "meta-llama/Llama-4-Scout-17B-16E-Instruct", label: "Llama 4 Scout 17B", provider: "TOGETHER_AI", contextWindow: "128K" },
|
||||
{
|
||||
value: "llama-4-scout-17b-16e-instruct",
|
||||
label: "Llama 4 Scout 17B",
|
||||
provider: "GROQ",
|
||||
contextWindow: "128K",
|
||||
},
|
||||
{
|
||||
value: "meta-llama/Llama-4-Scout-17B-16E-Instruct",
|
||||
label: "Llama 4 Scout 17B",
|
||||
provider: "TOGETHER_AI",
|
||||
contextWindow: "128K",
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -80,9 +80,7 @@ export function getAcceptedFileTypes(): Record<string, string[]> {
|
|||
return FILE_TYPE_CONFIG[etlService || "default"] || FILE_TYPE_CONFIG.default;
|
||||
}
|
||||
|
||||
export function getSupportedExtensions(
|
||||
acceptedFileTypes?: Record<string, string[]>
|
||||
): string[] {
|
||||
export function getSupportedExtensions(acceptedFileTypes?: Record<string, string[]>): string[] {
|
||||
const types = acceptedFileTypes ?? getAcceptedFileTypes();
|
||||
return Array.from(new Set(Object.values(types).flat())).sort();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue