Merge remote-tracking branch 'upstream/dev' into fix/onboarding

This commit is contained in:
Anish Sarkar 2026-07-14 10:18:03 +05:30
commit 2d837ea18c
185 changed files with 4729 additions and 3630 deletions

View file

@ -35,6 +35,7 @@ import {
useAllCitationMetadata,
} from "@/components/assistant-ui/citation-metadata-context";
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import { MessageTimestamp } from "@/components/assistant-ui/message-timestamp";
import { ReasoningMessagePart } from "@/components/assistant-ui/reasoning-message-part";
import { RevertTurnButton } from "@/components/assistant-ui/revert-turn-button";
import {
@ -62,6 +63,7 @@ import { withArtifactAnchor } from "@/features/chat-artifacts";
import { useComments } from "@/hooks/use-comments";
import { useMediaQuery } from "@/hooks/use-media-query";
import { useElectronAPI } from "@/hooks/use-platform";
import { formatMessageTimestamp } from "@/lib/format-date";
import { getProviderIcon } from "@/lib/provider-icons";
import { tryGetHostname } from "@/lib/url";
import { cn } from "@/lib/utils";
@ -249,16 +251,6 @@ export const MessageError: FC = () => {
);
};
function formatMessageDate(date: Date): string {
return date.toLocaleDateString(undefined, {
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
hour12: true,
});
}
/**
* Format provider USD cost (in micro-USD) for inline display next to a
* token count. Falls back to ``"<$0.001"`` for sub-tenth-of-a-cent
@ -367,7 +359,7 @@ const MessageInfoDropdown: FC<{ chatTurnId: string | null | undefined }> = ({ ch
>
{createdAt && (
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal select-none">
{formatMessageDate(createdAt)}
{formatMessageTimestamp(createdAt)}
</DropdownMenuLabel>
)}
{hasUsage && (
@ -463,6 +455,8 @@ const AssistantMessageInner: FC = () => {
<MessageError />
</div>
<MessageTimestamp className="ml-2 mt-2" />
{isMobile && (
<div className="ml-2 mt-2">
<MobileCitationDrawer />

View file

@ -0,0 +1,24 @@
import { useAuiState } from "@assistant-ui/react";
import { useAtomValue } from "jotai";
import type { FC } from "react";
import { showMessageTimestampsAtom } from "@/atoms/chat/show-timestamps.atom";
import { formatMessageTimestamp } from "@/lib/format-date";
import { cn } from "@/lib/utils";
/**
* Muted, always-visible timestamp under a chat message. Renders only when the
* user has opted in via {@link showMessageTimestampsAtom} and the message
* carries a ``createdAt`` (absent on optimistic pre-persist messages).
*/
export const MessageTimestamp: FC<{ className?: string }> = ({ className }) => {
const show = useAtomValue(showMessageTimestampsAtom);
const createdAt = useAuiState(({ message }) => message?.createdAt);
if (!show || !createdAt) return null;
return (
<div className={cn("select-none text-[11px] text-muted-foreground", className)}>
{formatMessageTimestamp(createdAt)}
</div>
);
};

View file

@ -1070,10 +1070,7 @@ const ConnectedScraperIcons: FC<{ workspaceId: number }> = ({ workspaceId }) =>
return (
<Tooltip key={platform.id}>
<TooltipTrigger asChild>
<Avatar
className="size-5"
style={{ zIndex: platforms.length - i }}
>
<Avatar className="size-5" style={{ zIndex: platforms.length - i }}>
<AvatarFallback className="bg-popover text-[10px]">
<Icon className="size-3" />
</AvatarFallback>

View file

@ -22,6 +22,7 @@ import { currentThreadAtom } from "@/atoms/chat/current-thread.atom";
import { messageDocumentsMapAtom } from "@/atoms/chat/mentioned-documents.atom";
import { openEditorPanelAtom } from "@/atoms/editor/editor-panel.atom";
import { MentionChip } from "@/components/assistant-ui/mention-chip";
import { MessageTimestamp } from "@/components/assistant-ui/message-timestamp";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { getMentionDocKey } from "@/lib/chat/mention-doc-key";
@ -182,6 +183,7 @@ export const UserMessage: FC = () => {
</div>
)}
</div>
<MessageTimestamp className="mt-1 pl-1" />
</div>
</MessagePrimitive.Root>
);

View file

@ -0,0 +1,23 @@
"use client";
import { useEffect } from "react";
import { chatStreamStore } from "@/lib/chat/stream-engine/store";
/**
* Persistent, render-null host that scopes the in-flight chat turn's lifetime
* to the workspace shell, not the chat page.
*
* Mounted in ``LayoutDataProvider``, it survives in-app navigation between
* workspace routes and aborts the single active turn only on workspace/app
* teardown, so ordinary navigation disconnects the view without stopping the
* stream.
*/
export function ActiveChatStreamRunner() {
useEffect(() => {
return () => {
chatStreamStore.abortActive();
};
}, []);
return null;
}

View file

@ -27,7 +27,8 @@ export type HeroChatDemoScript = {
type Stage = "typing" | "steps" | "answer" | "done";
const PLACEHOLDER = "Track competitors, scrape platforms, automate briefs. Use / for prompts, @ for docs";
const PLACEHOLDER =
"Track competitors, scrape platforms, automate briefs. Use / for prompts, @ for docs";
/** Blinking caret for the typewriter (overlay only, never inside the real input). */
function Caret() {

View file

@ -708,7 +708,7 @@ export function HeroSection() {
"relative mt-4 max-w-4xl text-left text-4xl font-bold tracking-tight text-balance text-neutral-900 sm:text-5xl md:text-6xl dark:text-neutral-50"
)}
>
<Balancer>Give your AI agents competitive intelligence.</Balancer>
<Balancer>NotebookLM for competitive intelligence research.</Balancer>
</h1>
<div className="mt-4 flex w-full flex-col items-start justify-between gap-4 md:mt-8 md:flex-row md:items-end md:gap-10">
<div>
@ -717,9 +717,10 @@ export function HeroSection() {
"relative mb-8 max-w-2xl text-left text-sm text-neutral-600 antialiased sm:text-base md:text-lg dark:text-neutral-400"
)}
>
SurfSense is an open-source competitive intelligence platform. Your AI agents monitor
competitors, track rankings, and listen to your market with live data from platforms
like Reddit, YouTube, Instagram, TikTok, Google Maps, Google Search, and the open web.
SurfSense is an open-source competitive intelligence platform, like NotebookLM but
with live scraping connectors. Your AI agents monitor competitors, track rankings, and
listen to your market with live data from platforms like Reddit, YouTube, Instagram,
TikTok, Google Maps, Google Search, and the open web.
</p>
<div className="relative mb-4 flex w-full flex-col justify-center gap-y-2 sm:flex-row sm:justify-start sm:space-y-0 sm:space-x-4">

View file

@ -27,6 +27,7 @@ export { default as PerplexityIcon } from "./perplexity.svg";
export { default as QwenIcon } from "./qwen.svg";
export { default as RecraftIcon } from "./recraft.svg";
export { default as ReplicateIcon } from "./replicate.svg";
export { default as RequestyIcon } from "./requesty.svg";
export { default as SambaNovaIcon } from "./sambanova.svg";
export { default as TogetherAiIcon } from "./togetherai.svg";
export { default as VertexAiIcon } from "./vertexai.svg";

View file

@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 2a1 1 0 000 2h2.086l-5.293 5.293a1 1 0 001.414 1.414L18 5.414V7.5a1 1 0 102 0v-4.5a1 1 0 00-1-1h-4.5zM4 6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4a1 1 0 10-2 0v4H4V8h4a1 1 0 000-2H4z"></path></svg>

After

Width:  |  Height:  |  Size: 318 B

View file

@ -18,6 +18,7 @@ import { workspacesAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { ActionLogDialog } from "@/components/agent-action-log/action-log-dialog";
import { AnnouncementSpotlight } from "@/components/announcements/AnnouncementSpotlight";
import { AnnouncementsDialog } from "@/components/announcements/AnnouncementsDialog";
import { ActiveChatStreamRunner } from "@/components/chat/active-chat-stream-runner";
import {
AlertDialog,
AlertDialogAction,
@ -649,6 +650,9 @@ export function LayoutDataProvider({
return (
<>
{/* Persistent host: keeps an in-flight chat turn streaming across
in-app navigation and aborts it only on workspace teardown. */}
<ActiveChatStreamRunner />
<LayoutShell
workspaces={workspaces}
activeWorkspaceId={Number(workspaceId)}

View file

@ -324,9 +324,7 @@ export function NotificationsDropdown({
)}
>
<span>{tab.label}</span>
<span
className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-muted px-1.5 text-[11px] font-semibold text-muted-foreground"
>
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-muted px-1.5 text-[11px] font-semibold text-muted-foreground">
{formatNotificationCount(tab.count)}
</span>
</button>

View file

@ -198,8 +198,8 @@ export function AutoReloadSettings() {
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Last top-up failed</AlertTitle>
<AlertDescription>
Your saved card was declined and top-ups were turned off. Update your card and
re-enable top-ups below.
Your saved card was declined and top-ups were turned off. Update your card and re-enable
top-ups below.
</AlertDescription>
</Alert>
)}
@ -290,7 +290,11 @@ export function AutoReloadSettings() {
</div>
<div className="flex justify-end">
<Button className="w-full sm:w-auto" onClick={handleSave} disabled={saveMutation.isPending}>
<Button
className="w-full sm:w-auto"
onClick={handleSave}
disabled={saveMutation.isPending}
>
{saveMutation.isPending ? (
<>
<Spinner size="xs" />

View file

@ -9,9 +9,17 @@ function baseUrlHint(provider: string) {
return "For local servers, use host.docker.internal instead of localhost.";
}
if (provider === "openai_compatible") {
return "Enter the full endpoint URL.";
return "Enter the full endpoint URL. This provider expects a /v1-compatible endpoint.";
}
if (provider === "openai" || provider === "anthropic" || provider === "openrouter") {
if (provider === "openai_compatible_raw") {
return "Enter the exact chat-completions API base URL. SurfSense will not append /v1.";
}
if (
provider === "openai" ||
provider === "anthropic" ||
provider === "openrouter" ||
provider === "requesty"
) {
return "Override only if you route through a proxy or gateway.";
}
return undefined;

View file

@ -7,9 +7,11 @@ export const PROVIDER_ORDER = [
"bedrock",
"azure",
"openrouter",
"requesty",
"ollama_chat",
"lm_studio",
"openai_compatible",
"openai_compatible_raw",
];
export const PROVIDER_DISPLAY: Record<
@ -37,12 +39,23 @@ export const PROVIDER_DISPLAY: Record<
subtitle: "OpenAI-compatible endpoint",
iconKey: "custom",
},
openai_compatible_raw: {
name: "OpenAI-Compatible Raw",
subtitle: "Use the exact base URL, no /v1 is appended",
iconKey: "custom",
},
openrouter: {
name: "OpenRouter",
subtitle: "OpenRouter",
iconKey: "openrouter",
defaultBaseUrl: "https://openrouter.ai/api/v1",
},
requesty: {
name: "Requesty",
subtitle: "Requesty",
iconKey: "requesty",
defaultBaseUrl: "https://router.requesty.ai/v1",
},
vertex_ai: { name: "Gemini", subtitle: "Google Cloud Vertex AI", iconKey: "vertex_ai" },
};

View file

@ -59,7 +59,12 @@ export function WorkspaceApiAccessControl({
if (isLoading) {
return (
<div className={cn("flex flex-col gap-3 md:flex-row md:items-center md:justify-between", className)}>
<div
className={cn(
"flex flex-col gap-3 md:flex-row md:items-center md:justify-between",
className
)}
>
<div className="space-y-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-3 w-56" />
@ -71,7 +76,12 @@ export function WorkspaceApiAccessControl({
if (isError) {
return (
<div className={cn("flex flex-col gap-3 md:flex-row md:items-center md:justify-between", className)}>
<div
className={cn(
"flex flex-col gap-3 md:flex-row md:items-center md:justify-between",
className
)}
>
<div className="space-y-1">
<Label>API key access</Label>
<p className="text-xs text-destructive">Failed to load workspace API access.</p>
@ -84,7 +94,12 @@ export function WorkspaceApiAccessControl({
}
return (
<div className={cn("flex flex-col gap-3 md:flex-row md:items-center md:justify-between", className)}>
<div
className={cn(
"flex flex-col gap-3 md:flex-row md:items-center md:justify-between",
className
)}
>
<div className="space-y-1">
<Label htmlFor="api-access-enabled">API key access</Label>
<p className="text-xs text-muted-foreground">Allow API keys to access this workspace.</p>