Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
28 changes: 14 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,54 @@

### Bug Fixes

* **security:** add SSRF protection to WebCache fetch_from_network ([ed5999b](https://github.com/iblai/mentorai/commit/ed5999bb45f7ace1dac1d8054ac0e44c997f333e))
- **security:** add SSRF protection to WebCache fetch_from_network ([ed5999b](https://github.com/iblai/mentorai/commit/ed5999bb45f7ace1dac1d8054ac0e44c997f333e))

## [0.47.7](https://github.com/iblai/mentorai/compare/v0.47.6...v0.47.7) (2026-04-19)

### Chores

* **deps:** cargo update all Tauri dependencies ([d1b0bbc](https://github.com/iblai/mentorai/commit/d1b0bbcb5a6f9da2b09724caad84090ea529b3da))
- **deps:** cargo update all Tauri dependencies ([d1b0bbc](https://github.com/iblai/mentorai/commit/d1b0bbcb5a6f9da2b09724caad84090ea529b3da))

## [0.47.6](https://github.com/iblai/mentorai/compare/v0.47.5...v0.47.6) (2026-04-19)

### Bug Fixes

* **deps:** update bytes 1.11.0 -> 1.11.1 in Cargo.lock ([68915ec](https://github.com/iblai/mentorai/commit/68915ecf851a1f12834ba036bf6099a7131ddf92)), closes [#46](https://github.com/iblai/mentorai/issues/46)
- **deps:** update bytes 1.11.0 -> 1.11.1 in Cargo.lock ([68915ec](https://github.com/iblai/mentorai/commit/68915ecf851a1f12834ba036bf6099a7131ddf92)), closes [#46](https://github.com/iblai/mentorai/issues/46)

## [0.47.5](https://github.com/iblai/mentorai/compare/v0.47.4...v0.47.5) (2026-04-19)

### Bug Fixes

* **deps:** fix misspelled lodash/lodash-es overrides ([bc615f2](https://github.com/iblai/mentorai/commit/bc615f2042e2885be3598b587d9f9c3d36d4af84))
* **deps:** remove bogus npm overrides and update Cargo.lock ([b95371a](https://github.com/iblai/mentorai/commit/b95371ae481015e969768f731ca99f016637537e))
- **deps:** fix misspelled lodash/lodash-es overrides ([bc615f2](https://github.com/iblai/mentorai/commit/bc615f2042e2885be3598b587d9f9c3d36d4af84))
- **deps:** remove bogus npm overrides and update Cargo.lock ([b95371a](https://github.com/iblai/mentorai/commit/b95371ae481015e969768f731ca99f016637537e))

## [0.47.4](https://github.com/iblai/mentorai/compare/v0.47.3...v0.47.4) (2026-04-19)

### Bug Fixes

* **deps:** add more security overrides for transitive vulnerabilities ([a0c6632](https://github.com/iblai/mentorai/commit/a0c663287807762c535b9b7cd11c47a4519299ee))
* **deps:** add security overrides and bump next to 15.5.15 ([ebdf831](https://github.com/iblai/mentorai/commit/ebdf831a546d1cca34654dd4efa25cc3465ee91c))
* **deps:** override basic-ftp and follow-redirects for security patches ([22e5b4d](https://github.com/iblai/mentorai/commit/22e5b4d68244fcb2a41e20322da9aef61957a555)), closes [#143](https://github.com/iblai/mentorai/issues/143)
- **deps:** add more security overrides for transitive vulnerabilities ([a0c6632](https://github.com/iblai/mentorai/commit/a0c663287807762c535b9b7cd11c47a4519299ee))
- **deps:** add security overrides and bump next to 15.5.15 ([ebdf831](https://github.com/iblai/mentorai/commit/ebdf831a546d1cca34654dd4efa25cc3465ee91c))
- **deps:** override basic-ftp and follow-redirects for security patches ([22e5b4d](https://github.com/iblai/mentorai/commit/22e5b4d68244fcb2a41e20322da9aef61957a555)), closes [#143](https://github.com/iblai/mentorai/issues/143)

### Chores

* **release:** v0.47.3 ([438c209](https://github.com/iblai/mentorai/commit/438c2097e154f718ee94c099dfc1d191865243df))
- **release:** v0.47.3 ([438c209](https://github.com/iblai/mentorai/commit/438c2097e154f718ee94c099dfc1d191865243df))

* fix(deps): add security overrides and bump next to 15.5.15 (ebdf831)
* fix(deps): override basic-ftp and follow-redirects for security patches (22e5b4d)
- fix(deps): add security overrides and bump next to 15.5.15 (ebdf831)
- fix(deps): override basic-ftp and follow-redirects for security patches (22e5b4d)

## [0.47.3](https://github.com/iblai/mentorai/compare/v0.47.2...v0.47.3) (2026-04-19)

### Chores

* bump nextjs version ([1a650f0](https://github.com/iblai/mentorai/commit/1a650f01e1eed67700a0d7b08b965b69e3e566c0))
- bump nextjs version ([1a650f0](https://github.com/iblai/mentorai/commit/1a650f01e1eed67700a0d7b08b965b69e3e566c0))

## [0.47.2](https://github.com/iblai/mentorai/compare/v0.47.1...v0.47.2) (2026-04-19)

### Chores

* bump next from 15.3.6 to 15.5.14 ([dfd1435](https://github.com/iblai/mentorai/commit/dfd143506c2beb60c809da3642646346dad358a3))
* update pnpm-lock.yaml ([ec9cf09](https://github.com/iblai/mentorai/commit/ec9cf094cc68fa5ac0ff696c63359cb63397084a))
- bump next from 15.3.6 to 15.5.14 ([dfd1435](https://github.com/iblai/mentorai/commit/dfd143506c2beb60c809da3642646346dad358a3))
- update pnpm-lock.yaml ([ec9cf09](https://github.com/iblai/mentorai/commit/ec9cf094cc68fa5ac0ff696c63359cb63397084a))

## [0.47.1](https://github.com/iblai/mentorai/compare/v0.47.0...v0.47.1) (2026-04-19)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,38 @@ vi.mock('@iblai/iblai-js/web-containers', () => ({
),
}));

// Chat-slice selectors used by the session-share button (issue #645).
let mockSessionId = 'session-123';
let mockShowingSharedChat = false;
let mockActiveChatMessages: Array<{ role: string; content: string }> = [
{ role: 'assistant', content: 'Welcome!' },
{ role: 'user', content: 'Hi' },
];

vi.mock('@iblai/iblai-js/web-utils', async (importOriginal) => {
const actual = await importOriginal<any>();
return {
...actual,
selectSessionId: () => mockSessionId,
selectShowingSharedChat: () => mockShowingSharedChat,
selectActiveChatMessages: () => mockActiveChatMessages,
};
});

vi.mock('@/components/chat/ai-message-share', () => ({
AIMessageShare: ({
sessionId,
tenantKey,
}: {
sessionId: string;
tenantKey: string;
}) => (
<button data-testid="session-share-button">
Share: {sessionId} ({tenantKey})
</button>
),
}));

vi.mock('@iblai/iblai-js/web-containers/next', () => ({
UserProfileModal: ({
isOpen,
Expand Down Expand Up @@ -445,6 +477,12 @@ describe('NavBar', () => {
mockUserIsStudent = false;
mockIsVisiting = false;
mockIsAccessingPublicRoute = false;
mockSessionId = 'session-123';
mockShowingSharedChat = false;
mockActiveChatMessages = [
{ role: 'assistant', content: 'Welcome!' },
{ role: 'user', content: 'Hi' },
];
mockMentorSettings = {
mentor: 'Test Mentor',
mentor_id: 123,
Expand Down Expand Up @@ -800,6 +838,138 @@ describe('NavBar', () => {
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
});
});

// --------------------------------------------------------------------------
// Session-level share button (issue #645)
//
// Share lives on the navbar (not per-message) and is visible only when
// there is a real conversation to share. The first message is often an AI
// welcome — it's skipped so an empty session can't be shared.
// --------------------------------------------------------------------------

describe('Session-level share button (issue #645)', () => {
it('renders the share button when there is a real user message', () => {
mockActiveChatMessages = [
{ role: 'assistant', content: 'Welcome!' },
{ role: 'user', content: 'Hello' },
];
const store = createTestStore();

render(
<Provider store={store}>
<NavBar />
</Provider>,
);

expect(screen.getByTestId('session-share-button')).toHaveTextContent(
'Share: session-123 (tenant123)',
);
});

it('hides the share button when the only message is the AI welcome', () => {
mockActiveChatMessages = [{ role: 'assistant', content: 'Welcome!' }];
const store = createTestStore();

render(
<Provider store={store}>
<NavBar />
</Provider>,
);

expect(
screen.queryByTestId('session-share-button'),
).not.toBeInTheDocument();
});

it('hides the share button when there are no messages at all', () => {
mockActiveChatMessages = [];
const store = createTestStore();

render(
<Provider store={store}>
<NavBar />
</Provider>,
);

expect(
screen.queryByTestId('session-share-button'),
).not.toBeInTheDocument();
});

it('hides the share button when viewing a shared chat', () => {
mockShowingSharedChat = true;
const store = createTestStore();

render(
<Provider store={store}>
<NavBar />
</Provider>,
);

expect(
screen.queryByTestId('session-share-button'),
).not.toBeInTheDocument();
});

it('hides the share button when sessionId is empty', () => {
mockSessionId = '';
const store = createTestStore();

render(
<Provider store={store}>
<NavBar />
</Provider>,
);

expect(
screen.queryByTestId('session-share-button'),
).not.toBeInTheDocument();
});

it('hides the share button on non-chat pages (e.g. analytics)', () => {
mockPathname = '/platform/tenant123/mentor456/analytics';
const store = createTestStore();

render(
<Provider store={store}>
<NavBar />
</Provider>,
);

expect(
screen.queryByTestId('session-share-button'),
).not.toBeInTheDocument();
});

it('hides the share button on the explore page', () => {
mockPathname = '/platform/tenant123/mentor456/explore';
const store = createTestStore();

render(
<Provider store={store}>
<NavBar />
</Provider>,
);

expect(
screen.queryByTestId('session-share-button'),
).not.toBeInTheDocument();
});

it('treats a user-first message list as a real conversation', () => {
// No leading AI welcome — every message is real.
mockActiveChatMessages = [{ role: 'user', content: 'Hi' }];
const store = createTestStore();

render(
<Provider store={store}>
<NavBar />
</Provider>,
);

expect(screen.getByTestId('session-share-button')).toBeInTheDocument();
});
});
});

// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from '@/components/ui/tooltip';
import { MyMentorsModal } from '@/components/modals/my-mentors-modal';
import { EditMentorModal } from '@/components/modals/edit-mentor-modal';
import { AIMessageShare } from '@/components/chat/ai-message-share';
import { NotificationDropdown } from '@iblai/iblai-js/web-containers';
import { UserProfileModal } from '@iblai/iblai-js/web-containers/next';
import { CreateMentorModal } from '@/components/modals/create-mentor-modal';
Expand Down Expand Up @@ -62,7 +63,11 @@ import { UserProfile } from './user-profile';
import { useSidebar } from '@/components/ui/sidebar';
import { LearnerModeSwitch } from './learner-mode-switch';
import { useShowFreeTrialDialog } from '@/hooks/user-user-actions';
// import { useAdvancedChat } from '@iblai/iblai-js/web-utils';
import {
selectActiveChatMessages,
selectSessionId,
selectShowingSharedChat,
} from '@iblai/iblai-js/web-utils';
import { useAccessingPublicRoute } from '@/hooks/use-anonymous-mentor';
import { useEmbedMode } from '@/hooks/use-embed-mode';
import { EmbedNavBar } from './embed-nav-bar';
Expand Down Expand Up @@ -126,6 +131,9 @@ export function NavBar() {
const [openModal, setOpenModal] = React.useState(false);
const dispatch = useAppDispatch();
const selectedAnalyticsMentor = useAppSelector(selectSelectedMentor);
const sessionId = useAppSelector(selectSessionId);
const showingSharedChat = useAppSelector(selectShowingSharedChat);
const activeChatMessages = useAppSelector(selectActiveChatMessages);
const isAccessingPublicRoute = useAccessingPublicRoute();
const { tenantKey, mentorId } = useParams<TenantKeyMentorIdParams>();
const username = useUsername();
Expand Down Expand Up @@ -360,6 +368,22 @@ export function NavBar() {
!pathname.includes('/explore') &&
!isWorkflowsPage;

// Session-level share button (issue #645).
// Match the chat's own "is there a real conversation?" rule — if the first
// message is the AI welcome, skip it so an empty session (welcome only,
// or just streaming) can't be shared. Sharing a shared chat or an
// anonymous session doesn't make sense either.
const conversationMessages =
activeChatMessages[0]?.role === 'assistant'
? activeChatMessages.slice(1)
: activeChatMessages;
const canShareSession =
isOnChatPage &&
isLoggedIn() &&
!showingSharedChat &&
!!sessionId &&
conversationMessages.length > 0;

const handleAvatarClick = () => {
// Open the mentor menu instead of the profile
setIsMentorListOpen(true);
Expand Down Expand Up @@ -637,6 +661,11 @@ export function NavBar() {
</span>
</div>
)}
{canShareSession && (
<TooltipProvider>
<AIMessageShare sessionId={sessionId} tenantKey={tenantKey} />
</TooltipProvider>
)}
{!embedMode && visibleToLoggedInUsersOnly && (
<NotificationDropdown
org={tenantKey}
Expand Down
25 changes: 3 additions & 22 deletions components/chat/__tests__/ai-message-bubble.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,6 @@ vi.mock('@/components/chat/ai-message-copy', () => ({
),
}));

vi.mock('@/components/chat/ai-message-share', () => ({
AIMessageShare: ({
sessionId,
tenantKey,
}: {
sessionId: string;
tenantKey: string;
}) => (
<button data-testid="ai-message-share">
Share: {sessionId} ({tenantKey})
</button>
),
}));

vi.mock('@/components/chat/ai-message-rating', () => ({
AIMessageRating: () => <div data-testid="ai-message-rating">Rating</div>,
}));
Expand Down Expand Up @@ -216,16 +202,11 @@ describe('AIMessageBubble', () => {
expect(screen.getByTestId('ai-message-copy')).toBeInTheDocument();
});

it('should render share button when not in shared chat', () => {
// Session-level share button was relocated out of per-message actions
// in issue #645 — it now lives at the top of the thread in ChatMessages.
it('should not render a per-message share button (moved session-level, #645)', () => {
renderWithRedux(<AIMessageBubble {...defaultProps} />);
expect(screen.getByTestId('ai-message-share')).toBeInTheDocument();
});

it('should not render share button when in shared chat', () => {
mockShowingSharedChat = true;
renderWithRedux(<AIMessageBubble {...defaultProps} />, true);
expect(screen.queryByTestId('ai-message-share')).not.toBeInTheDocument();
mockShowingSharedChat = false; // Reset for other tests
});

it('should render rating component when logged in and not shared chat', () => {
Expand Down
Loading
Loading