mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-24 23:41:10 +02:00
chore: linting
This commit is contained in:
parent
8d1e2a5134
commit
4a727a2c57
41 changed files with 114 additions and 133 deletions
|
|
@ -51,8 +51,7 @@ from app.gateway.inbox_worker import (
|
|||
start_gateway_inbox_worker,
|
||||
stop_gateway_inbox_worker,
|
||||
)
|
||||
from app.observability import analytics as ph_analytics
|
||||
from app.observability import metrics as ot_metrics
|
||||
from app.observability import analytics as ph_analytics, metrics as ot_metrics
|
||||
from app.observability.bootstrap import init_otel, shutdown_otel
|
||||
from app.rate_limiter import get_real_client_ip, limiter
|
||||
from app.routes import router as crud_router
|
||||
|
|
@ -843,11 +842,7 @@ class PatApiAnalyticsMiddleware(BaseHTTPMiddleware):
|
|||
response = await call_next(request)
|
||||
with contextlib.suppress(Exception):
|
||||
ctx = getattr(request.state, "auth_context", None)
|
||||
if (
|
||||
ctx is not None
|
||||
and ctx.method == "pat"
|
||||
and ph_analytics.is_enabled()
|
||||
):
|
||||
if ctx is not None and ctx.method == "pat" and ph_analytics.is_enabled():
|
||||
# Use the route *template* (e.g. /documents/{id}) to keep the
|
||||
# ``route`` property low-cardinality; fall back to the raw path.
|
||||
route = request.scope.get("route")
|
||||
|
|
|
|||
|
|
@ -41,9 +41,7 @@ def _capture_run_outcome(run: AutomationRun, status: str) -> None:
|
|||
"status": status,
|
||||
"trigger_type": run.trigger.type.value if run.trigger else None,
|
||||
},
|
||||
groups={"workspace": str(workspace_id)}
|
||||
if workspace_id is not None
|
||||
else None,
|
||||
groups={"workspace": str(workspace_id)} if workspace_id is not None else None,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -132,7 +132,9 @@ def _breadcrumbs(product: dict[str, Any]) -> list[str]:
|
|||
path = dig(product, "category", "path")
|
||||
if not isinstance(path, list):
|
||||
return []
|
||||
return [node["name"] for node in path if isinstance(node, dict) and node.get("name")]
|
||||
return [
|
||||
node["name"] for node in path if isinstance(node, dict) and node.get("name")
|
||||
]
|
||||
|
||||
|
||||
def _reviews_sample(
|
||||
|
|
|
|||
|
|
@ -14,16 +14,16 @@ from fastapi.responses import StreamingResponse
|
|||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.config import config
|
||||
from app.observability import analytics as ph_analytics
|
||||
from app.tasks.chat.streaming.flows.shared.analytics import (
|
||||
build_llm_callback_handler,
|
||||
)
|
||||
from app.etl_pipeline.file_classifier import (
|
||||
DIRECT_CONVERT_EXTENSIONS,
|
||||
PLAINTEXT_EXTENSIONS,
|
||||
)
|
||||
from app.observability import analytics as ph_analytics
|
||||
from app.rate_limiter import limiter
|
||||
from app.tasks.chat.streaming.errors.classifier import classify_stream_exception
|
||||
from app.tasks.chat.streaming.flows.shared.analytics import (
|
||||
build_llm_callback_handler,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.observability import analytics as ph_analytics
|
||||
from app.config import config
|
||||
from app.db import (
|
||||
ImageGeneration,
|
||||
|
|
@ -27,6 +26,7 @@ from app.db import (
|
|||
WorkspaceMembership,
|
||||
get_async_session,
|
||||
)
|
||||
from app.observability import analytics as ph_analytics
|
||||
from app.schemas import (
|
||||
ImageGenerationCreate,
|
||||
ImageGenerationListRead,
|
||||
|
|
|
|||
|
|
@ -45,8 +45,11 @@ from app.db import (
|
|||
get_async_session,
|
||||
)
|
||||
from app.notifications.service import NotificationService
|
||||
from app.observability import analytics as ph_analytics
|
||||
from app.observability import metrics as ot_metrics, otel as ot
|
||||
from app.observability import (
|
||||
analytics as ph_analytics,
|
||||
metrics as ot_metrics,
|
||||
otel as ot,
|
||||
)
|
||||
from app.schemas import (
|
||||
GoogleDriveIndexRequest,
|
||||
MCPConnectorCreate,
|
||||
|
|
|
|||
|
|
@ -247,9 +247,7 @@ async def build_export_zip(
|
|||
suffix = used_paths[file_path]
|
||||
base_name = f"{base_name}_{suffix}"
|
||||
file_path = (
|
||||
f"{dir_path}/{base_name}.md"
|
||||
if dir_path
|
||||
else f"{base_name}.md"
|
||||
f"{dir_path}/{base_name}.md" if dir_path else f"{base_name}.md"
|
||||
)
|
||||
used_paths[file_path] = used_paths.get(file_path, 0) + 1
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ from app.services.okf.validator import (
|
|||
__all__ = [
|
||||
"INDEX_FILENAME",
|
||||
"LOG_FILENAME",
|
||||
"RECOMMENDED_FRONTMATTER_KEYS",
|
||||
"REQUIRED_FRONTMATTER_KEYS",
|
||||
"ConceptRef",
|
||||
"LogEntry",
|
||||
"SubdirRef",
|
||||
|
|
@ -44,13 +46,11 @@ __all__ = [
|
|||
"document_to_concept",
|
||||
"folder_to_index",
|
||||
"folder_to_log",
|
||||
"render_frontmatter",
|
||||
"is_conformant_concept",
|
||||
"okf_resource",
|
||||
"okf_type",
|
||||
"RECOMMENDED_FRONTMATTER_KEYS",
|
||||
"REQUIRED_FRONTMATTER_KEYS",
|
||||
"is_conformant_concept",
|
||||
"parse_frontmatter",
|
||||
"render_frontmatter",
|
||||
"validate_bundle",
|
||||
"validate_concept",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -49,7 +49,11 @@ def build_frontmatter(document: Document) -> dict[str, Any]:
|
|||
Only ``type`` is required; recommended keys are included only when we have a
|
||||
value. Insertion order is preserved in the emitted YAML.
|
||||
"""
|
||||
metadata = document.document_metadata if isinstance(document.document_metadata, dict) else {}
|
||||
metadata = (
|
||||
document.document_metadata
|
||||
if isinstance(document.document_metadata, dict)
|
||||
else {}
|
||||
)
|
||||
|
||||
frontmatter: dict[str, Any] = {"type": okf_type(document.document_type)}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,9 @@ OKF_TYPE_BY_DOCUMENT_TYPE: dict[DocumentType, str] = {
|
|||
}
|
||||
|
||||
|
||||
def _coerce_document_type(document_type: DocumentType | str | None) -> DocumentType | None:
|
||||
def _coerce_document_type(
|
||||
document_type: DocumentType | str | None,
|
||||
) -> DocumentType | None:
|
||||
if document_type is None:
|
||||
return None
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@ from collections.abc import Awaitable, Callable
|
|||
from celery import current_task
|
||||
|
||||
from app.celery_app import celery_app
|
||||
from app.observability import analytics as ph_analytics
|
||||
from app.observability import metrics as ot_metrics, otel as ot
|
||||
from app.observability import (
|
||||
analytics as ph_analytics,
|
||||
metrics as ot_metrics,
|
||||
otel as ot,
|
||||
)
|
||||
from app.tasks.celery_tasks import (
|
||||
get_celery_session_maker,
|
||||
run_async_celery_task as _run_async_celery_task,
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ from uuid import UUID
|
|||
from app.celery_app import celery_app
|
||||
from app.config import config
|
||||
from app.notifications.service import NotificationService
|
||||
from app.observability import analytics as ph_analytics
|
||||
from app.observability import metrics as ot_metrics
|
||||
from app.observability import analytics as ph_analytics, metrics as ot_metrics
|
||||
from app.services.task_logging_service import TaskLoggingService
|
||||
from app.tasks.celery_tasks import get_celery_session_maker, run_async_celery_task
|
||||
from app.tasks.connector_indexers.local_folder_indexer import (
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ from app.tasks.chat.streaming.flows.new_chat.title_gen import (
|
|||
maybe_emit_title_update,
|
||||
spawn_title_task,
|
||||
)
|
||||
from app.tasks.chat.streaming.flows.shared.analytics import (
|
||||
build_llm_callback_handler,
|
||||
capture_chat_turn_completed,
|
||||
)
|
||||
from app.tasks.chat.streaming.flows.shared.assistant_finalize import (
|
||||
finalize_assistant_message,
|
||||
)
|
||||
|
|
@ -97,10 +101,6 @@ from app.tasks.chat.streaming.flows.shared.rate_limit_recovery import (
|
|||
log_rate_limit_recovered,
|
||||
reroute_to_next_auto_pin,
|
||||
)
|
||||
from app.tasks.chat.streaming.flows.shared.analytics import (
|
||||
build_llm_callback_handler,
|
||||
capture_chat_turn_completed,
|
||||
)
|
||||
from app.tasks.chat.streaming.flows.shared.span import (
|
||||
close_chat_request_span,
|
||||
open_chat_request_span,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ from app.tasks.chat.streaming.flows.resume_chat.resume_routing import (
|
|||
from app.tasks.chat.streaming.flows.resume_chat.runtime_context import (
|
||||
build_resume_chat_runtime_context,
|
||||
)
|
||||
from app.tasks.chat.streaming.flows.shared.analytics import (
|
||||
build_llm_callback_handler,
|
||||
capture_chat_turn_completed,
|
||||
)
|
||||
from app.tasks.chat.streaming.flows.shared.assistant_finalize import (
|
||||
finalize_assistant_message,
|
||||
)
|
||||
|
|
@ -76,10 +80,6 @@ from app.tasks.chat.streaming.flows.shared.rate_limit_recovery import (
|
|||
log_rate_limit_recovered,
|
||||
reroute_to_next_auto_pin,
|
||||
)
|
||||
from app.tasks.chat.streaming.flows.shared.analytics import (
|
||||
build_llm_callback_handler,
|
||||
capture_chat_turn_completed,
|
||||
)
|
||||
from app.tasks.chat.streaming.flows.shared.span import (
|
||||
close_chat_request_span,
|
||||
open_chat_request_span,
|
||||
|
|
|
|||
|
|
@ -105,9 +105,7 @@ def capture_chat_turn_completed(
|
|||
groups = {"workspace": str(workspace_id)}
|
||||
|
||||
if auth_context is not None:
|
||||
analytics.capture_for(
|
||||
auth_context, "chat_turn_completed", props, groups=groups
|
||||
)
|
||||
analytics.capture_for(auth_context, "chat_turn_completed", props, groups=groups)
|
||||
else:
|
||||
analytics.capture(
|
||||
"chat_turn_completed",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from app.auth.context import AuthContext
|
||||
from app.auth.session_cookies import access_expires_at, write_session
|
||||
from app.config import config
|
||||
from app.observability import analytics as ph_analytics
|
||||
from app.db import (
|
||||
Prompt,
|
||||
User,
|
||||
|
|
@ -32,6 +31,7 @@ from app.db import (
|
|||
get_default_roles_config,
|
||||
get_user_db,
|
||||
)
|
||||
from app.observability import analytics as ph_analytics
|
||||
from app.prompts.system_defaults import SYSTEM_PROMPT_DEFAULTS
|
||||
from app.utils.pat import PAT_PREFIX, maybe_touch_last_used, resolve_pat
|
||||
from app.utils.refresh_tokens import create_refresh_token
|
||||
|
|
@ -353,6 +353,7 @@ async def get_auth_context(
|
|||
FastAPI-Users still handles JWT mechanics; PATs are resolved here so RBAC
|
||||
receives the full SurfSense principal instead of a bare User.
|
||||
"""
|
||||
|
||||
def _stash(ctx: AuthContext) -> AuthContext:
|
||||
# Expose the resolved principal on request.state so downstream
|
||||
# middleware (e.g. PostHog pat_api_request attribution) can read it
|
||||
|
|
|
|||
|
|
@ -75,9 +75,7 @@ async def step1_search(sess, state: dict) -> bool:
|
|||
)
|
||||
for it in items[:5]:
|
||||
print(f" - {it.get('jobKey')} | {it.get('title')} @ {it.get('company')}")
|
||||
state["job_url"] = next(
|
||||
(it["jobUrl"] for it in items if it.get("jobUrl")), None
|
||||
)
|
||||
state["job_url"] = next((it["jobUrl"] for it in items if it.get("jobUrl")), None)
|
||||
return _check("search returned jobs", len(items) > 0, f"{len(items)} jobs")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -52,12 +52,20 @@ async def test_export_bundle_is_okf_conformant(
|
|||
await db_session.flush()
|
||||
|
||||
await _add_doc(
|
||||
db_session, workspace=db_workspace, user=db_user,
|
||||
title="Root Note", folder_id=None, uid="okf-export-root",
|
||||
db_session,
|
||||
workspace=db_workspace,
|
||||
user=db_user,
|
||||
title="Root Note",
|
||||
folder_id=None,
|
||||
uid="okf-export-root",
|
||||
)
|
||||
await _add_doc(
|
||||
db_session, workspace=db_workspace, user=db_user,
|
||||
title="Nested Note", folder_id=folder.id, uid="okf-export-nested",
|
||||
db_session,
|
||||
workspace=db_workspace,
|
||||
user=db_user,
|
||||
title="Nested Note",
|
||||
folder_id=folder.id,
|
||||
uid="okf-export-nested",
|
||||
)
|
||||
|
||||
result = await build_export_zip(db_session, db_workspace.id)
|
||||
|
|
|
|||
|
|
@ -56,9 +56,7 @@ async def _roundtrip(
|
|||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def research_folder(
|
||||
db_session: AsyncSession, db_workspace: Workspace
|
||||
) -> Folder:
|
||||
async def research_folder(db_session: AsyncSession, db_workspace: Workspace) -> Folder:
|
||||
folder = Folder(name="Research", position="0", workspace_id=db_workspace.id)
|
||||
db_session.add(folder)
|
||||
await db_session.flush()
|
||||
|
|
|
|||
|
|
@ -85,9 +85,7 @@ class TestConceptIdentityRoundTrip:
|
|||
|
||||
def test_folder_nested_document_roundtrips(self):
|
||||
index = PathIndex(folder_paths={5: f"{DOCUMENTS_ROOT}/Research/AI"})
|
||||
path = doc_to_virtual_path(
|
||||
doc_id=2, title="My Note", folder_id=5, index=index
|
||||
)
|
||||
path = doc_to_virtual_path(doc_id=2, title="My Note", folder_id=5, index=index)
|
||||
assert path == f"{DOCUMENTS_ROOT}/Research/AI/My Note.xml"
|
||||
folder_parts, title = parse_documents_path(path)
|
||||
assert folder_parts == ["Research", "AI"]
|
||||
|
|
@ -97,9 +95,7 @@ class TestConceptIdentityRoundTrip:
|
|||
# Second doc with the same title gets a " (<id>)" suffix; parsing the
|
||||
# path must strip the disambiguator and recover the original title.
|
||||
index = PathIndex(occupants={f"{DOCUMENTS_ROOT}/Hello.xml": 7})
|
||||
path = doc_to_virtual_path(
|
||||
doc_id=8, title="Hello", folder_id=None, index=index
|
||||
)
|
||||
path = doc_to_virtual_path(doc_id=8, title="Hello", folder_id=None, index=index)
|
||||
assert path == f"{DOCUMENTS_ROOT}/Hello (8).xml"
|
||||
folder_parts, title = parse_documents_path(path)
|
||||
assert folder_parts == []
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ async def test_community_only_scrapes_listing(monkeypatch):
|
|||
assert [i["id"] for i in items] == ["movies", "movies-p1"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("raw", ["movies", "r/movies", "/r/movies/", " movies ", "R/movies"])
|
||||
@pytest.mark.parametrize(
|
||||
"raw", ["movies", "r/movies", "/r/movies/", " movies ", "R/movies"]
|
||||
)
|
||||
async def test_community_name_normalized(monkeypatch, raw):
|
||||
seen: list[str] = []
|
||||
monkeypatch.setattr(scraper, "_subreddit_flow", _fake_subreddit_flow(seen))
|
||||
|
|
|
|||
|
|
@ -62,7 +62,9 @@ def test_validator_rejects_non_conformant_documents() -> None:
|
|||
def test_folder_index_groups_by_type_and_lists_subdirs() -> None:
|
||||
index = folder_to_index(
|
||||
concepts=[
|
||||
ConceptRef(title="Orders", filename="orders.md", type="Note", description="x"),
|
||||
ConceptRef(
|
||||
title="Orders", filename="orders.md", type="Note", description="x"
|
||||
),
|
||||
],
|
||||
subdirectories=[SubdirRef(name="tables", description="Table docs")],
|
||||
)
|
||||
|
|
@ -114,11 +116,7 @@ def test_export_index_files_include_root_version_and_ancestors() -> None:
|
|||
# A concept nested two levels deep, with no direct docs in the middle dir.
|
||||
files = dict(
|
||||
_build_index_files(
|
||||
{
|
||||
"Research/AI": [
|
||||
ConceptRef(title="Note", filename="note.md", type="Note")
|
||||
]
|
||||
}
|
||||
{"Research/AI": [ConceptRef(title="Note", filename="note.md", type="Note")]}
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -93,8 +93,10 @@ def register(mcp: FastMCP, client: SurfSenseClient, context: WorkspaceContext) -
|
|||
] = False,
|
||||
country_code: Annotated[
|
||||
str | None,
|
||||
Field(description="Two-letter delivery country for localized pricing, "
|
||||
"e.g. 'us'."),
|
||||
Field(
|
||||
description="Two-letter delivery country for localized pricing, "
|
||||
"e.g. 'us'."
|
||||
),
|
||||
] = None,
|
||||
zip_code: Annotated[
|
||||
str | None,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ def register(mcp: FastMCP, client: SurfSenseClient, context: WorkspaceContext) -
|
|||
] = None,
|
||||
country: Annotated[
|
||||
str,
|
||||
Field(description="Country code selecting the Indeed domain, e.g. 'us', 'gb'."),
|
||||
Field(
|
||||
description="Country code selecting the Indeed domain, e.g. 'us', 'gb'."
|
||||
),
|
||||
] = "us",
|
||||
location: Annotated[
|
||||
str | None,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ def register(mcp: FastMCP, client: SurfSenseClient, context: WorkspaceContext) -
|
|||
] = None,
|
||||
max_items: Annotated[
|
||||
int,
|
||||
Field(ge=1, le=100, description="Max products per search term or listing URL."),
|
||||
Field(
|
||||
ge=1, le=100, description="Max products per search term or listing URL."
|
||||
),
|
||||
] = 10,
|
||||
include_details: Annotated[
|
||||
bool,
|
||||
|
|
|
|||
|
|
@ -178,8 +178,7 @@ export default function NewChatPage() {
|
|||
const hasLiveStream = !!streamState && streamState.messages.length > 0;
|
||||
const isActiveThreadHydrated = hydratedMessagesRef.current.threadId === activeThreadId;
|
||||
const shouldHideStaleMessages = !!activeThreadId && !hasLiveStream && !isActiveThreadHydrated;
|
||||
const isThreadMessagesLoading =
|
||||
shouldHideStaleMessages && !threadMessagesQuery.error;
|
||||
const isThreadMessagesLoading = shouldHideStaleMessages && !threadMessagesQuery.error;
|
||||
const runtimeMessages = shouldHideStaleMessages ? EMPTY_MESSAGES : displayMessages;
|
||||
|
||||
// Live collaboration: sync session state and messages via Zero. Kept on the
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ import { atom, type Getter, type Setter } from "jotai";
|
|||
import { rightPanelCollapsedAtom, rightPanelTabAtom } from "@/atoms/layout/right-panel.atom";
|
||||
|
||||
/** The source the citation panel is showing: a KB chunk or a scraper run. */
|
||||
export type CitationTarget =
|
||||
| { kind: "chunk"; chunkId: number }
|
||||
| { kind: "run"; runId: string };
|
||||
export type CitationTarget = { kind: "chunk"; chunkId: number } | { kind: "run"; runId: string };
|
||||
|
||||
interface CitationPanelState {
|
||||
isOpen: boolean;
|
||||
|
|
|
|||
|
|
@ -285,7 +285,11 @@ export const pruneMissingChatTabsAtom = atom(null, (get, set, missingChatIds: Se
|
|||
}
|
||||
|
||||
const activeWasPruned = state.tabs.some(
|
||||
(t) => t.id === state.activeTabId && t.type === "chat" && t.entityId !== null && missingChatIds.has(t.entityId)
|
||||
(t) =>
|
||||
t.id === state.activeTabId &&
|
||||
t.type === "chat" &&
|
||||
t.entityId !== null &&
|
||||
missingChatIds.has(t.entityId)
|
||||
);
|
||||
const newActiveId = activeWasPruned
|
||||
? remaining[Math.min(firstMissingIdx, remaining.length - 1)].id
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@ type Screen =
|
|||
| { kind: "tools" }
|
||||
| { kind: "toolGroup"; label: string };
|
||||
|
||||
const ROW = "flex w-full items-center gap-3 px-4 py-3 text-sm hover:bg-accent hover:text-accent-foreground transition-colors";
|
||||
const ROW =
|
||||
"flex w-full items-center gap-3 px-4 py-3 text-sm hover:bg-accent hover:text-accent-foreground transition-colors";
|
||||
|
||||
export function ComposerAddMenuDrawer({
|
||||
trigger,
|
||||
|
|
@ -156,11 +157,7 @@ export function ComposerAddMenuDrawer({
|
|||
<Upload className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="flex-1 text-left">Upload Files</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={ROW}
|
||||
onClick={() => push({ kind: "connectors" })}
|
||||
>
|
||||
<button type="button" className={ROW} onClick={() => push({ kind: "connectors" })}>
|
||||
<Unplug className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="flex-1 text-left">MCP Connectors</span>
|
||||
<ChevronRight className="size-4 shrink-0 text-muted-foreground" />
|
||||
|
|
|
|||
|
|
@ -266,11 +266,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
|
||||
{/* Scrollable Content */}
|
||||
<div className="flex-1 min-h-0 relative overflow-hidden">
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
className="h-full overflow-y-auto"
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<div ref={scrollContainerRef} className="h-full overflow-y-auto" onScroll={handleScroll}>
|
||||
<div className="space-y-6 pb-6 pt-2">
|
||||
{/* Connector-specific configuration */}
|
||||
{ConnectorConfigComponent && (
|
||||
|
|
|
|||
|
|
@ -164,11 +164,7 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
|
|||
|
||||
{/* Scrollable Content */}
|
||||
<div className="flex-1 min-h-0 relative overflow-hidden">
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
className="h-full overflow-y-auto"
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<div ref={scrollContainerRef} className="h-full overflow-y-auto" onScroll={handleScroll}>
|
||||
<div className="space-y-6 pb-6 pt-2">
|
||||
{/* Connector-specific configuration */}
|
||||
{ConnectorConfigComponent && connector && (
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ import {
|
|||
getConnectorTitle,
|
||||
OAUTH_CONNECTORS,
|
||||
} from "./constants/connector-constants";
|
||||
import { type ConnectorRow, useConnectorRows } from "./hooks/use-connector-rows";
|
||||
import { useConnectorDialog } from "./hooks/use-connector-dialog";
|
||||
import { type ConnectorRow, useConnectorRows } from "./hooks/use-connector-rows";
|
||||
import { AllConnectorsTab } from "./tabs/all-connectors-tab";
|
||||
import { ConnectorAccountsListView } from "./views/connector-accounts-list-view";
|
||||
import { YouTubeCrawlerView } from "./views/youtube-crawler-view";
|
||||
|
|
@ -115,7 +115,9 @@ export function ConnectorsSection() {
|
|||
} = useConnectorsSync(workspaceId);
|
||||
|
||||
const useSyncData = connectorsFromSync.length > 0 || (connectorsLoading && !connectorsError);
|
||||
const connectors = (useSyncData ? connectorsFromSync : allConnectors || []) as SearchSourceConnector[];
|
||||
const connectors = (
|
||||
useSyncData ? connectorsFromSync : allConnectors || []
|
||||
) as SearchSourceConnector[];
|
||||
|
||||
const refreshConnectors = async () => {
|
||||
if (useSyncData) {
|
||||
|
|
@ -174,9 +176,7 @@ export function ConnectorsSection() {
|
|||
onManage={handleStartEdit}
|
||||
onAddAccount={() => {
|
||||
const oauthConnector =
|
||||
OAUTH_CONNECTORS.find(
|
||||
(c) => c.connectorType === viewingAccountsType.connectorType
|
||||
) ||
|
||||
OAUTH_CONNECTORS.find((c) => c.connectorType === viewingAccountsType.connectorType) ||
|
||||
COMPOSIO_CONNECTORS.find(
|
||||
(c) => c.connectorType === viewingAccountsType.connectorType
|
||||
);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ export function useConnectorRows(connectors: SearchSourceConnector[]) {
|
|||
return groupConnectorsByType(connectors).map((row) => {
|
||||
const ids = row.connectors.map((c) => c.id);
|
||||
const syncing = ids.some(
|
||||
(id) => indexingConnectorIds.has(id) || statusByConnectorId.get(id)?.status === "in_progress"
|
||||
(id) =>
|
||||
indexingConnectorIds.has(id) || statusByConnectorId.get(id)?.status === "in_progress"
|
||||
);
|
||||
const failedEntry = syncing
|
||||
? undefined
|
||||
|
|
|
|||
|
|
@ -152,15 +152,10 @@ interface ThreadProps {
|
|||
}
|
||||
|
||||
export const Thread: FC<ThreadProps> = ({ hasActiveThread = false, isLoadingMessages = false }) => {
|
||||
return (
|
||||
<ThreadContent hasActiveThread={hasActiveThread} isLoadingMessages={isLoadingMessages} />
|
||||
);
|
||||
return <ThreadContent hasActiveThread={hasActiveThread} isLoadingMessages={isLoadingMessages} />;
|
||||
};
|
||||
|
||||
const ThreadContent: FC<ThreadProps> = ({
|
||||
hasActiveThread = false,
|
||||
isLoadingMessages = false,
|
||||
}) => {
|
||||
const ThreadContent: FC<ThreadProps> = ({ hasActiveThread = false, isLoadingMessages = false }) => {
|
||||
return (
|
||||
<ThreadPrimitive.Root
|
||||
className="aui-root aui-thread-root @container flex h-full min-h-0 flex-col bg-main-panel"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import {
|
||||
Check,
|
||||
Copy,
|
||||
FileQuestionMark,
|
||||
FileText,
|
||||
Pencil,
|
||||
RefreshCw,
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import { Check, Copy, FileQuestionMark, FileText, Pencil, RefreshCw, XIcon } from "lucide-react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
|
|
|||
|
|
@ -658,8 +658,8 @@ export function HeroSection() {
|
|||
>
|
||||
SurfSense is the open-source NotebookLM alternative for AI agents, an open web
|
||||
research platform with live data connectors. Your AI agents research the live web with
|
||||
structured data from Reddit, YouTube, Instagram, TikTok, Amazon, Walmart, Google
|
||||
Maps, Google Search, and any page on the open web.
|
||||
structured data from Reddit, YouTube, Instagram, TikTok, Amazon, Walmart, Google Maps,
|
||||
Google Search, and any page on the open web.
|
||||
</p>
|
||||
|
||||
<div className="relative mb-4 flex w-full flex-col justify-center gap-y-2 sm:flex-row sm:justify-start sm:space-y-0 sm:space-x-4">
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import { activeTabIdAtom } from "@/atoms/tabs/tabs.atom";
|
|||
import { Logo } from "@/components/Logo";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { type ResolvedTab, useResolvedTabs } from "@/hooks/use-resolved-tabs";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { useElectronAPI } from "@/hooks/use-platform";
|
||||
import { type ResolvedTab, useResolvedTabs } from "@/hooks/use-resolved-tabs";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SidebarProvider, useSidebarState } from "../../hooks";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -298,14 +298,8 @@ function AllChatsContent({ workspaceId, className }: AllChatsContentProps) {
|
|||
{isLoading ? (
|
||||
<div className="space-y-1">
|
||||
{[75, 90, 55, 80, 65, 85].map((titleWidth) => (
|
||||
<div
|
||||
key={`skeleton-${titleWidth}`}
|
||||
className="rounded-md px-3 py-1.5 md:py-2"
|
||||
>
|
||||
<Skeleton
|
||||
className="h-4 rounded md:h-4.5"
|
||||
style={{ width: `${titleWidth}%` }}
|
||||
/>
|
||||
<div key={`skeleton-${titleWidth}`} className="rounded-md px-3 py-1.5 md:py-2">
|
||||
<Skeleton className="h-4 rounded md:h-4.5" style={{ width: `${titleWidth}%` }} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -419,7 +413,8 @@ function AllChatsContent({ workspaceId, className }: AllChatsContentProps) {
|
|||
className={cn(
|
||||
"pointer-events-auto absolute right-0 h-7 w-7 hover:bg-transparent",
|
||||
openDropdownId === thread.id && "bg-accent hover:bg-accent",
|
||||
openDropdownId !== thread.id && "opacity-0 group-hover/item:opacity-100"
|
||||
openDropdownId !== thread.id &&
|
||||
"opacity-0 group-hover/item:opacity-100"
|
||||
)}
|
||||
disabled={isBusy}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -100,9 +100,7 @@ export function ModelsSelectionPanel({
|
|||
variant="secondary"
|
||||
size="sm"
|
||||
className={`h-7 rounded-full px-3 text-xs ${
|
||||
isActive
|
||||
? "bg-brand text-white hover:bg-brand/90"
|
||||
: "opacity-80"
|
||||
isActive ? "bg-brand text-white hover:bg-brand/90" : "opacity-80"
|
||||
}`}
|
||||
onClick={() => setModelFilter(isActive ? null : filter.key)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,8 @@ export const indeed: ConnectorPageContent = {
|
|||
},
|
||||
{
|
||||
label: "Location",
|
||||
description: "Formatted location, city, state, postal code, country, and remote or hybrid flags.",
|
||||
description:
|
||||
"Formatted location, city, state, postal code, country, and remote or hybrid flags.",
|
||||
},
|
||||
{
|
||||
label: "Salary",
|
||||
|
|
@ -157,8 +158,7 @@ export const indeed: ConnectorPageContent = {
|
|||
},
|
||||
|
||||
schema: {
|
||||
requestNote:
|
||||
"Provide at least one source: urls or search_queries. Up to 20 sources per call.",
|
||||
requestNote: "Provide at least one source: urls or search_queries. Up to 20 sources per call.",
|
||||
request: [
|
||||
{
|
||||
name: "urls",
|
||||
|
|
|
|||
|
|
@ -131,7 +131,8 @@ export const walmart: ConnectorPageContent = {
|
|||
{
|
||||
feature: "Reviews",
|
||||
official: "A separate paid endpoint, often capped shallow",
|
||||
surfsense: "A dedicated verb pages the full public review history with photos and seller replies",
|
||||
surfsense:
|
||||
"A dedicated verb pages the full public review history with photos and seller replies",
|
||||
},
|
||||
{
|
||||
feature: "Data access",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue