Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion src/ButtonModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class ButtonModule {
createMiniSettingsButton(container, position = 0) {
const button = this.createIconButton({
id: 'saypi-settingsButton',
label: getMessage("extensionSettings"),
label: getMessage("voiceSettings"),
icon: settingsIconSVG,
onClick: () => openSettings(),
className: 'settings-button'
Expand Down
71 changes: 34 additions & 37 deletions src/chatbots/Claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,56 +220,53 @@ class ClaudeChatbot extends AbstractChatbot {
}

getSidebarConfig(sidebar: HTMLElement): SidebarConfig | null {
const actionSelectors = [
'[data-testid="new-chat-button"]',
'[data-testid="navigation-link-new-chat"]',
'[data-testid="create-new-chat-button"]',
'[aria-label="New chat"]',
'a[href="/new"]',
'a[href="/chat/new"]',
];

const newChatButton = actionSelectors
.map((selector) => sidebar.querySelector(selector) as HTMLElement | null)
.find((element) => element !== null) as HTMLElement | null;

const collectCandidateContainers = () => {
const candidates: HTMLElement[] = [];
if (newChatButton) {
let ancestor = newChatButton.parentElement as HTMLElement | null;
while (ancestor && ancestor !== sidebar) {
candidates.push(ancestor);
ancestor = ancestor.parentElement as HTMLElement | null;
// Claude's sidebar structure (Jan 2025):
// - "New chat" is in its own container above the scrollable area
// - "Chats, Projects, Artifacts, Code" are in a flex column inside the scrollable area
// We want to insert our button after "Code" in that menu container

// Strategy: Find the container that has menu items like "Chats", "Projects", "Artifacts", "Code"
// These are direct children divs that contain anchor or button elements with those labels
const findMenuContainer = (): HTMLElement | null => {
// Look for a flex column container that has the navigation items
const candidates = Array.from(sidebar.querySelectorAll('div.flex.flex-col'));

for (const candidate of candidates) {
// Check if this container has children that look like menu items
const children = Array.from(candidate.children);
if (children.length < 3) continue;

// Look for recognizable menu items (Chats, Projects, Artifacts, Code)
const menuLabels = ['chats', 'projects', 'artifacts', 'code'];
const foundLabels = children.filter(child => {
const text = child.textContent?.toLowerCase().trim() || '';
return menuLabels.some(label => text === label);
});

// If we found at least 3 of the expected menu items, this is our container
if (foundLabels.length >= 3) {
return candidate as HTMLElement;
}
}
const allDivs = Array.from(sidebar.querySelectorAll('div')).map((div) => div as HTMLElement);
return [...candidates, ...allDivs];

return null;
};

const menuContainer = collectCandidateContainers().find((candidate) => {
const actions = Array.from(candidate.querySelectorAll('a, button, div[role="button"]')).filter((action) => !action.closest('[data-testid="user-profile"]'));
return actions.length >= 3;
}) || null;
const menuContainer = findMenuContainer();

if (!menuContainer) {
console.warn('[Claude] sidebar: Could not find menu container');
console.warn('[Claude] sidebar: Could not find menu container with navigation items');
return null;
}

let header: HTMLElement | null = menuContainer;
while (header && header.parentElement && header.parentElement !== sidebar) {
header = header.parentElement as HTMLElement;
}

if (!header || header.parentElement !== sidebar) {
console.warn('[Claude] sidebar: Could not find header element');
return null;
}
// Insert after "Code" which is typically the last item (position 4, 0-indexed)
// Count the actual children to determine insert position
const insertPosition = menuContainer.children.length;

return {
buttonContainer: menuContainer,
buttonStyle: 'menu',
insertPosition: 3,
insertPosition: insertPosition,
};
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/icons/settings.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 33 additions & 11 deletions src/styles/pi.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ html body.pi {
}

/* Pi-specific button sizing for control panel */
/* Use 40x40 button containers to match Pi's native button sizing */
/* Use 36x36 button containers to match Pi's native button sizing (size-9) */
#saypi-control-panel,
.saypi-control-panel {
.saypi-control-button.mini {
width: 40px;
height: 40px;
width: 36px;
height: 36px;
}

/* Match Pi's native brown icon color */
Expand All @@ -30,12 +30,28 @@ html body.pi {
stroke: rgb(107, 98, 85);
}
}

/* Hide outer circle ring on settings icon to match Pi's flat button style */
#saypi-settingsButton svg .outer-circle {
display: none;
}

/* Give settings button visible background to match enter button */
#saypi-settingsButton {
background-color: rgb(237, 225, 209); /* bg-cream-550 */
border-radius: 50%;
}

/* Consistent spacing between SayPi buttons */
#saypi-enter-button {
margin-right: 8px;
}
}

/* Enter focus mode button specific sizing */
#saypi-enter-button {
width: 40px;
height: 40px;
width: 36px;
height: 36px;

svg {
color: rgb(107, 98, 85);
Expand Down Expand Up @@ -153,16 +169,22 @@ html body.pi {
align-self: center;
}

/* Mobile-specific call button sizing for new chat pages */
/* Mobile-specific button sizing to match Pi's native buttons */
@media (max-width: 768px) {
#saypi-callButton {
/* Ensure button is visible and tappable on mobile */
min-width: 2.5rem;
min-height: 2.5rem;
width: 2.5rem;
height: 2.5rem;
/* Match Pi's native submit button size (size-9 = 36px = 2.25rem) */
min-width: 2.25rem;
min-height: 2.25rem;
width: 2.25rem;
height: 2.25rem;
flex-shrink: 0; /* Prevent button from shrinking in flex container */
}

/* Ensure SayPi toolbar buttons have visible background to match native neighbors */
#saypi-enter-button,
#saypi-settingsButton {
background-color: rgb(237, 225, 209); /* bg-cream-550 */
}
}

/* Sidebar menu button styling to match Pi's native buttons */
Expand Down
Loading