Merge branch 'dev' into documents-mentions

This commit is contained in:
Thierry CH. 2025-12-24 06:31:49 +02:00 committed by GitHub
commit c4400a0ec2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 55 additions and 115 deletions

View file

@ -6,9 +6,8 @@ Create Date: 2025-12-21
This migration:
1. Migrates data from old 'chats' table to 'new_chat_threads' and 'new_chat_messages'
2. Drops the 'podcasts' table (podcast data is not migrated as per user request)
3. Drops the 'chats' table
4. Removes the 'chattype' enum
2. Drops the 'chats' table
3. Removes the 'chattype' enum
"""
import json
@ -92,7 +91,11 @@ def upgrade() -> None:
print(f"[Migration 49] Skipping empty chat {chat_id}")
continue
# Create new thread
# Create new thread - truncate title to 500 chars (VARCHAR(500) limit)
thread_title = title or "Migrated Chat"
if len(thread_title) > 500:
thread_title = thread_title[:497] + "..."
result = connection.execute(
sa.text("""
INSERT INTO new_chat_threads
@ -101,7 +104,7 @@ def upgrade() -> None:
RETURNING id
"""),
{
"title": title or "Migrated Chat",
"title": thread_title,
"search_space_id": search_space_id,
"created_at": created_at,
},
@ -162,11 +165,7 @@ def upgrade() -> None:
print(f"[Migration 49] Successfully migrated {migrated_count} chats")
# Drop podcasts table (FK references chats, so drop first)
print("[Migration 49] Dropping podcasts table...")
op.drop_table("podcasts")
# Drop chats table
# Drop chats table (podcasts table was already updated to remove chat_id FK)
print("[Migration 49] Dropping chats table...")
op.drop_table("chats")
@ -178,7 +177,7 @@ def upgrade() -> None:
def downgrade() -> None:
"""Recreate old tables (data cannot be restored)."""
"""Recreate old chats table (data cannot be restored)."""
# Recreate chattype enum
op.execute(
sa.text("""
@ -209,32 +208,4 @@ def downgrade() -> None:
),
)
# Recreate podcasts table
op.create_table(
"podcasts",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("title", sa.String(), nullable=False, index=True),
sa.Column("podcast_transcript", sa.JSON(), nullable=False, server_default="{}"),
sa.Column("file_location", sa.String(500), nullable=False, server_default=""),
sa.Column(
"chat_id",
sa.Integer(),
sa.ForeignKey("chats.id", ondelete="CASCADE"),
nullable=True,
),
sa.Column("chat_state_version", sa.BigInteger(), nullable=True),
sa.Column(
"search_space_id",
sa.Integer(),
sa.ForeignKey("searchspaces.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column(
"created_at",
sa.TIMESTAMP(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
)
print("[Migration 49 Downgrade] Tables recreated (data not restored)")
print("[Migration 49 Downgrade] Chats table recreated (data not restored)")

View file

@ -6,8 +6,8 @@ Create Date: 2024-12-22
This migration:
1. Migrates data from old llm_configs table to new_llm_configs (preserving user configs)
2. Drops the old llm_configs table (no longer used)
3. Removes the is_default column from new_llm_configs (roles now determine which config to use)
2. Updates searchspaces to point to migrated configs
3. Drops the old llm_configs table (no longer used)
"""
from alembic import op
@ -47,7 +47,6 @@ def upgrade():
system_instructions,
use_default_system_instructions,
citations_enabled,
is_default,
search_space_id,
created_at
)
@ -59,11 +58,10 @@ def upgrade():
lc.model_name,
lc.api_key,
lc.api_base,
COALESCE(lc.litellm_params, '{}'::jsonb),
COALESCE(lc.litellm_params::json, '{}'::json),
'' as system_instructions, -- Use defaults
TRUE as use_default_system_instructions,
TRUE as citations_enabled,
FALSE as is_default,
lc.search_space_id,
COALESCE(lc.created_at, NOW())
FROM llm_configs lc
@ -130,23 +128,7 @@ def upgrade():
"""
)
# STEP 3: Drop the is_default column from new_llm_configs
# (role assignments now determine which config to use)
op.execute(
"""
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'new_llm_configs' AND column_name = 'is_default'
) THEN
ALTER TABLE new_llm_configs DROP COLUMN is_default;
END IF;
END$$;
"""
)
# STEP 4: Drop the old llm_configs table (data has been migrated)
# STEP 3: Drop the old llm_configs table (data has been migrated)
op.execute("DROP TABLE IF EXISTS llm_configs CASCADE")
@ -213,7 +195,7 @@ def downgrade():
nlc.api_key,
nlc.api_base,
'English' as language, -- Default language
COALESCE(nlc.litellm_params, '{}'::jsonb),
COALESCE(nlc.litellm_params::jsonb, '{}'::jsonb),
nlc.search_space_id,
nlc.created_at
FROM new_llm_configs nlc
@ -227,18 +209,3 @@ def downgrade():
END$$;
"""
)
# Add back the is_default column to new_llm_configs
op.execute(
"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'new_llm_configs' AND column_name = 'is_default'
) THEN
ALTER TABLE new_llm_configs ADD COLUMN is_default BOOLEAN NOT NULL DEFAULT FALSE;
END IF;
END$$;
"""
)

View file

@ -253,7 +253,7 @@ export function DashboardClientLayout({
/>
<SidebarInset className="h-full ">
<main className="flex flex-col h-full">
<header className="sticky top-0 z-50 flex h-16 shrink-0 items-center gap-2 bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60 border-b">
<header className="sticky top-0 flex h-16 shrink-0 items-center gap-2 bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60 border-b">
<div className="flex items-center justify-between w-full gap-2 px-4">
<div className="flex items-center gap-2">
<SidebarTrigger className="-ml-1" />
@ -265,7 +265,7 @@ export function DashboardClientLayout({
</div>
</div>
</header>
<div className="grow flex-1 overflow-auto min-h-[calc(100vh-64px)]">{children}</div>
<div className="flex-1 overflow-hidden">{children}</div>
</main>
</SidebarInset>
</SidebarProvider>

View file

@ -250,7 +250,7 @@ export default function ConnectorsPage() {
};
return (
<div className="container mx-auto py-8 max-w-6xl">
<div className="container mx-auto py-8 px-4 max-w-6xl min-h-[calc(100vh-64px)]">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}

View file

@ -189,7 +189,7 @@ export default function DocumentsTable() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="w-full px-6 py-4"
className="w-full px-6 py-4 min-h-[calc(100vh-64px)]"
>
<DocumentsFilters
typeCounts={typeCounts ?? {}}

View file

@ -444,7 +444,7 @@ export default function LogsManagePage() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="w-full px-6 py-4 space-y-6"
className="w-full px-6 py-4 space-y-6 min-h-[calc(100vh-64px)]"
>
{/* Summary Dashboard */}
<LogsSummaryDashboard

View file

@ -694,11 +694,11 @@ export default function NewChatPage() {
<LinkPreviewToolUI />
<DisplayImageToolUI />
<ScrapeWebpageToolUI />
<div className="flex flex-col h-[calc(100vh-64px)] max-h-[calc(100vh-64px)] overflow-hidden">
<ChatHeader searchSpaceId={searchSpaceId} />
<div className="flex-1 min-h-0 overflow-hidden">
<Thread messageThinkingSteps={messageThinkingSteps} />
</div>
<div className="flex flex-col h-[calc(100vh-64px)] overflow-hidden">
<Thread
messageThinkingSteps={messageThinkingSteps}
header={<ChatHeader searchSpaceId={searchSpaceId} />}
/>
</div>
</AssistantRuntimeProvider>
);

View file

@ -34,7 +34,7 @@ export default function AddSourcesPage() {
};
return (
<div className="container mx-auto py-8 px-4">
<div className="container mx-auto py-8 px-4 min-h-[calc(100vh-64px)]">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}

View file

@ -8,6 +8,7 @@ import {
ThreadPrimitive,
useAssistantState,
useThreadViewport,
useMessage,
} from "@assistant-ui/react";
import { useAtom, useAtomValue, useSetAtom } from "jotai";
import {
@ -75,6 +76,8 @@ import { cn } from "@/lib/utils";
*/
interface ThreadProps {
messageThinkingSteps?: Map<string, ThinkingStep[]>;
/** Optional header component to render at the top of the viewport (sticky) */
header?: React.ReactNode;
}
// Context to pass thinking steps to AssistantMessage
@ -267,20 +270,21 @@ const ThinkingStepsScrollHandler: FC = () => {
};
export const Thread: FC<ThreadProps> = ({ messageThinkingSteps = new Map() }) => {
export const Thread: FC<ThreadProps> = ({ messageThinkingSteps = new Map(), header }) => {
return (
<ThinkingStepsContext.Provider value={messageThinkingSteps}>
<ThreadPrimitive.Root
className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
className="aui-root aui-thread-root @container flex h-full min-h-0 flex-col bg-background"
style={{
["--thread-max-width" as string]: "44rem",
}}
>
<ThreadPrimitive.Viewport
turnAnchor="top"
className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
className="aui-thread-viewport relative flex flex-1 min-h-0 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
>
{/* Auto-scroll handler for thinking steps - must be inside Viewport */}
<ThinkingStepsScrollHandler />
{/* Optional sticky header for model selector etc. */}
{header && <div className="sticky top-0 z-10 mb-4">{header}</div>}
<AssistantIf condition={({ thread }) => thread.isEmpty}>
<ThreadWelcome />
@ -375,7 +379,7 @@ const ThreadWelcome: FC = () => {
return (
<div className="aui-thread-welcome-root mx-auto flex w-full max-w-(--thread-max-width) grow flex-col items-center px-4 relative">
{/* Greeting positioned above the composer - fixed position */}
<div className="aui-thread-welcome-message absolute bottom-[calc(50%+5rem)] left-0 right-0 flex flex-col items-center text-center z-10">
<div className="aui-thread-welcome-message absolute bottom-[calc(50%+5rem)] left-0 right-0 flex flex-col items-center text-center">
<h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-2 animate-in text-5xl delay-100 duration-500 ease-out fill-mode-both">
{greeting}
</h1>

View file

@ -47,12 +47,7 @@ export function ChatHeader({ searchSpaceId }: ChatHeaderProps) {
return (
<>
{/* Header Bar */}
<div className="flex items-center justify-between px-4 py-2 border-b border-border/30 bg-background/80 backdrop-blur-sm">
<ModelSelector onEdit={handleEditConfig} onAddNew={handleAddNew} />
</div>
{/* Config Sidebar */}
<ModelSelector onEdit={handleEditConfig} onAddNew={handleAddNew} />
<ModelConfigSidebar
open={sidebarOpen}
onOpenChange={handleSidebarClose}

View file

@ -175,9 +175,10 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
role="combobox"
aria-expanded={open}
className={cn(
"h-9 gap-2 px-3 rounded-xl border border-border/50 bg-background/50 backdrop-blur-sm",
"hover:bg-muted/80 hover:border-border transition-all duration-200",
"h-9 gap-2 px-3 rounded-xl border border-border/30 bg-background/50 backdrop-blur-sm",
"hover:bg-muted/80 hover:border-border/30 transition-all duration-200",
"text-sm font-medium text-foreground",
"focus-visible:ring-0 focus-visible:ring-offset-0",
className
)}
>
@ -206,11 +207,14 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
</PopoverTrigger>
<PopoverContent
className="w-[360px] p-0 rounded-xl shadow-lg border-border/50"
className="w-[360px] p-0 rounded-xl shadow-lg border-border/30"
align="start"
sideOffset={8}
>
<Command shouldFilter={false} className="rounded-xl relative">
<Command
shouldFilter={false}
className="rounded-xl relative [&_[data-slot=command-input-wrapper]]:border-0 [&_[data-slot=command-input-wrapper]]:px-0 [&_[data-slot=command-input-wrapper]]:gap-2"
>
{/* Switching overlay */}
{isSwitching && (
<div className="absolute inset-0 z-10 flex items-center justify-center bg-background/80 backdrop-blur-sm rounded-xl">
@ -221,8 +225,7 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
</div>
)}
<div className="flex items-center gap-2 border-b px-3 py-2 bg-muted/30">
<Bot className="size-4 text-muted-foreground" />
<div className="flex items-center gap-2 border-b border-border/30 px-3 py-2">
<CommandInput
placeholder="Search models..."
value={searchQuery}
@ -300,7 +303,7 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
)}
{filteredGlobalConfigs.length > 0 && filteredUserConfigs.length > 0 && (
<CommandSeparator className="my-1" />
<CommandSeparator className="my-1 bg-border/30" />
)}
{/* User Configs Section */}
@ -362,7 +365,7 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
)}
{/* Add New Config Button */}
<div className="p-2 border-t border-border/50 bg-muted/20">
<div className="p-2 bg-muted/20">
<Button
variant="ghost"
size="sm"

View file

@ -160,8 +160,8 @@ export function AllChatsSidebar({ open, onOpenChange, searchSpaceId }: AllChatsS
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="left" className="w-80 p-0 flex flex-col">
<SheetHeader className="mx-3 px-4 py-4 border-b space-y-3">
<SheetContent side="left" className="w-80 p-0 flex flex-col border-0">
<SheetHeader className="mx-3 px-4 pt-4 pb-0 space-y-2">
<SheetTitle>{t("all_chats") || "All Chats"}</SheetTitle>
<SheetDescription className="sr-only">
{t("all_chats_description") || "Browse and manage all your chats"}
@ -175,7 +175,7 @@ export function AllChatsSidebar({ open, onOpenChange, searchSpaceId }: AllChatsS
placeholder={t("search_chats") || "Search chats..."}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 pr-8 h-9"
className="pl-9 pr-8 h-9 border-0 focus-visible:ring-0 focus-visible:border-0 shadow-none"
/>
{searchQuery && (
<Button
@ -193,7 +193,7 @@ export function AllChatsSidebar({ open, onOpenChange, searchSpaceId }: AllChatsS
{/* Tab toggle for active/archived (only show when not searching) */}
{!isSearchMode && (
<div className="flex border-b mx-3">
<div className="flex border-b mx-3 -mt-3">
<button
type="button"
onClick={() => setShowArchived(false)}

View file

@ -159,8 +159,8 @@ export function AllNotesSidebar({
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="left" className="w-80 p-0 flex flex-col">
<SheetHeader className="mx-3 px-4 py-4 border-b space-y-3">
<SheetContent side="left" className="w-80 p-0 flex flex-col border-0">
<SheetHeader className="mx-3 px-4 pt-4 pb-2 border-b space-y-2">
<SheetTitle>{t("all_notes") || "All Notes"}</SheetTitle>
<SheetDescription className="sr-only">
{t("all_notes_description") || "Browse and manage all your notes"}
@ -174,7 +174,7 @@ export function AllNotesSidebar({
placeholder={t("search_notes") || "Search notes..."}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 pr-8 h-9"
className="pl-9 pr-8 h-9 border-0 focus-visible:ring-0 focus-visible:border-0 shadow-none"
/>
{searchQuery && (
<Button