Merge branch 'main' into dev-docker-compose

This commit is contained in:
Edwin Jose 2025-09-18 16:51:59 -04:00
commit 1736ff56bd
16 changed files with 1095 additions and 1278 deletions

View file

@ -62,8 +62,7 @@ LANGFLOW_CHAT_FLOW_ID=your_chat_flow_id
LANGFLOW_INGEST_FLOW_ID=your_ingest_flow_id
NUDGES_FLOW_ID=your_nudges_flow_id
```
ee extended configuration, including ingestion and optional variables: [docs/configuration.md](docs/
configuration.md)
See extended configuration, including ingestion and optional variables: [docs/configuration.md](docs/configuration.md)
### 3. Start OpenRAG
```bash

View file

@ -91,7 +91,7 @@ services:
langflow:
volumes:
- ./flows:/app/flows:Z
image: phact/langflow:${LANGFLOW_VERSION:-responses}
image: phact/openrag-langflow:${LANGFLOW_VERSION:-latest}
container_name: langflow
ports:
- "7860:7860"

View file

@ -1,12 +1,12 @@
"use client";
import { EllipsisVertical } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { EllipsisVertical } from "lucide-react";
import { Button } from "./ui/button";
export function KnowledgeActionsDropdown() {
@ -18,7 +18,9 @@ export function KnowledgeActionsDropdown() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="right" sideOffset={-10}>
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
<DropdownMenuItem className="text-destructive focus:text-destructive-foreground focus:bg-destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);

View file

@ -62,7 +62,7 @@ export const MarkdownRenderer = ({ chatMessage }: MarkdownRendererProps) => {
<Markdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeMathjax, rehypeRaw]}
linkTarget="_blank"
urlTransform={(url) => url}
components={{
p({ node, ...props }) {
return <p className="w-fit max-w-full">{props.children}</p>;
@ -79,7 +79,7 @@ export const MarkdownRenderer = ({ chatMessage }: MarkdownRendererProps) => {
h3({ node, ...props }) {
return <h3 className="mb-2 mt-4">{props.children}</h3>;
},
hr({ node, ...props }) {
hr() {
return <hr className="w-full mt-4 mb-8" />;
},
ul({ node, ...props }) {
@ -97,8 +97,12 @@ export const MarkdownRenderer = ({ chatMessage }: MarkdownRendererProps) => {
</div>
);
},
a({ node, ...props }) {
return <a {...props} target="_blank" rel="noopener noreferrer">{props.children}</a>;
},
code: ({ node, className, inline, children, ...props }) => {
code(props) {
const { children, className, ...rest } = props;
let content = children as string;
if (
Array.isArray(children) &&
@ -120,14 +124,15 @@ export const MarkdownRenderer = ({ chatMessage }: MarkdownRendererProps) => {
}
const match = /language-(\w+)/.exec(className || "");
const isInline = !className?.startsWith("language-");
return !inline ? (
return !isInline ? (
<CodeComponent
language={(match && match[1]) || ""}
code={String(content).replace(/\n$/, "")}
/>
) : (
<code className={className} {...props}>
<code className={className} {...rest}>
{content}
</code>
);

File diff suppressed because it is too large Load diff

View file

@ -38,11 +38,11 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"react-markdown": "^8.0.7",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^15.6.1",
"rehype-mathjax": "^4.0.3",
"rehype-raw": "^6.1.1",
"remark-gfm": "3.0.1",
"rehype-mathjax": "^7.1.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.6",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
@ -53,6 +53,7 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react-syntax-highlighter": "^15.5.13",
"autoprefixer": "^10.4.21",
"eslint": "^9",
"eslint-config-next": "15.3.5",

View file

@ -18,8 +18,7 @@ async def chat_endpoint(request: Request, chat_service, session_manager):
user = request.state.user
user_id = user.user_id
# Get JWT token from auth middleware
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user_id, request.state.jwt_token)
if not prompt:
return JSONResponse({"error": "Prompt is required"}, status_code=400)
@ -76,8 +75,7 @@ async def langflow_endpoint(request: Request, chat_service, session_manager):
user = request.state.user
user_id = user.user_id
# Get JWT token from auth middleware
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user_id, request.state.jwt_token)
if not prompt:
return JSONResponse({"error": "Prompt is required"}, status_code=400)

View file

@ -13,8 +13,8 @@ async def list_connectors(request: Request, connector_service, session_manager):
)
return JSONResponse({"connectors": connector_types})
except Exception as e:
logger.error("Error listing connectors", error=str(e))
return JSONResponse({"error": str(e)}, status_code=500)
logger.info("Error listing connectors", error=str(e))
return JSONResponse({"connectors": []})
async def connector_sync(request: Request, connector_service, session_manager):
@ -31,7 +31,7 @@ async def connector_sync(request: Request, connector_service, session_manager):
max_files=max_files,
)
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
# Get all active connections for this connector type and user
connections = await connector_service.connection_manager.list_connections(

View file

@ -26,7 +26,7 @@ async def create_knowledge_filter(
return JSONResponse({"error": "Query data is required"}, status_code=400)
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
# Create knowledge filter document
filter_id = str(uuid.uuid4())
@ -70,7 +70,7 @@ async def search_knowledge_filters(
limit = payload.get("limit", 20)
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
result = await knowledge_filter_service.search_knowledge_filters(
query, user_id=user.user_id, jwt_token=jwt_token, limit=limit
@ -101,7 +101,7 @@ async def get_knowledge_filter(
)
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
result = await knowledge_filter_service.get_knowledge_filter(
filter_id, user_id=user.user_id, jwt_token=jwt_token
@ -136,7 +136,7 @@ async def update_knowledge_filter(
payload = await request.json()
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
# First, get the existing knowledge filter
existing_result = await knowledge_filter_service.get_knowledge_filter(
@ -205,7 +205,7 @@ async def delete_knowledge_filter(
)
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
result = await knowledge_filter_service.delete_knowledge_filter(
filter_id, user_id=user.user_id, jwt_token=jwt_token
@ -239,7 +239,7 @@ async def subscribe_to_knowledge_filter(
payload = await request.json()
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
# Get the knowledge filter to validate it exists and get its details
filter_result = await knowledge_filter_service.get_knowledge_filter(
@ -309,7 +309,7 @@ async def list_knowledge_filter_subscriptions(
)
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
result = await knowledge_filter_service.get_filter_subscriptions(
filter_id, user_id=user.user_id, jwt_token=jwt_token
@ -341,7 +341,7 @@ async def cancel_knowledge_filter_subscription(
)
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
# Get subscription details to find the monitor ID
subscriptions_result = await knowledge_filter_service.get_filter_subscriptions(

View file

@ -9,7 +9,7 @@ async def nudges_from_kb_endpoint(request: Request, chat_service, session_manage
"""Get nudges for a user"""
user = request.state.user
user_id = user.user_id
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user_id, request.state.jwt_token)
try:
result = await chat_service.langflow_nudges_chat(
@ -28,7 +28,8 @@ async def nudges_from_chat_id_endpoint(request: Request, chat_service, session_m
user = request.state.user
user_id = user.user_id
chat_id = request.path_params["chat_id"]
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user_id, request.state.jwt_token)
try:
result = await chat_service.langflow_nudges_chat(

View file

@ -20,8 +20,7 @@ async def search(request: Request, search_service, session_manager):
) # Optional score threshold, defaults to 0
user = request.state.user
# Extract JWT token from auth middleware
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
logger.debug(
"Search API request",

View file

@ -11,7 +11,7 @@ async def upload(request: Request, document_service, session_manager):
form = await request.form()
upload_file = form["file"]
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
from config.settings import is_no_auth_mode
@ -60,7 +60,7 @@ async def upload_path(request: Request, task_service, session_manager):
return JSONResponse({"error": "No files found in directory"}, status_code=400)
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
from config.settings import is_no_auth_mode
@ -100,8 +100,7 @@ async def upload_context(
previous_response_id = form.get("previous_response_id")
endpoint = form.get("endpoint", "langflow")
# Get JWT token from auth middleware
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user_id, request.state.jwt_token)
# Get user info from request state (set by auth middleware)
user = request.state.user
@ -169,7 +168,7 @@ async def upload_bucket(request: Request, task_service, session_manager):
return JSONResponse({"error": "No files found in bucket"}, status_code=400)
user = request.state.user
jwt_token = request.state.jwt_token
jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token)
from models.processors import S3FileProcessor
from config.settings import is_no_auth_mode

View file

@ -321,7 +321,7 @@ class ConnectionManager:
return None
def get_available_connector_types(self) -> Dict[str, Dict[str, str]]:
def get_available_connector_types(self) -> Dict[str, Dict[str, Any]]:
"""Get available connector types with their metadata"""
return {
"google_drive": {

View file

@ -191,26 +191,8 @@ class SessionManager:
def get_user_opensearch_client(self, user_id: str, jwt_token: str):
"""Get or create OpenSearch client for user with their JWT"""
from config.settings import is_no_auth_mode
logger.debug(
"get_user_opensearch_client",
user_id=user_id,
jwt_token_present=(jwt_token is not None),
no_auth_mode=is_no_auth_mode(),
)
# In no-auth mode, create anonymous JWT for OpenSearch DLS
if jwt_token is None and (is_no_auth_mode() or user_id in (None, AnonymousUser().user_id)):
if not hasattr(self, "_anonymous_jwt"):
# Create anonymous JWT token for OpenSearch OIDC
logger.debug("Creating anonymous JWT")
self._anonymous_jwt = self._create_anonymous_jwt()
logger.debug(
"Anonymous JWT created", jwt_prefix=self._anonymous_jwt[:50]
)
jwt_token = self._anonymous_jwt
logger.debug("Using anonymous JWT for OpenSearch")
# Get the effective JWT token (handles anonymous JWT creation)
jwt_token = self.get_effective_jwt_token(user_id, jwt_token)
# Check if we have a cached client for this user
if user_id not in self.user_opensearch_clients:
@ -222,6 +204,31 @@ class SessionManager:
return self.user_opensearch_clients[user_id]
def get_effective_jwt_token(self, user_id: str, jwt_token: str) -> str:
"""Get the effective JWT token, creating anonymous JWT if needed in no-auth mode"""
from config.settings import is_no_auth_mode
logger.debug(
"get_effective_jwt_token",
user_id=user_id,
jwt_token_present=(jwt_token is not None),
no_auth_mode=is_no_auth_mode(),
)
# In no-auth mode, create anonymous JWT if needed
if jwt_token is None and (is_no_auth_mode() or user_id in (None, AnonymousUser().user_id)):
if not hasattr(self, "_anonymous_jwt"):
# Create anonymous JWT token for OpenSearch OIDC
logger.debug("Creating anonymous JWT")
self._anonymous_jwt = self._create_anonymous_jwt()
logger.debug(
"Anonymous JWT created", jwt_prefix=self._anonymous_jwt[:50]
)
jwt_token = self._anonymous_jwt
logger.debug("Using anonymous JWT")
return jwt_token
def _create_anonymous_jwt(self) -> str:
"""Create JWT token for anonymous user in no-auth mode"""
anonymous_user = AnonymousUser()

View file

@ -91,7 +91,7 @@ services:
langflow:
volumes:
- ./flows:/app/flows:Z
image: phact/langflow:${LANGFLOW_VERSION:-responses}
image: phact/openrag-langflow:${LANGFLOW_VERSION:-latest}
container_name: langflow
ports:
- "7860:7860"

View file

@ -91,7 +91,7 @@ services:
langflow:
volumes:
- ./flows:/app/flows:Z
image: phact/langflow:${LANGFLOW_VERSION:-responses}
image: phact/openrag-langflow:${LANGFLOW_VERSION:-latest}
container_name: langflow
ports:
- "7860:7860"