mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-17 18:35:19 +02:00
chore: linting
This commit is contained in:
parent
3375aeb9bc
commit
7ae68455b3
20 changed files with 128 additions and 103 deletions
|
|
@ -24,9 +24,7 @@ def enum_exists(enum_name: str) -> bool:
|
||||||
"""Check if an enum type exists in the database."""
|
"""Check if an enum type exists in the database."""
|
||||||
conn = op.get_bind()
|
conn = op.get_bind()
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
sa.text(
|
sa.text("SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"),
|
||||||
"SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"
|
|
||||||
),
|
|
||||||
{"enum_name": enum_name},
|
{"enum_name": enum_name},
|
||||||
)
|
)
|
||||||
return result.scalar()
|
return result.scalar()
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@ def enum_exists(enum_name: str) -> bool:
|
||||||
"""Check if an enum type exists in the database."""
|
"""Check if an enum type exists in the database."""
|
||||||
conn = op.get_bind()
|
conn = op.get_bind()
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
sa.text(
|
sa.text("SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"),
|
||||||
"SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"
|
|
||||||
),
|
|
||||||
{"enum_name": enum_name},
|
{"enum_name": enum_name},
|
||||||
)
|
)
|
||||||
return result.scalar()
|
return result.scalar()
|
||||||
|
|
|
||||||
|
|
@ -197,9 +197,7 @@ def enum_exists(enum_name: str) -> bool:
|
||||||
"""Check if an enum type exists in the database."""
|
"""Check if an enum type exists in the database."""
|
||||||
conn = op.get_bind()
|
conn = op.get_bind()
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
sa.text(
|
sa.text("SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"),
|
||||||
"SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"
|
|
||||||
),
|
|
||||||
{"enum_name": enum_name},
|
{"enum_name": enum_name},
|
||||||
)
|
)
|
||||||
return result.scalar()
|
return result.scalar()
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@ Revises: 61
|
||||||
Create Date: 2026-01-09 15:19:51.827647
|
Create Date: 2026-01-09 15:19:51.827647
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = '62'
|
revision: str = "62"
|
||||||
down_revision: str | None = '61'
|
down_revision: str | None = "61"
|
||||||
branch_labels: str | Sequence[str] | None = None
|
branch_labels: str | Sequence[str] | None = None
|
||||||
depends_on: str | Sequence[str] | None = None
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ Revises: 62
|
||||||
Create Date: 2026-01-13 12:23:31.481643
|
Create Date: 2026-01-13 12:23:31.481643
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
|
|
@ -12,8 +13,8 @@ from sqlalchemy import text
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = '63'
|
revision: str = "63"
|
||||||
down_revision: str | None = '62'
|
down_revision: str | None = "62"
|
||||||
branch_labels: str | Sequence[str] | None = None
|
branch_labels: str | Sequence[str] | None = None
|
||||||
depends_on: str | Sequence[str] | None = None
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ depends_on: str | Sequence[str] | None = None
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Upgrade schema."""
|
"""Upgrade schema."""
|
||||||
connection = op.get_bind()
|
connection = op.get_bind()
|
||||||
|
|
||||||
# Check if old constraint exists before trying to drop it
|
# Check if old constraint exists before trying to drop it
|
||||||
old_constraint_exists = connection.execute(
|
old_constraint_exists = connection.execute(
|
||||||
text("""
|
text("""
|
||||||
|
|
@ -31,14 +32,14 @@ def upgrade() -> None:
|
||||||
AND constraint_name='uq_searchspace_user_connector_type'
|
AND constraint_name='uq_searchspace_user_connector_type'
|
||||||
""")
|
""")
|
||||||
).scalar()
|
).scalar()
|
||||||
|
|
||||||
if old_constraint_exists:
|
if old_constraint_exists:
|
||||||
op.drop_constraint(
|
op.drop_constraint(
|
||||||
'uq_searchspace_user_connector_type',
|
"uq_searchspace_user_connector_type",
|
||||||
'search_source_connectors',
|
"search_source_connectors",
|
||||||
type_='unique'
|
type_="unique",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if new constraint already exists before creating it
|
# Check if new constraint already exists before creating it
|
||||||
new_constraint_exists = connection.execute(
|
new_constraint_exists = connection.execute(
|
||||||
text("""
|
text("""
|
||||||
|
|
@ -48,19 +49,19 @@ def upgrade() -> None:
|
||||||
AND constraint_name='uq_searchspace_user_connector_type_name'
|
AND constraint_name='uq_searchspace_user_connector_type_name'
|
||||||
""")
|
""")
|
||||||
).scalar()
|
).scalar()
|
||||||
|
|
||||||
if not new_constraint_exists:
|
if not new_constraint_exists:
|
||||||
op.create_unique_constraint(
|
op.create_unique_constraint(
|
||||||
'uq_searchspace_user_connector_type_name',
|
"uq_searchspace_user_connector_type_name",
|
||||||
'search_source_connectors',
|
"search_source_connectors",
|
||||||
['search_space_id', 'user_id', 'connector_type', 'name']
|
["search_space_id", "user_id", "connector_type", "name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
"""Downgrade schema."""
|
"""Downgrade schema."""
|
||||||
connection = op.get_bind()
|
connection = op.get_bind()
|
||||||
|
|
||||||
# Check if new constraint exists before trying to drop it
|
# Check if new constraint exists before trying to drop it
|
||||||
new_constraint_exists = connection.execute(
|
new_constraint_exists = connection.execute(
|
||||||
text("""
|
text("""
|
||||||
|
|
@ -70,14 +71,14 @@ def downgrade() -> None:
|
||||||
AND constraint_name='uq_searchspace_user_connector_type_name'
|
AND constraint_name='uq_searchspace_user_connector_type_name'
|
||||||
""")
|
""")
|
||||||
).scalar()
|
).scalar()
|
||||||
|
|
||||||
if new_constraint_exists:
|
if new_constraint_exists:
|
||||||
op.drop_constraint(
|
op.drop_constraint(
|
||||||
'uq_searchspace_user_connector_type_name',
|
"uq_searchspace_user_connector_type_name",
|
||||||
'search_source_connectors',
|
"search_source_connectors",
|
||||||
type_='unique'
|
type_="unique",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if old constraint already exists before creating it
|
# Check if old constraint already exists before creating it
|
||||||
old_constraint_exists = connection.execute(
|
old_constraint_exists = connection.execute(
|
||||||
text("""
|
text("""
|
||||||
|
|
@ -87,10 +88,10 @@ def downgrade() -> None:
|
||||||
AND constraint_name='uq_searchspace_user_connector_type'
|
AND constraint_name='uq_searchspace_user_connector_type'
|
||||||
""")
|
""")
|
||||||
).scalar()
|
).scalar()
|
||||||
|
|
||||||
if not old_constraint_exists:
|
if not old_constraint_exists:
|
||||||
op.create_unique_constraint(
|
op.create_unique_constraint(
|
||||||
'uq_searchspace_user_connector_type',
|
"uq_searchspace_user_connector_type",
|
||||||
'search_source_connectors',
|
"search_source_connectors",
|
||||||
['search_space_id', 'user_id', 'connector_type']
|
["search_space_id", "user_id", "connector_type"],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -44,4 +44,3 @@ def downgrade() -> None:
|
||||||
DROP COLUMN IF EXISTS author_id;
|
DROP COLUMN IF EXISTS author_id;
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ logger = logging.getLogger(__name__)
|
||||||
class MCPClient:
|
class MCPClient:
|
||||||
"""Client for communicating with an MCP server."""
|
"""Client for communicating with an MCP server."""
|
||||||
|
|
||||||
def __init__(self, command: str, args: list[str], env: dict[str, str] | None = None):
|
def __init__(
|
||||||
|
self, command: str, args: list[str], env: dict[str, str] | None = None
|
||||||
|
):
|
||||||
"""Initialize MCP client.
|
"""Initialize MCP client.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -44,18 +46,16 @@ class MCPClient:
|
||||||
# Merge env vars with current environment
|
# Merge env vars with current environment
|
||||||
server_env = os.environ.copy()
|
server_env = os.environ.copy()
|
||||||
server_env.update(self.env)
|
server_env.update(self.env)
|
||||||
|
|
||||||
# Create server parameters with env
|
# Create server parameters with env
|
||||||
server_params = StdioServerParameters(
|
server_params = StdioServerParameters(
|
||||||
command=self.command,
|
command=self.command, args=self.args, env=server_env
|
||||||
args=self.args,
|
|
||||||
env=server_env
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Spawn server process and create session
|
# Spawn server process and create session
|
||||||
# Note: Cannot combine these context managers because ClientSession
|
# Note: Cannot combine these context managers because ClientSession
|
||||||
# needs the read/write streams from stdio_client
|
# needs the read/write streams from stdio_client
|
||||||
async with stdio_client(server=server_params) as (read, write):
|
async with stdio_client(server=server_params) as (read, write): # noqa: SIM117
|
||||||
async with ClientSession(read, write) as session:
|
async with ClientSession(read, write) as session:
|
||||||
# Initialize the connection
|
# Initialize the connection
|
||||||
await session.initialize()
|
await session.initialize()
|
||||||
|
|
@ -85,7 +85,9 @@ class MCPClient:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.session:
|
if not self.session:
|
||||||
raise RuntimeError("Not connected to MCP server. Use 'async with client.connect():'")
|
raise RuntimeError(
|
||||||
|
"Not connected to MCP server. Use 'async with client.connect():'"
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Call tools/list RPC method
|
# Call tools/list RPC method
|
||||||
|
|
@ -93,11 +95,15 @@ class MCPClient:
|
||||||
|
|
||||||
tools = []
|
tools = []
|
||||||
for tool in response.tools:
|
for tool in response.tools:
|
||||||
tools.append({
|
tools.append(
|
||||||
"name": tool.name,
|
{
|
||||||
"description": tool.description or "",
|
"name": tool.name,
|
||||||
"input_schema": tool.inputSchema if hasattr(tool, "inputSchema") else {},
|
"description": tool.description or "",
|
||||||
})
|
"input_schema": tool.inputSchema
|
||||||
|
if hasattr(tool, "inputSchema")
|
||||||
|
else {},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
logger.info("Listed %d tools from MCP server", len(tools))
|
logger.info("Listed %d tools from MCP server", len(tools))
|
||||||
return tools
|
return tools
|
||||||
|
|
@ -121,10 +127,14 @@ class MCPClient:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.session:
|
if not self.session:
|
||||||
raise RuntimeError("Not connected to MCP server. Use 'async with client.connect():'")
|
raise RuntimeError(
|
||||||
|
"Not connected to MCP server. Use 'async with client.connect():'"
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info("Calling MCP tool '%s' with arguments: %s", tool_name, arguments)
|
logger.info(
|
||||||
|
"Calling MCP tool '%s' with arguments: %s", tool_name, arguments
|
||||||
|
)
|
||||||
|
|
||||||
# Call tools/call RPC method
|
# Call tools/call RPC method
|
||||||
response = await self.session.call_tool(tool_name, arguments=arguments)
|
response = await self.session.call_tool(tool_name, arguments=arguments)
|
||||||
|
|
@ -147,12 +157,17 @@ class MCPClient:
|
||||||
# Handle validation errors from MCP server responses
|
# Handle validation errors from MCP server responses
|
||||||
# Some MCP servers (like server-memory) return extra fields not in their schema
|
# Some MCP servers (like server-memory) return extra fields not in their schema
|
||||||
if "Invalid structured content" in str(e):
|
if "Invalid structured content" in str(e):
|
||||||
logger.warning("MCP server returned data not matching its schema, but continuing: %s", e)
|
logger.warning(
|
||||||
|
"MCP server returned data not matching its schema, but continuing: %s",
|
||||||
|
e,
|
||||||
|
)
|
||||||
# Try to extract result from error message or return a success message
|
# Try to extract result from error message or return a success message
|
||||||
return "Operation completed (server returned unexpected format)"
|
return "Operation completed (server returned unexpected format)"
|
||||||
raise
|
raise
|
||||||
except (ValueError, TypeError, AttributeError, KeyError) as e:
|
except (ValueError, TypeError, AttributeError, KeyError) as e:
|
||||||
logger.error("Failed to call MCP tool '%s': %s", tool_name, e, exc_info=True)
|
logger.error(
|
||||||
|
"Failed to call MCP tool '%s': %s", tool_name, e, exc_info=True
|
||||||
|
)
|
||||||
return f"Error calling tool: {e!s}"
|
return f"Error calling tool: {e!s}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _create_dynamic_input_model_from_schema(
|
def _create_dynamic_input_model_from_schema(
|
||||||
tool_name: str, input_schema: dict[str, Any],
|
tool_name: str,
|
||||||
|
input_schema: dict[str, Any],
|
||||||
) -> type[BaseModel]:
|
) -> type[BaseModel]:
|
||||||
"""Create a Pydantic model from MCP tool's JSON schema.
|
"""Create a Pydantic model from MCP tool's JSON schema.
|
||||||
|
|
||||||
|
|
@ -41,15 +42,18 @@ def _create_dynamic_input_model_from_schema(
|
||||||
for param_name, param_schema in properties.items():
|
for param_name, param_schema in properties.items():
|
||||||
param_description = param_schema.get("description", "")
|
param_description = param_schema.get("description", "")
|
||||||
is_required = param_name in required_fields
|
is_required = param_name in required_fields
|
||||||
|
|
||||||
# Use Any type for complex schemas to preserve structure
|
# Use Any type for complex schemas to preserve structure
|
||||||
# This allows the MCP server to do its own validation
|
# This allows the MCP server to do its own validation
|
||||||
from typing import Any as AnyType
|
from typing import Any as AnyType
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
if is_required:
|
if is_required:
|
||||||
field_definitions[param_name] = (AnyType, Field(..., description=param_description))
|
field_definitions[param_name] = (
|
||||||
|
AnyType,
|
||||||
|
Field(..., description=param_description),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
field_definitions[param_name] = (
|
field_definitions[param_name] = (
|
||||||
AnyType | None,
|
AnyType | None,
|
||||||
|
|
@ -88,7 +92,7 @@ async def _create_mcp_tool_from_definition(
|
||||||
async def mcp_tool_call(**kwargs) -> str:
|
async def mcp_tool_call(**kwargs) -> str:
|
||||||
"""Execute the MCP tool call via the client."""
|
"""Execute the MCP tool call via the client."""
|
||||||
logger.info(f"MCP tool '{tool_name}' called with params: {kwargs}")
|
logger.info(f"MCP tool '{tool_name}' called with params: {kwargs}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Connect to server and call tool
|
# Connect to server and call tool
|
||||||
async with mcp_client.connect():
|
async with mcp_client.connect():
|
||||||
|
|
@ -114,7 +118,8 @@ async def _create_mcp_tool_from_definition(
|
||||||
|
|
||||||
|
|
||||||
async def load_mcp_tools(
|
async def load_mcp_tools(
|
||||||
session: AsyncSession, search_space_id: int,
|
session: AsyncSession,
|
||||||
|
search_space_id: int,
|
||||||
) -> list[StructuredTool]:
|
) -> list[StructuredTool]:
|
||||||
"""Load all MCP tools from user's active MCP server connectors.
|
"""Load all MCP tools from user's active MCP server connectors.
|
||||||
|
|
||||||
|
|
@ -150,7 +155,9 @@ async def load_mcp_tools(
|
||||||
env = server_config.get("env", {})
|
env = server_config.get("env", {})
|
||||||
|
|
||||||
if not command:
|
if not command:
|
||||||
logger.warning(f"MCP connector {connector.id} missing command, skipping")
|
logger.warning(
|
||||||
|
f"MCP connector {connector.id} missing command, skipping"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Create MCP client
|
# Create MCP client
|
||||||
|
|
@ -168,7 +175,9 @@ async def load_mcp_tools(
|
||||||
# Create LangChain tools from definitions
|
# Create LangChain tools from definitions
|
||||||
for tool_def in tool_definitions:
|
for tool_def in tool_definitions:
|
||||||
try:
|
try:
|
||||||
tool = await _create_mcp_tool_from_definition(tool_def, mcp_client)
|
tool = await _create_mcp_tool_from_definition(
|
||||||
|
tool_def, mcp_client
|
||||||
|
)
|
||||||
tools.append(tool)
|
tools.append(tool)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,8 @@ async def build_tools_async(
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
mcp_tools = await load_mcp_tools(
|
mcp_tools = await load_mcp_tools(
|
||||||
dependencies["db_session"], dependencies["search_space_id"],
|
dependencies["db_session"],
|
||||||
|
dependencies["search_space_id"],
|
||||||
)
|
)
|
||||||
tools.extend(mcp_tools)
|
tools.extend(mcp_tools)
|
||||||
logging.info(
|
logging.info(
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ class SearchSourceConnectorBase(BaseModel):
|
||||||
@field_validator("config")
|
@field_validator("config")
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_config_for_connector_type(
|
def validate_config_for_connector_type(
|
||||||
cls, config: dict[str, Any], values: dict[str, Any],
|
cls,
|
||||||
|
config: dict[str, Any],
|
||||||
|
values: dict[str, Any],
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
connector_type = values.data.get("connector_type")
|
connector_type = values.data.get("connector_type")
|
||||||
return validate_connector_config(connector_type, config)
|
return validate_connector_config(connector_type, config)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ from .base import (
|
||||||
)
|
)
|
||||||
from .markdown_processor import add_received_markdown_file_document
|
from .markdown_processor import add_received_markdown_file_document
|
||||||
|
|
||||||
|
|
||||||
# Constants for LlamaCloud retry configuration
|
# Constants for LlamaCloud retry configuration
|
||||||
LLAMACLOUD_MAX_RETRIES = 3
|
LLAMACLOUD_MAX_RETRIES = 3
|
||||||
LLAMACLOUD_BASE_DELAY = 5 # Base delay in seconds for exponential backoff
|
LLAMACLOUD_BASE_DELAY = 5 # Base delay in seconds for exponential backoff
|
||||||
|
|
|
||||||
|
|
@ -120,4 +120,3 @@ export function ApiKeyContent({ onMenuClick }: ApiKeyContentProps) {
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import { AnimatePresence, motion } from "motion/react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
|
||||||
import { updateUserMutationAtom } from "@/atoms/user/user-mutation.atoms";
|
import { updateUserMutationAtom } from "@/atoms/user/user-mutation.atoms";
|
||||||
|
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ArrowLeft, ChevronRight, X } from "lucide-react";
|
|
||||||
import type { LucideIcon } from "lucide-react";
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
import { ArrowLeft, ChevronRight, X } from "lucide-react";
|
||||||
import { AnimatePresence, motion } from "motion/react";
|
import { AnimatePresence, motion } from "motion/react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -152,4 +152,3 @@ export function UserSettingsSidebar({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { useTranslations } from "next-intl";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { ApiKeyContent } from "./components/ApiKeyContent";
|
import { ApiKeyContent } from "./components/ApiKeyContent";
|
||||||
import { ProfileContent } from "./components/ProfileContent";
|
import { ProfileContent } from "./components/ProfileContent";
|
||||||
import { UserSettingsSidebar, type SettingsNavItem } from "./components/UserSettingsSidebar";
|
import { type SettingsNavItem, UserSettingsSidebar } from "./components/UserSettingsSidebar";
|
||||||
|
|
||||||
export default function UserSettingsPage() {
|
export default function UserSettingsPage() {
|
||||||
const t = useTranslations("userSettings");
|
const t = useTranslations("userSettings");
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,3 @@ export const updateUserMutationAtom = atomWithMutation((get) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,9 @@ const DocumentUploadPopupContent: FC<{
|
||||||
<Upload className="size-4 sm:size-7 text-primary" />
|
<Upload className="size-4 sm:size-7 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0 pr-8 sm:pr-0">
|
<div className="flex-1 min-w-0 pr-8 sm:pr-0">
|
||||||
<h2 className="text-base sm:text-2xl font-semibold tracking-tight">Upload Documents</h2>
|
<h2 className="text-base sm:text-2xl font-semibold tracking-tight">
|
||||||
|
Upload Documents
|
||||||
|
</h2>
|
||||||
<p className="text-xs sm:text-base text-muted-foreground mt-0.5 sm:mt-1 line-clamp-1 sm:line-clamp-none">
|
<p className="text-xs sm:text-base text-muted-foreground mt-0.5 sm:mt-1 line-clamp-1 sm:line-clamp-none">
|
||||||
Upload and sync your documents to your search space
|
Upload and sync your documents to your search space
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,7 @@ import {
|
||||||
newLLMConfigsAtom,
|
newLLMConfigsAtom,
|
||||||
} from "@/atoms/new-llm-config/new-llm-config-query.atoms";
|
} from "@/atoms/new-llm-config/new-llm-config-query.atoms";
|
||||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||||
import {
|
import { ComposerAddAttachment, ComposerAttachments } from "@/components/assistant-ui/attachment";
|
||||||
ComposerAddAttachment,
|
|
||||||
ComposerAttachments,
|
|
||||||
} from "@/components/assistant-ui/attachment";
|
|
||||||
import { UserMessage } from "@/components/assistant-ui/user-message";
|
|
||||||
import { ConnectorIndicator } from "@/components/assistant-ui/connector-popup";
|
import { ConnectorIndicator } from "@/components/assistant-ui/connector-popup";
|
||||||
import {
|
import {
|
||||||
InlineMentionEditor,
|
InlineMentionEditor,
|
||||||
|
|
@ -53,6 +49,7 @@ import {
|
||||||
} from "@/components/assistant-ui/thinking-steps";
|
} from "@/components/assistant-ui/thinking-steps";
|
||||||
import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
|
import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
|
||||||
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
||||||
|
import { UserMessage } from "@/components/assistant-ui/user-message";
|
||||||
import {
|
import {
|
||||||
DocumentMentionPicker,
|
DocumentMentionPicker,
|
||||||
type DocumentMentionPickerRef,
|
type DocumentMentionPickerRef,
|
||||||
|
|
@ -636,7 +633,6 @@ const AssistantActionBar: FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const EditComposer: FC = () => {
|
const EditComposer: FC = () => {
|
||||||
return (
|
return (
|
||||||
<MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
|
<MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
|
||||||
|
|
|
||||||
|
|
@ -139,30 +139,33 @@ export function DocumentUploadTab({
|
||||||
[acceptedFileTypes]
|
[acceptedFileTypes]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
const onDrop = useCallback(
|
||||||
setFiles((prev) => {
|
(acceptedFiles: File[]) => {
|
||||||
const newFiles = [...prev, ...acceptedFiles];
|
setFiles((prev) => {
|
||||||
|
const newFiles = [...prev, ...acceptedFiles];
|
||||||
// Check file count limit
|
|
||||||
if (newFiles.length > MAX_FILES) {
|
// Check file count limit
|
||||||
toast.error(t("max_files_exceeded"), {
|
if (newFiles.length > MAX_FILES) {
|
||||||
description: t("max_files_exceeded_desc", { max: MAX_FILES }),
|
toast.error(t("max_files_exceeded"), {
|
||||||
});
|
description: t("max_files_exceeded_desc", { max: MAX_FILES }),
|
||||||
return prev;
|
});
|
||||||
}
|
return prev;
|
||||||
|
}
|
||||||
// Check total size limit
|
|
||||||
const newTotalSize = newFiles.reduce((sum, file) => sum + file.size, 0);
|
// Check total size limit
|
||||||
if (newTotalSize > MAX_TOTAL_SIZE_BYTES) {
|
const newTotalSize = newFiles.reduce((sum, file) => sum + file.size, 0);
|
||||||
toast.error(t("max_size_exceeded"), {
|
if (newTotalSize > MAX_TOTAL_SIZE_BYTES) {
|
||||||
description: t("max_size_exceeded_desc", { max: MAX_TOTAL_SIZE_MB }),
|
toast.error(t("max_size_exceeded"), {
|
||||||
});
|
description: t("max_size_exceeded_desc", { max: MAX_TOTAL_SIZE_MB }),
|
||||||
return prev;
|
});
|
||||||
}
|
return prev;
|
||||||
|
}
|
||||||
return newFiles;
|
|
||||||
});
|
return newFiles;
|
||||||
}, [t]);
|
});
|
||||||
|
},
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
|
||||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||||
onDrop,
|
onDrop,
|
||||||
|
|
@ -191,7 +194,10 @@ export function DocumentUploadTab({
|
||||||
const isFileCountLimitReached = files.length >= MAX_FILES;
|
const isFileCountLimitReached = files.length >= MAX_FILES;
|
||||||
const isSizeLimitReached = totalFileSize >= MAX_TOTAL_SIZE_BYTES;
|
const isSizeLimitReached = totalFileSize >= MAX_TOTAL_SIZE_BYTES;
|
||||||
const remainingFiles = MAX_FILES - files.length;
|
const remainingFiles = MAX_FILES - files.length;
|
||||||
const remainingSizeMB = Math.max(0, (MAX_TOTAL_SIZE_BYTES - totalFileSize) / (1024 * 1024)).toFixed(1);
|
const remainingSizeMB = Math.max(
|
||||||
|
0,
|
||||||
|
(MAX_TOTAL_SIZE_BYTES - totalFileSize) / (1024 * 1024)
|
||||||
|
).toFixed(1);
|
||||||
|
|
||||||
// Track accordion state changes
|
// Track accordion state changes
|
||||||
const handleAccordionChange = useCallback(
|
const handleAccordionChange = useCallback(
|
||||||
|
|
@ -243,7 +249,8 @@ export function DocumentUploadTab({
|
||||||
<Alert className="border border-border bg-slate-400/5 dark:bg-white/5 flex items-start gap-3 [&>svg]:relative [&>svg]:left-0 [&>svg]:top-0 [&>svg~*]:pl-0">
|
<Alert className="border border-border bg-slate-400/5 dark:bg-white/5 flex items-start gap-3 [&>svg]:relative [&>svg]:left-0 [&>svg]:top-0 [&>svg~*]:pl-0">
|
||||||
<Info className="h-4 w-4 shrink-0 mt-0.5" />
|
<Info className="h-4 w-4 shrink-0 mt-0.5" />
|
||||||
<AlertDescription className="text-xs sm:text-sm leading-relaxed pt-0.5">
|
<AlertDescription className="text-xs sm:text-sm leading-relaxed pt-0.5">
|
||||||
{t("file_size_limit")} {t("upload_limits", { maxFiles: MAX_FILES, maxSizeMB: MAX_TOTAL_SIZE_MB })}
|
{t("file_size_limit")}{" "}
|
||||||
|
{t("upload_limits", { maxFiles: MAX_FILES, maxSizeMB: MAX_TOTAL_SIZE_MB })}
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
|
@ -270,7 +277,9 @@ export function DocumentUploadTab({
|
||||||
<div className="flex flex-col items-center gap-2 sm:gap-4 text-center px-4">
|
<div className="flex flex-col items-center gap-2 sm:gap-4 text-center px-4">
|
||||||
<Upload className="h-8 w-8 sm:h-12 sm:w-12 text-destructive/70" />
|
<Upload className="h-8 w-8 sm:h-12 sm:w-12 text-destructive/70" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm sm:text-lg font-medium text-destructive">{t("file_limit_reached")}</p>
|
<p className="text-sm sm:text-lg font-medium text-destructive">
|
||||||
|
{t("file_limit_reached")}
|
||||||
|
</p>
|
||||||
<p className="text-xs sm:text-sm text-muted-foreground mt-1">
|
<p className="text-xs sm:text-sm text-muted-foreground mt-1">
|
||||||
{t("file_limit_reached_desc", { max: MAX_FILES })}
|
{t("file_limit_reached_desc", { max: MAX_FILES })}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
getMeResponse,
|
getMeResponse,
|
||||||
updateUserResponse,
|
|
||||||
type UpdateUserRequest,
|
type UpdateUserRequest,
|
||||||
|
updateUserResponse,
|
||||||
} from "@/contracts/types/user.types";
|
} from "@/contracts/types/user.types";
|
||||||
import { baseApiService } from "./base-api.service";
|
import { baseApiService } from "./base-api.service";
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue