Compare commits

..

10 Commits

Author SHA1 Message Date
pablonyx
3a01014212 k 2025-02-27 18:04:19 -08:00
pablonyx
45b6c5bfed address comments 2025-02-27 15:37:53 -08:00
pablonyx
04e980a0e8 fix build 2025-02-27 15:25:58 -08:00
pablonyx
3c2480ef21 k 2025-02-27 15:23:51 -08:00
pablonyx
ebb57d6216 k 2025-02-27 15:23:51 -08:00
Richard Kuo (Danswer)
4c230f92ea trivy test 2025-02-27 15:05:03 -08:00
Richard Kuo (Danswer)
07d75b04d1 enable trivy scan 2025-02-27 14:22:44 -08:00
evan-danswer
a8d10750c1 fix propagation of is_agentic (#4150) 2025-02-27 11:56:51 -08:00
pablonyx
85e3ed57f1 Order chat sessions by time updated, not created (#4143)
* order chat sessions by time updated, not created

* quick update

* k
2025-02-27 17:35:42 +00:00
pablonyx
e10cc8ccdb Multi tenant user google auth fix (#4145) 2025-02-27 10:35:38 -08:00
17 changed files with 133 additions and 51 deletions

View File

@@ -53,22 +53,26 @@ jobs:
exclude: '(?i)^(pylint|aio[-_]*).*'
- name: Print report
if: ${{ always() }}
if: always()
run: echo "${{ steps.license_check_report.outputs.report }}"
- name: Install npm dependencies
working-directory: ./web
run: npm ci
# be careful enabling the sarif and upload as it may spam the security tab
# with a huge amount of items. Work out the issues before enabling upload.
- name: Run Trivy vulnerability scanner in repo mode
uses: aquasecurity/trivy-action@0.28.0
if: always()
uses: aquasecurity/trivy-action@0.29.0
with:
scan-type: fs
scan-ref: .
scanners: license
format: table
severity: HIGH,CRITICAL
# format: sarif
# output: trivy-results.sarif
severity: HIGH,CRITICAL
# - name: Upload Trivy scan results to GitHub Security tab
# uses: github/codeql-action/upload-sarif@v3

View File

@@ -138,6 +138,7 @@ def get_user_chat_sessions(
name=chat.description,
persona_id=chat.persona_id,
time_created=chat.time_created.isoformat(),
time_updated=chat.time_updated.isoformat(),
shared_status=chat.shared_status,
folder_id=chat.folder_id,
current_alternate_model=chat.current_alternate_model,

View File

@@ -168,7 +168,7 @@ def get_chat_sessions_by_user(
if not include_onyxbot_flows:
stmt = stmt.where(ChatSession.onyxbot_flow.is_(False))
stmt = stmt.order_by(desc(ChatSession.time_created))
stmt = stmt.order_by(desc(ChatSession.time_updated))
if deleted is not None:
stmt = stmt.where(ChatSession.deleted == deleted)
@@ -962,6 +962,7 @@ def translate_db_message_to_chat_message_detail(
chat_message.sub_questions
),
refined_answer_improvement=chat_message.refined_answer_improvement,
is_agentic=chat_message.is_agentic,
error=chat_message.error,
)

View File

@@ -49,6 +49,7 @@ def get_folders(
name=chat_session.description,
persona_id=chat_session.persona_id,
time_created=chat_session.time_created.isoformat(),
time_updated=chat_session.time_updated.isoformat(),
shared_status=chat_session.shared_status,
folder_id=folder.id,
)

View File

@@ -343,7 +343,8 @@ def list_bot_configs(
]
MAX_CHANNELS = 200
MAX_SLACK_PAGES = 5
SLACK_API_CHANNELS_PER_PAGE = 100
@router.get(
@@ -355,8 +356,8 @@ def get_all_channels_from_slack_api(
_: User | None = Depends(current_admin_user),
) -> list[SlackChannel]:
"""
Fetches all channels from the Slack API.
If the workspace has 200 or more channels, we raise an error.
Fetches channels the bot is a member of from the Slack API.
Handles pagination with a limit to avoid excessive API calls.
"""
tokens = fetch_slack_bot_tokens(db_session, bot_id)
if not tokens or "bot_token" not in tokens:
@@ -365,28 +366,60 @@ def get_all_channels_from_slack_api(
)
client = WebClient(token=tokens["bot_token"])
all_channels = []
next_cursor = None
current_page = 0
try:
response = client.conversations_list(
types="public_channel,private_channel",
exclude_archived=True,
limit=MAX_CHANNELS,
)
# Use users_conversations with limited pagination
while current_page < MAX_SLACK_PAGES:
current_page += 1
# Make API call with cursor if we have one
if next_cursor:
response = client.users_conversations(
types="public_channel,private_channel",
exclude_archived=True,
cursor=next_cursor,
limit=SLACK_API_CHANNELS_PER_PAGE,
)
else:
response = client.users_conversations(
types="public_channel,private_channel",
exclude_archived=True,
limit=SLACK_API_CHANNELS_PER_PAGE,
)
# Add channels to our list
if "channels" in response and response["channels"]:
all_channels.extend(response["channels"])
# Check if we need to paginate
if (
"response_metadata" in response
and "next_cursor" in response["response_metadata"]
):
next_cursor = response["response_metadata"]["next_cursor"]
if next_cursor:
if current_page == MAX_SLACK_PAGES:
raise HTTPException(
status_code=400,
detail="Workspace has too many channels to paginate over in this call.",
)
continue
# If we get here, no more pages
break
channels = [
SlackChannel(id=channel["id"], name=channel["name"])
for channel in response["channels"]
for channel in all_channels
]
if len(channels) == MAX_CHANNELS:
raise HTTPException(
status_code=400,
detail=f"Workspace has {MAX_CHANNELS} or more channels.",
)
return channels
except SlackApiError as e:
# Handle rate limiting or other API errors
raise HTTPException(
status_code=500,
detail=f"Error fetching channels from Slack API: {str(e)}",

View File

@@ -147,9 +147,11 @@ def list_threads(
name=chat.description,
persona_id=chat.persona_id,
time_created=chat.time_created.isoformat(),
time_updated=chat.time_updated.isoformat(),
shared_status=chat.shared_status,
folder_id=chat.folder_id,
current_alternate_model=chat.current_alternate_model,
current_temperature_override=chat.temperature_override,
)
for chat in chat_sessions
]

View File

@@ -119,6 +119,7 @@ def get_user_chat_sessions(
name=chat.description,
persona_id=chat.persona_id,
time_created=chat.time_created.isoformat(),
time_updated=chat.time_updated.isoformat(),
shared_status=chat.shared_status,
folder_id=chat.folder_id,
current_alternate_model=chat.current_alternate_model,

View File

@@ -181,6 +181,7 @@ class ChatSessionDetails(BaseModel):
name: str | None
persona_id: int | None = None
time_created: str
time_updated: str
shared_status: ChatSessionSharedStatus
folder_id: int | None = None
current_alternate_model: str | None = None
@@ -241,6 +242,7 @@ class ChatMessageDetail(BaseModel):
files: list[FileDescriptor]
tool_call: ToolCallFinalResult | None
refined_answer_improvement: bool | None = None
is_agentic: bool | None = None
error: str | None = None
def model_dump(self, *args: list, **kwargs: dict[str, Any]) -> dict[str, Any]: # type: ignore

View File

@@ -159,6 +159,7 @@ def get_user_search_sessions(
name=sessions_with_documents_dict[search.id],
persona_id=search.persona_id,
time_created=search.time_created.isoformat(),
time_updated=search.time_updated.isoformat(),
shared_status=search.shared_status,
folder_id=search.folder_id,
current_alternate_model=search.current_alternate_model,

View File

@@ -199,17 +199,17 @@ export function SlackChannelConfigFormFields({
<Badge variant="agent" className="bg-blue-100 text-blue-800">
Default Configuration
</Badge>
<p className="mt-2 text-sm text-gray-600">
<p className="mt-2 text-sm text-neutral-600">
This default configuration will apply across all Slack channels
the bot is added to in the Slack workspace, as well as direct
messages (DMs), unless disabled.
</p>
<div className="mt-4 p-4 bg-gray-100 rounded-md border border-gray-300">
<div className="mt-4 p-4 bg-neutral-100 rounded-md border border-neutral-300">
<CheckFormField
name="disabled"
label="Disable Default Configuration"
/>
<p className="mt-2 text-sm text-gray-600 italic">
<p className="mt-2 text-sm text-neutral-600 italic">
Warning: Disabling the default configuration means the bot
won&apos;t respond in Slack channels or DMs unless explicitly
configured for them.
@@ -238,20 +238,28 @@ export function SlackChannelConfigFormFields({
/>
</div>
) : (
<Field name="channel_name">
{({ field, form }: { field: any; form: any }) => (
<SearchMultiSelectDropdown
options={channelOptions || []}
onSelect={(selected) => {
form.setFieldValue("channel_name", selected.name);
}}
initialSearchTerm={field.value}
onSearchTermChange={(term) => {
form.setFieldValue("channel_name", term);
}}
/>
)}
</Field>
<>
<Field name="channel_name">
{({ field, form }: { field: any; form: any }) => (
<SearchMultiSelectDropdown
options={channelOptions || []}
onSelect={(selected) => {
form.setFieldValue("channel_name", selected.name);
}}
initialSearchTerm={field.value}
onSearchTermChange={(term) => {
form.setFieldValue("channel_name", term);
}}
/>
)}
</Field>
<p className="mt-2 text-sm dark:text-neutral-400 text-neutral-600">
Note: This list shows public and private channels where the
bot is a member (up to 500 channels). If you don&apos;t see a
channel, make sure the bot is added to that channel in Slack
first, or type the channel name manually.
</p>
</>
)}
</>
)}

View File

@@ -0,0 +1,16 @@
export default function AuthErrorLayout({
children,
}: {
children: React.ReactNode;
}) {
// Log error to console for debugging
console.error(
"Authentication error page was accessed - this should not happen in normal flow"
);
// In a production environment, you might want to send this to your error tracking service
// For example, if using a service like Sentry:
// captureException(new Error("Authentication error page was accessed unexpectedly"));
return <>{children}</>;
}

View File

@@ -4,6 +4,7 @@ import AuthFlowContainer from "@/components/auth/AuthFlowContainer";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { FiLogIn } from "react-icons/fi";
import { NEXT_PUBLIC_CLOUD_ENABLED } from "@/lib/constants";
const Page = () => {
return (
@@ -15,19 +16,21 @@ const Page = () => {
<p className="text-text-700 text-center">
We encountered an issue while attempting to log you in.
</p>
<div className="bg-red-50 border border-red-200 rounded-lg p-4 shadow-sm">
<h3 className="text-red-800 font-semibold mb-2">Possible Issues:</h3>
<div className="bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800 rounded-lg p-4 shadow-sm">
<h3 className="text-red-800 dark:text-red-400 font-semibold mb-2">
Possible Issues:
</h3>
<ul className="space-y-2">
<li className="flex items-center text-red-700">
<div className="w-2 h-2 bg-red-500 rounded-full mr-2"></div>
<li className="flex items-center text-red-700 dark:text-red-400">
<div className="w-2 h-2 bg-red-500 dark:bg-red-400 rounded-full mr-2"></div>
Incorrect or expired login credentials
</li>
<li className="flex items-center text-red-700">
<div className="w-2 h-2 bg-red-500 rounded-full mr-2"></div>
<li className="flex items-center text-red-700 dark:text-red-400">
<div className="w-2 h-2 bg-red-500 dark:bg-red-400 rounded-full mr-2"></div>
Temporary authentication system disruption
</li>
<li className="flex items-center text-red-700">
<div className="w-2 h-2 bg-red-500 rounded-full mr-2"></div>
<li className="flex items-center text-red-700 dark:text-red-400">
<div className="w-2 h-2 bg-red-500 dark:bg-red-400 rounded-full mr-2"></div>
Account access restrictions or permissions
</li>
</ul>
@@ -41,6 +44,12 @@ const Page = () => {
<p className="text-sm text-text-500 text-center">
We recommend trying again. If you continue to experience problems,
please reach out to your system administrator for assistance.
{NEXT_PUBLIC_CLOUD_ENABLED && (
<span className="block mt-1 text-blue-600">
A member of our team has been automatically notified about this
issue.
</span>
)}
</p>
</div>
</AuthFlowContainer>

View File

@@ -168,7 +168,7 @@ const FolderItem = ({
};
const folders = folder.chat_sessions.sort((a, b) => {
return a.time_created.localeCompare(b.time_created);
return a.time_updated.localeCompare(b.time_updated);
});
// Determine whether to show the trash can icon

View File

@@ -70,6 +70,7 @@ export interface ChatSession {
name: string;
persona_id: number;
time_created: string;
time_updated: string;
shared_status: ChatSessionSharedStatus;
folder_id: number | null;
current_alternate_model: string;
@@ -123,6 +124,7 @@ export interface BackendChatSession {
persona_icon_shape: number | null;
messages: BackendMessage[];
time_created: string;
time_updated: string;
shared_status: ChatSessionSharedStatus;
current_temperature_override: number | null;
current_alternate_model?: string;

View File

@@ -48,10 +48,10 @@ export function getChatRetentionInfo(
): ChatRetentionInfo {
// If `maximum_chat_retention_days` isn't set- never display retention warning.
const chatRetentionDays = settings.maximum_chat_retention_days || 10000;
const createdDate = new Date(chatSession.time_created);
const updatedDate = new Date(chatSession.time_updated);
const today = new Date();
const daysFromCreation = Math.ceil(
(today.getTime() - createdDate.getTime()) / (1000 * 3600 * 24)
(today.getTime() - updatedDate.getTime()) / (1000 * 3600 * 24)
);
const daysUntilExpiration = chatRetentionDays - daysFromCreation;
const showRetentionWarning =
@@ -419,7 +419,7 @@ export function groupSessionsByDateRange(chatSessions: ChatSession[]) {
};
chatSessions.forEach((chatSession) => {
const chatSessionDate = new Date(chatSession.time_created);
const chatSessionDate = new Date(chatSession.time_updated);
const diffTime = today.getTime() - chatSessionDate.getTime();
const diffDays = diffTime / (1000 * 3600 * 24); // Convert time difference to days
@@ -501,6 +501,7 @@ export function processRawChatHistory(
sub_questions: subQuestions,
isImprovement:
(messageInfo.refined_answer_improvement as unknown as boolean) || false,
is_agentic: messageInfo.is_agentic,
};
messages.set(messageInfo.message_id, message);

View File

@@ -206,7 +206,7 @@ export function SharedChatDisplay({
{chatSession.description || `Unnamed Chat`}
</h1>
<p className=" text-text-darker">
{humanReadableFormat(chatSession.time_created)}
{humanReadableFormat(chatSession.time_updated)}
</p>
<div
className={`

View File

@@ -148,7 +148,7 @@ export async function fetchChatData(searchParams: {
chatSessions.sort(
(a, b) =>
new Date(b.time_created).getTime() - new Date(a.time_created).getTime()
new Date(b.time_updated).getTime() - new Date(a.time_updated).getTime()
);
let documentSets: DocumentSet[] = [];