Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `wa_user_created` PostHog event fired on successful user sign-up. [#933](https://github.com/sourcebot-dev/sourcebot/pull/933)
- Added `wa_askgh_login_wall_prompted` PostHog event fired when an unauthenticated user attempts to ask a question on Ask GitHub. [#933](https://github.com/sourcebot-dev/sourcebot/pull/933)
- Added Bitbucket Server (Data Center) OAuth 2.0 SSO identity provider support (`provider: "bitbucket-server"`). [#934](https://github.com/sourcebot-dev/sourcebot/pull/934)
- Added login wall when anonymous users try to send messages on duplicated chats (askgh experiment). [#939](https://github.com/sourcebot-dev/sourcebot/pull/939)
Comment thread
msukkari marked this conversation as resolved.

### Changed
- Hide version upgrade toast for askgithub deployment (`EXPERIMENT_ASK_GH_ENABLED`). [#931](https://github.com/sourcebot-dev/sourcebot/pull/931)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SearchModeSelector } from "@/app/[domain]/components/searchModeSelector
import { Separator } from "@/components/ui/separator";
import { ChatBox } from "@/features/chat/components/chatBox";
import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolbar";
import { LoginModal } from "./loginModal";
import { LoginModal } from "@/app/components/loginModal";
import { NotConfiguredErrorBanner } from "@/features/chat/components/notConfiguredErrorBanner";
import { LanguageModelInfo, RepoSearchScope } from "@/features/chat/types";
import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread";
Expand Down
1 change: 0 additions & 1 deletion packages/web/src/app/[domain]/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { ChatVisibility } from '@sourcebot/db';
import { Metadata } from 'next';
import { SBChatMessage } from '@/features/chat/types';
import { env, hasEntitlement } from '@sourcebot/shared';

import { captureEvent } from '@/lib/posthog';

interface PageProps {
Expand Down
10 changes: 10 additions & 0 deletions packages/web/src/features/chat/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,16 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel): P

}

export const getAskGhLoginWallData = async () => sew(async () => {
const isEnabled = env.EXPERIMENT_ASK_GH_ENABLED === 'true';
if (!isEnabled) {
return { isEnabled: false as const, providers: [] };
}

const { getIdentityProviderMetadata } = await import('@/lib/identityProviders');
return { isEnabled: true as const, providers: getIdentityProviderMetadata() };
});

const extractLanguageModelKeyValuePairs = async (
pairs: {
[k: string]: string | Token;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@ import { NotConfiguredErrorBanner } from '../notConfiguredErrorBanner';
import useCaptureEvent from '@/hooks/useCaptureEvent';
import { SignInPromptBanner } from './signInPromptBanner';
import { DuplicateChatDialog } from '@/app/[domain]/chat/components/duplicateChatDialog';
import { LoginModal } from '@/app/components/loginModal';
import type { IdentityProviderMetadata } from '@/lib/identityProviders';
import { getAskGhLoginWallData } from '../../actions';
import { useParams } from 'next/navigation';

type ChatHistoryState = {
scrollOffset?: number;
}

const PENDING_MESSAGE_STORAGE_KEY = "askgh_chat_pending_message";

interface ChatThreadProps {
id?: string | undefined;
initialMessages?: SBChatMessage[];
Expand Down Expand Up @@ -71,6 +76,9 @@ export const ChatThread = ({
const params = useParams<{ domain: string }>();
const [isContextSelectorOpen, setIsContextSelectorOpen] = useState(false);
const [isDuplicateDialogOpen, setIsDuplicateDialogOpen] = useState(false);
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false);
const [loginWallProviders, setLoginWallProviders] = useState<IdentityProviderMetadata[]>([]);
const hasRestoredPendingMessage = useRef(false);
const captureEvent = useCaptureEvent();

// Initial state is from attachments that exist in in the chat history.
Expand Down Expand Up @@ -200,6 +208,38 @@ export const ChatThread = ({
hasSubmittedInputMessage.current = true;
}, [inputMessage, sendMessage]);

// Restore pending message after OAuth redirect (askgh login wall)
useEffect(() => {
if (!isAuthenticated || !isOwner || hasRestoredPendingMessage.current) {
return;
}

const stored = sessionStorage.getItem(PENDING_MESSAGE_STORAGE_KEY);
if (!stored) {
return;
}

hasRestoredPendingMessage.current = true;
sessionStorage.removeItem(PENDING_MESSAGE_STORAGE_KEY);

Comment thread
msukkari marked this conversation as resolved.
try {
const { chatId: storedChatId, children } = JSON.parse(stored) as { chatId: string; children: Descendant[] };

// Only restore if we're on the same chat that stored the pending message
if (storedChatId !== chatId) {
return;
}

const text = slateContentToString(children);
const mentions = getAllMentionElements(children);
const message = createUIMessage(text, mentions.map(({ data }) => data), selectedSearchScopes);
sendMessage(message);
setIsAutoScrollEnabled(true);
} catch (error) {
console.error('Failed to restore pending message:', error);
}
}, [isAuthenticated, isOwner, chatId, sendMessage, selectedSearchScopes]);

// Track scroll position changes.
useEffect(() => {
const scrollElement = scrollAreaRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement;
Expand Down Expand Up @@ -287,7 +327,18 @@ export const ChatThread = ({
}
}, [error]);

const onSubmit = useCallback((children: Descendant[], editor: CustomEditor) => {
const onSubmit = useCallback(async (children: Descendant[], editor: CustomEditor) => {
if (!isAuthenticated) {
const result = await getAskGhLoginWallData();
if (!isServiceError(result) && result.isEnabled) {
captureEvent('wa_askgh_login_wall_prompted', {});
sessionStorage.setItem(PENDING_MESSAGE_STORAGE_KEY, JSON.stringify({ chatId, children }));
setLoginWallProviders(result.providers);
setIsLoginModalOpen(true);
return;
}
}

const text = slateContentToString(children);
const mentions = getAllMentionElements(children);

Expand All @@ -297,7 +348,7 @@ export const ChatThread = ({
setIsAutoScrollEnabled(true);

resetEditor(editor);
}, [sendMessage, selectedSearchScopes]);
}, [sendMessage, selectedSearchScopes, isAuthenticated, captureEvent, chatId]);

const onDuplicate = useCallback(async (newName: string): Promise<string | null> => {
if (!defaultChatId) {
Expand Down Expand Up @@ -449,6 +500,13 @@ export const ChatThread = ({
</div>
)}
</div>

<LoginModal
isOpen={isLoginModalOpen}
onOpenChange={setIsLoginModalOpen}
providers={loginWallProviders}
callbackUrl={typeof window !== 'undefined' ? window.location.href : ''}
/>
</>
);
}
6 changes: 3 additions & 3 deletions packages/web/src/features/git/listCommitsApi.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { listCommits } from './listCommitsApi';
import * as dateUtils from './dateUtils';

Expand Down Expand Up @@ -63,8 +63,8 @@ describe('searchCommits', () => {
const mockGitLog = vi.fn();
const mockGitRaw = vi.fn();
const mockCwd = vi.fn();
const mockSimpleGit = simpleGit as unknown as vi.Mock;
const mockExistsSync = existsSync as unknown as vi.Mock;
const mockSimpleGit = simpleGit as unknown as Mock;
const mockExistsSync = existsSync as unknown as Mock;

beforeEach(() => {
vi.clearAllMocks();
Expand Down
12 changes: 11 additions & 1 deletion packages/web/src/features/git/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { expect, test } from 'vitest';
import { expect, test, vi } from 'vitest';

vi.mock('@sourcebot/shared', () => ({
createLogger: () => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
}),
}));

import { buildFileTree, isPathValid, normalizePath } from './utils';

test('normalizePath adds a trailing slash and strips leading slashes', () => {
Expand Down