mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-20 13:05:49 +02:00
Update e2e frontend tests (#3843)
* fix input prompts
* assistant ordering validation
* k
* Revert "fix input prompts"
This reverts commit a4b577bdd7
.
* fix alembic
* foreign key updates
* Revert "foreign key updates"
This reverts commit fe17795a037f831790d69229e1067ccb5aab5bd9.
* improve e2e tests
* fix admin
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
"""remove recent assistants
|
||||||
|
|
||||||
|
Revision ID: a6df6b88ef81
|
||||||
|
Revises: 4d58345da04a
|
||||||
|
Create Date: 2025-01-29 10:25:52.790407
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "a6df6b88ef81"
|
||||||
|
down_revision = "4d58345da04a"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.drop_column("user", "recent_assistants")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.add_column(
|
||||||
|
"user",
|
||||||
|
sa.Column(
|
||||||
|
"recent_assistants", postgresql.JSONB(), server_default="[]", nullable=False
|
||||||
|
),
|
||||||
|
)
|
@@ -161,9 +161,7 @@ class User(SQLAlchemyBaseUserTableUUID, Base):
|
|||||||
hidden_assistants: Mapped[list[int]] = mapped_column(
|
hidden_assistants: Mapped[list[int]] = mapped_column(
|
||||||
postgresql.JSONB(), nullable=False, default=[]
|
postgresql.JSONB(), nullable=False, default=[]
|
||||||
)
|
)
|
||||||
recent_assistants: Mapped[list[dict]] = mapped_column(
|
|
||||||
postgresql.JSONB(), nullable=False, default=list, server_default="[]"
|
|
||||||
)
|
|
||||||
pinned_assistants: Mapped[list[int] | None] = mapped_column(
|
pinned_assistants: Mapped[list[int] | None] = mapped_column(
|
||||||
postgresql.JSONB(), nullable=True, default=None
|
postgresql.JSONB(), nullable=True, default=None
|
||||||
)
|
)
|
||||||
|
@@ -44,7 +44,6 @@ class UserPreferences(BaseModel):
|
|||||||
chosen_assistants: list[int] | None = None
|
chosen_assistants: list[int] | None = None
|
||||||
hidden_assistants: list[int] = []
|
hidden_assistants: list[int] = []
|
||||||
visible_assistants: list[int] = []
|
visible_assistants: list[int] = []
|
||||||
recent_assistants: list[int] | None = None
|
|
||||||
default_model: str | None = None
|
default_model: str | None = None
|
||||||
auto_scroll: bool | None = None
|
auto_scroll: bool | None = None
|
||||||
pinned_assistants: list[int] | None = None
|
pinned_assistants: list[int] | None = None
|
||||||
|
@@ -572,59 +572,6 @@ class ChosenDefaultModelRequest(BaseModel):
|
|||||||
default_model: str | None = None
|
default_model: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class RecentAssistantsRequest(BaseModel):
|
|
||||||
current_assistant: int
|
|
||||||
|
|
||||||
|
|
||||||
def update_recent_assistants(
|
|
||||||
recent_assistants: list[int] | None, current_assistant: int
|
|
||||||
) -> list[int]:
|
|
||||||
if recent_assistants is None:
|
|
||||||
recent_assistants = []
|
|
||||||
else:
|
|
||||||
recent_assistants = [x for x in recent_assistants if x != current_assistant]
|
|
||||||
|
|
||||||
# Add current assistant to start of list
|
|
||||||
recent_assistants.insert(0, current_assistant)
|
|
||||||
|
|
||||||
# Keep only the 5 most recent assistants
|
|
||||||
recent_assistants = recent_assistants[:5]
|
|
||||||
return recent_assistants
|
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/user/recent-assistants")
|
|
||||||
def update_user_recent_assistants(
|
|
||||||
request: RecentAssistantsRequest,
|
|
||||||
user: User | None = Depends(current_user),
|
|
||||||
db_session: Session = Depends(get_session),
|
|
||||||
) -> None:
|
|
||||||
if user is None:
|
|
||||||
if AUTH_TYPE == AuthType.DISABLED:
|
|
||||||
store = get_kv_store()
|
|
||||||
no_auth_user = fetch_no_auth_user(store)
|
|
||||||
preferences = no_auth_user.preferences
|
|
||||||
recent_assistants = preferences.recent_assistants
|
|
||||||
updated_preferences = update_recent_assistants(
|
|
||||||
recent_assistants, request.current_assistant
|
|
||||||
)
|
|
||||||
preferences.recent_assistants = updated_preferences
|
|
||||||
set_no_auth_user_preferences(store, preferences)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise RuntimeError("This should never happen")
|
|
||||||
|
|
||||||
recent_assistants = UserInfo.from_model(user).preferences.recent_assistants
|
|
||||||
updated_recent_assistants = update_recent_assistants(
|
|
||||||
recent_assistants, request.current_assistant
|
|
||||||
)
|
|
||||||
db_session.execute(
|
|
||||||
update(User)
|
|
||||||
.where(User.id == user.id) # type: ignore
|
|
||||||
.values(recent_assistants=updated_recent_assistants)
|
|
||||||
)
|
|
||||||
db_session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/shortcut-enabled")
|
@router.patch("/shortcut-enabled")
|
||||||
def update_user_shortcut_enabled(
|
def update_user_shortcut_enabled(
|
||||||
shortcut_enabled: bool,
|
shortcut_enabled: bool,
|
||||||
@@ -731,30 +678,6 @@ class ChosenAssistantsRequest(BaseModel):
|
|||||||
chosen_assistants: list[int]
|
chosen_assistants: list[int]
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/user/assistant-list")
|
|
||||||
def update_user_assistant_list(
|
|
||||||
request: ChosenAssistantsRequest,
|
|
||||||
user: User | None = Depends(current_user),
|
|
||||||
db_session: Session = Depends(get_session),
|
|
||||||
) -> None:
|
|
||||||
if user is None:
|
|
||||||
if AUTH_TYPE == AuthType.DISABLED:
|
|
||||||
store = get_kv_store()
|
|
||||||
no_auth_user = fetch_no_auth_user(store)
|
|
||||||
no_auth_user.preferences.chosen_assistants = request.chosen_assistants
|
|
||||||
set_no_auth_user_preferences(store, no_auth_user.preferences)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise RuntimeError("This should never happen")
|
|
||||||
|
|
||||||
db_session.execute(
|
|
||||||
update(User)
|
|
||||||
.where(User.id == user.id) # type: ignore
|
|
||||||
.values(chosen_assistants=request.chosen_assistants)
|
|
||||||
)
|
|
||||||
db_session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def update_assistant_visibility(
|
def update_assistant_visibility(
|
||||||
preferences: UserPreferences, assistant_id: int, show: bool
|
preferences: UserPreferences, assistant_id: int, show: bool
|
||||||
) -> UserPreferences:
|
) -> UserPreferences:
|
||||||
|
@@ -1,41 +1,16 @@
|
|||||||
import { defineConfig, devices } from "@playwright/test";
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
workers: 1, // temporary change to see if single threaded testing stabilizes the tests
|
globalSetup: require.resolve("./tests/e2e/global-setup"),
|
||||||
testDir: "./tests/e2e", // Folder for test files
|
|
||||||
reporter: "list",
|
|
||||||
// Configure paths for screenshots
|
|
||||||
// expect: {
|
|
||||||
// toMatchSnapshot: {
|
|
||||||
// threshold: 0.2, // Adjust the threshold for visual diffs
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// reporter: [["html", { outputFolder: "test-results/output/report" }]], // HTML report location
|
|
||||||
// outputDir: "test-results/output/screenshots", // Set output folder for test artifacts
|
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
// dependency for admin workflows
|
name: "admin",
|
||||||
name: "admin_setup",
|
|
||||||
testMatch: /.*\admin_auth\.setup\.ts/,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// tests admin workflows
|
|
||||||
name: "chromium-admin",
|
|
||||||
grep: /@admin/,
|
|
||||||
use: {
|
use: {
|
||||||
...devices["Desktop Chrome"],
|
...devices["Desktop Chrome"],
|
||||||
// Use prepared auth state.
|
|
||||||
storageState: "admin_auth.json",
|
storageState: "admin_auth.json",
|
||||||
},
|
},
|
||||||
dependencies: ["admin_setup"],
|
testIgnore: ["**/codeUtils.test.ts"],
|
||||||
},
|
|
||||||
{
|
|
||||||
// tests logged out / guest workflows
|
|
||||||
name: "chromium-guest",
|
|
||||||
grep: /@guest/,
|
|
||||||
use: {
|
|
||||||
...devices["Desktop Chrome"],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@@ -111,6 +111,7 @@ import {
|
|||||||
import AssistantModal from "../assistants/mine/AssistantModal";
|
import AssistantModal from "../assistants/mine/AssistantModal";
|
||||||
import { getSourceMetadata } from "@/lib/sources";
|
import { getSourceMetadata } from "@/lib/sources";
|
||||||
import { UserSettingsModal } from "./modal/UserSettingsModal";
|
import { UserSettingsModal } from "./modal/UserSettingsModal";
|
||||||
|
import { AlignStartVertical } from "lucide-react";
|
||||||
|
|
||||||
const TEMP_USER_MESSAGE_ID = -1;
|
const TEMP_USER_MESSAGE_ID = -1;
|
||||||
const TEMP_ASSISTANT_MESSAGE_ID = -2;
|
const TEMP_ASSISTANT_MESSAGE_ID = -2;
|
||||||
@@ -189,7 +190,11 @@ export function ChatPage({
|
|||||||
|
|
||||||
const [userSettingsToggled, setUserSettingsToggled] = useState(false);
|
const [userSettingsToggled, setUserSettingsToggled] = useState(false);
|
||||||
|
|
||||||
const { assistants: availableAssistants, finalAssistants } = useAssistants();
|
const {
|
||||||
|
assistants: availableAssistants,
|
||||||
|
finalAssistants,
|
||||||
|
pinnedAssistants,
|
||||||
|
} = useAssistants();
|
||||||
|
|
||||||
const [showApiKeyModal, setShowApiKeyModal] = useState(
|
const [showApiKeyModal, setShowApiKeyModal] = useState(
|
||||||
!shouldShowWelcomeModal
|
!shouldShowWelcomeModal
|
||||||
@@ -272,16 +277,6 @@ export function ChatPage({
|
|||||||
SEARCH_PARAM_NAMES.TEMPERATURE
|
SEARCH_PARAM_NAMES.TEMPERATURE
|
||||||
);
|
);
|
||||||
|
|
||||||
const defaultTemperature = search_param_temperature
|
|
||||||
? parseFloat(search_param_temperature)
|
|
||||||
: selectedAssistant?.tools.some(
|
|
||||||
(tool) =>
|
|
||||||
tool.in_code_tool_id === SEARCH_TOOL_ID ||
|
|
||||||
tool.in_code_tool_id === INTERNET_SEARCH_TOOL_ID
|
|
||||||
)
|
|
||||||
? 0
|
|
||||||
: 0.7;
|
|
||||||
|
|
||||||
const setSelectedAssistantFromId = (assistantId: number) => {
|
const setSelectedAssistantFromId = (assistantId: number) => {
|
||||||
// NOTE: also intentionally look through available assistants here, so that
|
// NOTE: also intentionally look through available assistants here, so that
|
||||||
// even if the user has hidden an assistant they can still go back to it
|
// even if the user has hidden an assistant they can still go back to it
|
||||||
@@ -297,20 +292,21 @@ export function ChatPage({
|
|||||||
const [presentingDocument, setPresentingDocument] =
|
const [presentingDocument, setPresentingDocument] =
|
||||||
useState<OnyxDocument | null>(null);
|
useState<OnyxDocument | null>(null);
|
||||||
|
|
||||||
const { recentAssistants, refreshRecentAssistants } = useAssistants();
|
// Current assistant is decided based on this ordering
|
||||||
|
// 1. Alternative assistant (assistant selected explicitly by user)
|
||||||
|
// 2. Selected assistant (assistnat default in this chat session)
|
||||||
|
// 3. First pinned assistants (ordered list of pinned assistants)
|
||||||
|
// 4. Available assistants (ordered list of available assistants)
|
||||||
const liveAssistant: Persona | undefined = useMemo(
|
const liveAssistant: Persona | undefined = useMemo(
|
||||||
() =>
|
() =>
|
||||||
alternativeAssistant ||
|
alternativeAssistant ||
|
||||||
selectedAssistant ||
|
selectedAssistant ||
|
||||||
recentAssistants[0] ||
|
pinnedAssistants[0] ||
|
||||||
finalAssistants[0] ||
|
|
||||||
availableAssistants[0],
|
availableAssistants[0],
|
||||||
[
|
[
|
||||||
alternativeAssistant,
|
alternativeAssistant,
|
||||||
selectedAssistant,
|
selectedAssistant,
|
||||||
recentAssistants,
|
pinnedAssistants,
|
||||||
finalAssistants,
|
|
||||||
availableAssistants,
|
availableAssistants,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -816,7 +812,6 @@ export function ChatPage({
|
|||||||
setMaxTokens(maxTokens);
|
setMaxTokens(maxTokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refreshRecentAssistants(liveAssistant?.id);
|
|
||||||
fetchMaxTokens();
|
fetchMaxTokens();
|
||||||
}, [liveAssistant]);
|
}, [liveAssistant]);
|
||||||
|
|
||||||
|
@@ -19,9 +19,7 @@ import {
|
|||||||
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { ChatSession } from "../interfaces";
|
import { ChatSession } from "../interfaces";
|
||||||
import { NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA } from "@/lib/constants";
|
|
||||||
import { Folder } from "../folders/interfaces";
|
import { Folder } from "../folders/interfaces";
|
||||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
|
||||||
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
||||||
|
|
||||||
import { DocumentIcon2, NewChatIcon } from "@/components/icons/icons";
|
import { DocumentIcon2, NewChatIcon } from "@/components/icons/icons";
|
||||||
@@ -251,9 +249,11 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
|||||||
|
|
||||||
const handleNewChat = () => {
|
const handleNewChat = () => {
|
||||||
reset();
|
reset();
|
||||||
|
console.log("currentChatSession", currentChatSession);
|
||||||
|
|
||||||
const newChatUrl =
|
const newChatUrl =
|
||||||
`/${page}` +
|
`/${page}` +
|
||||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA && currentChatSession
|
(currentChatSession
|
||||||
? `?assistantId=${currentChatSession.persona_id}`
|
? `?assistantId=${currentChatSession.persona_id}`
|
||||||
: "");
|
: "");
|
||||||
router.push(newChatUrl);
|
router.push(newChatUrl);
|
||||||
@@ -294,8 +294,7 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
|||||||
className="w-full px-2 py-1 rounded-md items-center hover:bg-hover cursor-pointer transition-all duration-150 flex gap-x-2"
|
className="w-full px-2 py-1 rounded-md items-center hover:bg-hover cursor-pointer transition-all duration-150 flex gap-x-2"
|
||||||
href={
|
href={
|
||||||
`/${page}` +
|
`/${page}` +
|
||||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA &&
|
(currentChatSession
|
||||||
currentChatSession?.persona_id
|
|
||||||
? `?assistantId=${currentChatSession?.persona_id}`
|
? `?assistantId=${currentChatSession?.persona_id}`
|
||||||
: "")
|
: "")
|
||||||
}
|
}
|
||||||
@@ -320,14 +319,6 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
|||||||
<Link
|
<Link
|
||||||
className="w-full px-2 py-1 rounded-md items-center hover:bg-hover cursor-pointer transition-all duration-150 flex gap-x-2"
|
className="w-full px-2 py-1 rounded-md items-center hover:bg-hover cursor-pointer transition-all duration-150 flex gap-x-2"
|
||||||
href="/chat/input-prompts"
|
href="/chat/input-prompts"
|
||||||
onClick={(e) => {
|
|
||||||
if (e.metaKey || e.ctrlKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (handleNewChat) {
|
|
||||||
handleNewChat();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<DocumentIcon2
|
<DocumentIcon2
|
||||||
size={20}
|
size={20}
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
import { UserDropdown } from "../UserDropdown";
|
import { UserDropdown } from "../UserDropdown";
|
||||||
import { FiShare2 } from "react-icons/fi";
|
import { FiShare2 } from "react-icons/fi";
|
||||||
import { SetStateAction, useContext, useEffect } from "react";
|
import { SetStateAction, useContext, useEffect } from "react";
|
||||||
import { NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA } from "@/lib/constants";
|
|
||||||
import { ChatSession } from "@/app/chat/interfaces";
|
import { ChatSession } from "@/app/chat/interfaces";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { pageType } from "@/app/chat/sessionSidebar/types";
|
import { pageType } from "@/app/chat/sessionSidebar/types";
|
||||||
@@ -42,8 +41,7 @@ export default function FunctionalHeader({
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
window.open(
|
window.open(
|
||||||
`/${page}` +
|
`/${page}` +
|
||||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA &&
|
(currentChatSession
|
||||||
currentChatSession
|
|
||||||
? `?assistantId=${currentChatSession.persona_id}`
|
? `?assistantId=${currentChatSession.persona_id}`
|
||||||
: ""),
|
: ""),
|
||||||
"_self"
|
"_self"
|
||||||
@@ -63,7 +61,7 @@ export default function FunctionalHeader({
|
|||||||
reset();
|
reset();
|
||||||
const newChatUrl =
|
const newChatUrl =
|
||||||
`/${page}` +
|
`/${page}` +
|
||||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA && currentChatSession
|
(currentChatSession
|
||||||
? `?assistantId=${currentChatSession.persona_id}`
|
? `?assistantId=${currentChatSession.persona_id}`
|
||||||
: "");
|
: "");
|
||||||
router.push(newChatUrl);
|
router.push(newChatUrl);
|
||||||
@@ -128,25 +126,6 @@ export default function FunctionalHeader({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* <div
|
|
||||||
className={`absolute
|
|
||||||
${
|
|
||||||
documentSidebarToggled && !sidebarToggled
|
|
||||||
? "left-[calc(50%-125px)]"
|
|
||||||
: !documentSidebarToggled && sidebarToggled
|
|
||||||
? "left-[calc(50%+125px)]"
|
|
||||||
: "left-1/2"
|
|
||||||
}
|
|
||||||
${
|
|
||||||
documentSidebarToggled || sidebarToggled
|
|
||||||
? "mobile:w-[40vw] max-w-[50vw]"
|
|
||||||
: "mobile:w-[50vw] max-w-[60vw]"
|
|
||||||
}
|
|
||||||
top-1/2 transform -translate-x-1/2 -translate-y-1/2 transition-all duration-300`}
|
|
||||||
>
|
|
||||||
<ChatBanner />
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
<div className="invisible">
|
<div className="invisible">
|
||||||
<LogoWithText
|
<LogoWithText
|
||||||
page={page}
|
page={page}
|
||||||
@@ -156,8 +135,6 @@ export default function FunctionalHeader({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* className="fixed cursor-pointer flex z-40 left-4 bg-black top-3 h-8" */}
|
|
||||||
|
|
||||||
<div className="absolute right-2 mobile:top-1 desktop:top-1 h-8 flex">
|
<div className="absolute right-2 mobile:top-1 desktop:top-1 h-8 flex">
|
||||||
{setSharingModalVisible && !hideUserDropdown && (
|
{setSharingModalVisible && !hideUserDropdown && (
|
||||||
<div
|
<div
|
||||||
@@ -179,8 +156,7 @@ export default function FunctionalHeader({
|
|||||||
className="desktop:hidden ml-2 my-auto"
|
className="desktop:hidden ml-2 my-auto"
|
||||||
href={
|
href={
|
||||||
`/${page}` +
|
`/${page}` +
|
||||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA &&
|
(currentChatSession
|
||||||
currentChatSession
|
|
||||||
? `?assistantId=${currentChatSession.persona_id}`
|
? `?assistantId=${currentChatSession.persona_id}`
|
||||||
: "")
|
: "")
|
||||||
}
|
}
|
||||||
|
@@ -25,8 +25,6 @@ interface AssistantsContextProps {
|
|||||||
ownedButHiddenAssistants: Persona[];
|
ownedButHiddenAssistants: Persona[];
|
||||||
refreshAssistants: () => Promise<void>;
|
refreshAssistants: () => Promise<void>;
|
||||||
isImageGenerationAvailable: boolean;
|
isImageGenerationAvailable: boolean;
|
||||||
recentAssistants: Persona[];
|
|
||||||
refreshRecentAssistants: (currentAssistant: number) => Promise<void>;
|
|
||||||
// Admin only
|
// Admin only
|
||||||
editablePersonas: Persona[];
|
editablePersonas: Persona[];
|
||||||
allAssistants: Persona[];
|
allAssistants: Persona[];
|
||||||
@@ -56,35 +54,28 @@ export const AssistantsProvider: React.FC<{
|
|||||||
const [editablePersonas, setEditablePersonas] = useState<Persona[]>([]);
|
const [editablePersonas, setEditablePersonas] = useState<Persona[]>([]);
|
||||||
const [allAssistants, setAllAssistants] = useState<Persona[]>([]);
|
const [allAssistants, setAllAssistants] = useState<Persona[]>([]);
|
||||||
|
|
||||||
const [pinnedAssistants, setPinnedAssistants] = useState<Persona[]>(
|
const [pinnedAssistants, setPinnedAssistants] = useState<Persona[]>(() => {
|
||||||
user?.preferences.pinned_assistants
|
if (user?.preferences.pinned_assistants) {
|
||||||
? assistants.filter((assistant) =>
|
return user.preferences.pinned_assistants
|
||||||
user?.preferences?.pinned_assistants?.includes(assistant.id)
|
.map((id) => assistants.find((assistant) => assistant.id === id))
|
||||||
)
|
.filter((assistant): assistant is Persona => assistant !== undefined);
|
||||||
: assistants.filter((a) => a.builtin_persona)
|
} else {
|
||||||
);
|
return assistants.filter((a) => a.builtin_persona);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPinnedAssistants(
|
setPinnedAssistants(() => {
|
||||||
user?.preferences.pinned_assistants
|
if (user?.preferences.pinned_assistants) {
|
||||||
? assistants.filter((assistant) =>
|
return user.preferences.pinned_assistants
|
||||||
user?.preferences?.pinned_assistants?.includes(assistant.id)
|
.map((id) => assistants.find((assistant) => assistant.id === id))
|
||||||
)
|
.filter((assistant): assistant is Persona => assistant !== undefined);
|
||||||
: assistants.filter((a) => a.builtin_persona)
|
} else {
|
||||||
);
|
return assistants.filter((a) => a.builtin_persona);
|
||||||
|
}
|
||||||
|
});
|
||||||
}, [user?.preferences?.pinned_assistants, assistants]);
|
}, [user?.preferences?.pinned_assistants, assistants]);
|
||||||
|
|
||||||
const [recentAssistants, setRecentAssistants] = useState<Persona[]>(
|
|
||||||
user?.preferences.recent_assistants
|
|
||||||
?.filter((assistantId) =>
|
|
||||||
assistants.find((assistant) => assistant.id === assistantId)
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
(assistantId) =>
|
|
||||||
assistants.find((assistant) => assistant.id === assistantId)!
|
|
||||||
) || []
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isImageGenerationAvailable, setIsImageGenerationAvailable] =
|
const [isImageGenerationAvailable, setIsImageGenerationAvailable] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
|
|
||||||
@@ -135,28 +126,6 @@ export const AssistantsProvider: React.FC<{
|
|||||||
fetchPersonas();
|
fetchPersonas();
|
||||||
}, [isAdmin, isCurator]);
|
}, [isAdmin, isCurator]);
|
||||||
|
|
||||||
const refreshRecentAssistants = async (currentAssistant: number) => {
|
|
||||||
const response = await fetch("/api/user/recent-assistants", {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
current_assistant: currentAssistant,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setRecentAssistants((recentAssistants) => [
|
|
||||||
assistants.find((assistant) => assistant.id === currentAssistant)!,
|
|
||||||
|
|
||||||
...recentAssistants.filter(
|
|
||||||
(assistant) => assistant.id !== currentAssistant
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshAssistants = async () => {
|
const refreshAssistants = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/persona", {
|
const response = await fetch("/api/persona", {
|
||||||
@@ -181,13 +150,6 @@ export const AssistantsProvider: React.FC<{
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error refreshing assistants:", error);
|
console.error("Error refreshing assistants:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
setRecentAssistants(
|
|
||||||
assistants.filter(
|
|
||||||
(assistant) =>
|
|
||||||
user?.preferences.recent_assistants?.includes(assistant.id) || false
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -230,8 +192,6 @@ export const AssistantsProvider: React.FC<{
|
|||||||
editablePersonas,
|
editablePersonas,
|
||||||
allAssistants,
|
allAssistants,
|
||||||
isImageGenerationAvailable,
|
isImageGenerationAvailable,
|
||||||
recentAssistants,
|
|
||||||
refreshRecentAssistants,
|
|
||||||
setPinnedAssistants,
|
setPinnedAssistants,
|
||||||
pinnedAssistants,
|
pinnedAssistants,
|
||||||
}}
|
}}
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { FiSidebar } from "react-icons/fi";
|
import { FiSidebar } from "react-icons/fi";
|
||||||
import { SettingsContext } from "../settings/SettingsProvider";
|
import { SettingsContext } from "../settings/SettingsProvider";
|
||||||
import { NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA } from "@/lib/constants";
|
|
||||||
import { LeftToLineIcon, NewChatIcon, RightToLineIcon } from "../icons/icons";
|
import { LeftToLineIcon, NewChatIcon, RightToLineIcon } from "../icons/icons";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -90,9 +89,7 @@ export default function LogoWithText({
|
|||||||
className="my-auto mobile:hidden"
|
className="my-auto mobile:hidden"
|
||||||
href={
|
href={
|
||||||
`/${page}` +
|
`/${page}` +
|
||||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA && assistantId
|
(assistantId ? `?assistantId=${assistantId}` : "")
|
||||||
? `?assistantId=${assistantId}`
|
|
||||||
: "")
|
|
||||||
}
|
}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (e.metaKey || e.ctrlKey) {
|
if (e.metaKey || e.ctrlKey) {
|
||||||
|
@@ -18,10 +18,6 @@ export const NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED =
|
|||||||
process.env.NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED?.toLowerCase() ===
|
process.env.NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED?.toLowerCase() ===
|
||||||
"true";
|
"true";
|
||||||
|
|
||||||
export const NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA =
|
|
||||||
process.env.NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA?.toLowerCase() ===
|
|
||||||
"true";
|
|
||||||
|
|
||||||
export const GMAIL_AUTH_IS_ADMIN_COOKIE_NAME = "gmail_auth_is_admin";
|
export const GMAIL_AUTH_IS_ADMIN_COOKIE_NAME = "gmail_auth_is_admin";
|
||||||
|
|
||||||
export const GOOGLE_DRIVE_AUTH_IS_ADMIN_COOKIE_NAME =
|
export const GOOGLE_DRIVE_AUTH_IS_ADMIN_COOKIE_NAME =
|
||||||
|
@@ -1,24 +1,9 @@
|
|||||||
// dependency for all admin user tests
|
// dependency for all admin user tests
|
||||||
|
import { test as setup } from "@playwright/test";
|
||||||
|
|
||||||
import { test as setup, expect } from "@playwright/test";
|
setup("authenticate as admin", async ({ browser }) => {
|
||||||
import { TEST_CREDENTIALS } from "./constants";
|
const context = await browser.newContext({ storageState: "admin_auth.json" });
|
||||||
|
const page = await context.newPage();
|
||||||
setup("authenticate", async ({ page }) => {
|
|
||||||
const { email, password } = TEST_CREDENTIALS;
|
|
||||||
|
|
||||||
await page.goto("http://localhost:3000/chat");
|
await page.goto("http://localhost:3000/chat");
|
||||||
|
|
||||||
await page.waitForURL("http://localhost:3000/auth/login?next=%2Fchat");
|
|
||||||
|
|
||||||
await expect(page).toHaveTitle("Onyx");
|
|
||||||
|
|
||||||
await page.fill("#email", email);
|
|
||||||
await page.fill("#password", password);
|
|
||||||
|
|
||||||
// Click the login button
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
await page.waitForURL("http://localhost:3000/chat");
|
await page.waitForURL("http://localhost:3000/chat");
|
||||||
|
|
||||||
await page.context().storageState({ path: "admin_auth.json" });
|
|
||||||
});
|
});
|
||||||
|
@@ -1,65 +1,43 @@
|
|||||||
import { test, expect } from "@chromatic-com/playwright";
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
test(
|
test.use({ storageState: "admin_auth.json" });
|
||||||
"Admin - OAuth Redirect - Missing Code",
|
|
||||||
{
|
|
||||||
tag: "@admin",
|
|
||||||
},
|
|
||||||
async ({ page }, testInfo) => {
|
|
||||||
await page.goto(
|
|
||||||
"http://localhost:3000/admin/connectors/slack/oauth/callback?state=xyz"
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(page.locator("p.text-text-500")).toHaveText(
|
test("Admin - OAuth Redirect - Missing Code", async ({ page }) => {
|
||||||
"Missing authorization code."
|
await page.goto(
|
||||||
);
|
"http://localhost:3000/admin/connectors/slack/oauth/callback?state=xyz"
|
||||||
}
|
);
|
||||||
);
|
|
||||||
|
|
||||||
test(
|
await expect(page.locator("p.text-text-500")).toHaveText(
|
||||||
"Admin - OAuth Redirect - Missing State",
|
"Missing authorization code."
|
||||||
{
|
);
|
||||||
tag: "@admin",
|
});
|
||||||
},
|
|
||||||
async ({ page }, testInfo) => {
|
|
||||||
await page.goto(
|
|
||||||
"http://localhost:3000/admin/connectors/slack/oauth/callback?code=123"
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(page.locator("p.text-text-500")).toHaveText(
|
test("Admin - OAuth Redirect - Missing State", async ({ page }) => {
|
||||||
"Missing state parameter."
|
await page.goto(
|
||||||
);
|
"http://localhost:3000/admin/connectors/slack/oauth/callback?code=123"
|
||||||
}
|
);
|
||||||
);
|
|
||||||
|
|
||||||
test(
|
await expect(page.locator("p.text-text-500")).toHaveText(
|
||||||
"Admin - OAuth Redirect - Invalid Connector",
|
"Missing state parameter."
|
||||||
{
|
);
|
||||||
tag: "@admin",
|
});
|
||||||
},
|
|
||||||
async ({ page }, testInfo) => {
|
|
||||||
await page.goto(
|
|
||||||
"http://localhost:3000/admin/connectors/invalid-connector/oauth/callback?code=123&state=xyz"
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(page.locator("p.text-text-500")).toHaveText(
|
test("Admin - OAuth Redirect - Invalid Connector", async ({ page }) => {
|
||||||
"invalid_connector is not a valid source type."
|
await page.goto(
|
||||||
);
|
"http://localhost:3000/admin/connectors/invalid-connector/oauth/callback?code=123&state=xyz"
|
||||||
}
|
);
|
||||||
);
|
|
||||||
|
|
||||||
test(
|
await expect(page.locator("p.text-text-500")).toHaveText(
|
||||||
"Admin - OAuth Redirect - No Session",
|
"invalid_connector is not a valid source type."
|
||||||
{
|
);
|
||||||
tag: "@admin",
|
});
|
||||||
},
|
|
||||||
async ({ page }, testInfo) => {
|
|
||||||
await page.goto(
|
|
||||||
"http://localhost:3000/admin/connectors/slack/oauth/callback?code=123&state=xyz"
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(page.locator("p.text-text-500")).toHaveText(
|
test("Admin - OAuth Redirect - No Session", async ({ page }) => {
|
||||||
"An error occurred during the OAuth process. Please try again."
|
await page.goto(
|
||||||
);
|
"http://localhost:3000/admin/connectors/slack/oauth/callback?code=123&state=xyz"
|
||||||
}
|
);
|
||||||
);
|
|
||||||
|
await expect(page.locator("p.text-text-500")).toHaveText(
|
||||||
|
"An error occurred during the OAuth process. Please try again."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@@ -2,6 +2,8 @@ import { test, expect } from "@playwright/test";
|
|||||||
import chromaticSnpashots from "./chromaticSnpashots.json";
|
import chromaticSnpashots from "./chromaticSnpashots.json";
|
||||||
import type { Page } from "@playwright/test";
|
import type { Page } from "@playwright/test";
|
||||||
|
|
||||||
|
test.use({ storageState: "admin_auth.json" });
|
||||||
|
|
||||||
async function verifyAdminPageNavigation(
|
async function verifyAdminPageNavigation(
|
||||||
page: Page,
|
page: Page,
|
||||||
path: string,
|
path: string,
|
||||||
@@ -13,7 +15,10 @@ async function verifyAdminPageNavigation(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
await page.goto(`http://localhost:3000/admin/${path}`);
|
await page.goto(`http://localhost:3000/admin/${path}`);
|
||||||
await expect(page.locator("h1.text-3xl")).toHaveText(pageTitle);
|
|
||||||
|
await expect(page.locator("h1.text-3xl")).toHaveText(pageTitle, {
|
||||||
|
timeout: 2000,
|
||||||
|
});
|
||||||
|
|
||||||
if (options?.paragraphText) {
|
if (options?.paragraphText) {
|
||||||
await expect(page.locator("p.text-sm").nth(0)).toHaveText(
|
await expect(page.locator("p.text-sm").nth(0)).toHaveText(
|
||||||
@@ -35,18 +40,12 @@ async function verifyAdminPageNavigation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const chromaticSnapshot of chromaticSnpashots) {
|
for (const chromaticSnapshot of chromaticSnpashots) {
|
||||||
test(
|
test(`Admin - ${chromaticSnapshot.name}`, async ({ page }) => {
|
||||||
`Admin - ${chromaticSnapshot.name}`,
|
await verifyAdminPageNavigation(
|
||||||
{
|
page,
|
||||||
tag: "@admin",
|
chromaticSnapshot.path,
|
||||||
},
|
chromaticSnapshot.pageTitle,
|
||||||
async ({ page }) => {
|
chromaticSnapshot.options
|
||||||
await verifyAdminPageNavigation(
|
);
|
||||||
page,
|
});
|
||||||
chromaticSnapshot.path,
|
|
||||||
chromaticSnapshot.pageTitle,
|
|
||||||
chromaticSnapshot.options
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
54
web/tests/e2e/assisant_ordering.spec.ts
Normal file
54
web/tests/e2e/assisant_ordering.spec.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
// Use pre-signed in "admin" storage state
|
||||||
|
test.use({
|
||||||
|
storageState: "admin_auth.json",
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Chat workflow", async ({ page }) => {
|
||||||
|
// Initial setup
|
||||||
|
await page.goto("http://localhost:3000/chat", { timeout: 3000 });
|
||||||
|
|
||||||
|
// Interact with Art assistant
|
||||||
|
await page.locator("button").filter({ hasText: "Art" }).click();
|
||||||
|
await page.getByPlaceholder("Message Art assistant...").fill("Hi");
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
await page.waitForTimeout(3000);
|
||||||
|
|
||||||
|
// Start a new chat
|
||||||
|
await page.getByRole("link", { name: "Start New Chat" }).click();
|
||||||
|
await page.waitForNavigation({ waitUntil: "networkidle" });
|
||||||
|
|
||||||
|
// Check for expected text
|
||||||
|
await expect(page.getByText("Assistant for generating")).toBeVisible();
|
||||||
|
|
||||||
|
// Interact with General assistant
|
||||||
|
await page.locator("button").filter({ hasText: "General" }).click();
|
||||||
|
|
||||||
|
// Check URL after clicking General assistant
|
||||||
|
await expect(page).toHaveURL("http://localhost:3000/chat?assistantId=-1", {
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a new assistant
|
||||||
|
await page.getByRole("button", { name: "Explore Assistants" }).click();
|
||||||
|
await page.getByRole("button", { name: "Create" }).click();
|
||||||
|
await page.getByTestId("name").click();
|
||||||
|
await page.getByTestId("name").fill("Test Assistant");
|
||||||
|
await page.getByTestId("description").click();
|
||||||
|
await page.getByTestId("description").fill("Test Assistant Description");
|
||||||
|
await page.getByTestId("system_prompt").click();
|
||||||
|
await page.getByTestId("system_prompt").fill("Test Assistant Instructions");
|
||||||
|
await page.getByRole("button", { name: "Create" }).click();
|
||||||
|
|
||||||
|
// Verify new assistant creation
|
||||||
|
await expect(page.getByText("Test Assistant Description")).toBeVisible({
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start another new chat
|
||||||
|
await page.getByRole("link", { name: "Start New Chat" }).click();
|
||||||
|
await expect(page.getByText("Assistant with access to")).toBeVisible({
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
});
|
@@ -1,4 +1,9 @@
|
|||||||
export const TEST_CREDENTIALS = {
|
export const TEST_USER_CREDENTIALS = {
|
||||||
|
email: "user1@test.com",
|
||||||
|
password: "User1Password123!",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEST_ADMIN_CREDENTIALS = {
|
||||||
email: "admin_user@test.com",
|
email: "admin_user@test.com",
|
||||||
password: "TestPassword123!",
|
password: "TestPassword123!",
|
||||||
};
|
};
|
||||||
|
22
web/tests/e2e/global-setup.ts
Normal file
22
web/tests/e2e/global-setup.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { chromium, FullConfig } from "@playwright/test";
|
||||||
|
import { loginAs } from "./utils/auth";
|
||||||
|
|
||||||
|
async function globalSetup(config: FullConfig) {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
|
||||||
|
const adminContext = await browser.newContext();
|
||||||
|
const adminPage = await adminContext.newPage();
|
||||||
|
await loginAs(adminPage, "admin");
|
||||||
|
await adminContext.storageState({ path: "admin_auth.json" });
|
||||||
|
await adminContext.close();
|
||||||
|
|
||||||
|
const userContext = await browser.newContext();
|
||||||
|
const userPage = await userContext.newPage();
|
||||||
|
await loginAs(userPage, "user");
|
||||||
|
await userContext.storageState({ path: "user_auth.json" });
|
||||||
|
await userContext.close();
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default globalSetup;
|
@@ -1,35 +0,0 @@
|
|||||||
// ➕ Add this line
|
|
||||||
import { test, expect, takeSnapshot } from "@chromatic-com/playwright";
|
|
||||||
import { TEST_CREDENTIALS } from "./constants";
|
|
||||||
|
|
||||||
// Then use as normal 👇
|
|
||||||
test(
|
|
||||||
"Homepage",
|
|
||||||
{
|
|
||||||
tag: "@guest",
|
|
||||||
},
|
|
||||||
async ({ page }, testInfo) => {
|
|
||||||
// Test redirect to login, and redirect to search after login
|
|
||||||
const { email, password } = TEST_CREDENTIALS;
|
|
||||||
|
|
||||||
await page.goto("http://localhost:3000/chat");
|
|
||||||
|
|
||||||
await page.waitForURL("http://localhost:3000/auth/login?next=%2Fchat");
|
|
||||||
|
|
||||||
await expect(page).toHaveTitle("Onyx");
|
|
||||||
|
|
||||||
await takeSnapshot(page, "Before login", testInfo);
|
|
||||||
|
|
||||||
await page.fill("#email", email);
|
|
||||||
await page.fill("#password", password);
|
|
||||||
|
|
||||||
// Click the login button
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
await page.waitForURL("http://localhost:3000/chat");
|
|
||||||
|
|
||||||
await page.getByPlaceholder("Send a message or try using @ or /");
|
|
||||||
|
|
||||||
await expect(page.locator("body")).not.toContainText("Initializing Onyx");
|
|
||||||
}
|
|
||||||
);
|
|
37
web/tests/e2e/utils/auth.ts
Normal file
37
web/tests/e2e/utils/auth.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Page } from "@playwright/test";
|
||||||
|
import { TEST_ADMIN_CREDENTIALS, TEST_USER_CREDENTIALS } from "../constants";
|
||||||
|
|
||||||
|
// Basic function which logs in a user (either admin or regular user) to the application
|
||||||
|
// It handles both successful login attempts and potential timeouts, with a retry mechanism
|
||||||
|
export async function loginAs(page: Page, userType: "admin" | "user") {
|
||||||
|
const { email, password } =
|
||||||
|
userType === "admin" ? TEST_ADMIN_CREDENTIALS : TEST_USER_CREDENTIALS;
|
||||||
|
await page.goto("http://localhost:3000/auth/login", { timeout: 1000 });
|
||||||
|
|
||||||
|
await page.fill("#email", email);
|
||||||
|
await page.fill("#password", password);
|
||||||
|
|
||||||
|
// Click the login button
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await page.waitForURL("http://localhost:3000/chat", { timeout: 4000 });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Timeout occurred. Current URL: ${page.url()}`);
|
||||||
|
|
||||||
|
// If redirect to /chat doesn't happen, go to /auth/login
|
||||||
|
await page.goto("http://localhost:3000/auth/signup", { timeout: 1000 });
|
||||||
|
|
||||||
|
await page.fill("#email", email);
|
||||||
|
await page.fill("#password", password);
|
||||||
|
|
||||||
|
// Click the login button
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await page.waitForURL("http://localhost:3000/chat", { timeout: 4000 });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Timeout occurred again. Current URL: ${page.url()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
web/user_auth.json
Normal file
15
web/user_auth.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"cookies": [
|
||||||
|
{
|
||||||
|
"name": "fastapiusersauth",
|
||||||
|
"value": "n_EMYYKHn4tQbuPTEbtN1gJ6dQTGek9omJPhO2GhHoA",
|
||||||
|
"domain": "localhost",
|
||||||
|
"path": "/",
|
||||||
|
"expires": 1738801376.508558,
|
||||||
|
"httpOnly": true,
|
||||||
|
"secure": false,
|
||||||
|
"sameSite": "Lax"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"origins": []
|
||||||
|
}
|
Reference in New Issue
Block a user