-
Notifications
You must be signed in to change notification settings - Fork 54
Feat: Automatically organize tabs #198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
theUpsider
wants to merge
2
commits into
browseros-ai:main
Choose a base branch
from
theUpsider:theupsider/organizetabs
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { z } from "zod" | ||
| import { DynamicStructuredTool } from "@langchain/core/tools" | ||
| import { ExecutionContext } from "@/lib/runtime/ExecutionContext" | ||
| import { toolSuccess, toolError, type ToolOutput } from "@/lib/tools/ToolInterface" | ||
| import { PubSub } from "@/lib/pubsub" | ||
|
|
||
| // Input schema for adding a tab to a group | ||
| export const AddTabToGroupInputSchema = z.object({ | ||
| tabId: z.number().describe("The ID of the tab to add to a group"), | ||
| groupId: z.number().describe("The ID of the existing group to add the tab to") | ||
| }) | ||
|
|
||
| export type AddTabToGroupInput = z.infer<typeof AddTabToGroupInputSchema> | ||
|
|
||
| export class AddTabToGroupToolImpl { | ||
| constructor(private executionContext: ExecutionContext) {} | ||
|
|
||
| async execute(input: AddTabToGroupInput): Promise<ToolOutput> { | ||
| try { | ||
| this.executionContext.getPubSub().publishMessage( | ||
| PubSub.createMessage(`Adding tab ${input.tabId} to group ${input.groupId}`, 'thinking') | ||
| ) | ||
|
|
||
| // Validate the tab exists | ||
| const tab = await chrome.tabs.get(input.tabId) | ||
| if (!tab) { | ||
| return toolError(`Tab with ID ${input.tabId} not found`) | ||
| } | ||
|
|
||
| // Validate the group exists | ||
| const groups = await chrome.tabGroups.query({}) | ||
| const targetGroup = groups.find(g => g.id === input.groupId) | ||
| if (!targetGroup) { | ||
| return toolError(`Group with ID ${input.groupId} not found`) | ||
| } | ||
|
|
||
| // Add the tab to the group | ||
| await chrome.tabs.group({ | ||
| groupId: input.groupId, | ||
| tabIds: [input.tabId] | ||
| }) | ||
|
|
||
| const groupName = targetGroup.title || `Group ${input.groupId}` | ||
| return toolSuccess(`Added tab "${tab.title}" to group "${groupName}"`) | ||
|
|
||
| } catch (error) { | ||
| return toolError(`Failed to add tab to group: ${error instanceof Error ? error.message : String(error)}`) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // LangChain wrapper factory function | ||
| export function AddTabToGroupTool(executionContext: ExecutionContext): DynamicStructuredTool { | ||
| const tool = new AddTabToGroupToolImpl(executionContext) | ||
|
|
||
| return new DynamicStructuredTool({ | ||
| name: "add_tab_to_group", | ||
| description: "Add a specific tab to an existing tab group. Use this to move a tab into a group. Requires the tab ID and the group ID.", | ||
| schema: AddTabToGroupInputSchema, | ||
| func: async (args): Promise<string> => { | ||
| const result = await tool.execute(args) | ||
| return JSON.stringify(result) | ||
| } | ||
| }) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import { z } from "zod" | ||
| import { DynamicStructuredTool } from "@langchain/core/tools" | ||
| import { ExecutionContext } from "@/lib/runtime/ExecutionContext" | ||
| import { toolSuccess, toolError, type ToolOutput } from "@/lib/tools/ToolInterface" | ||
| import { PubSub } from "@/lib/pubsub" | ||
|
|
||
| // Constants | ||
| const VALID_COLORS = ["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"] as const | ||
| const DEFAULT_COLOR = "blue" | ||
|
|
||
| // Input schema for creating a tab group | ||
| export const CreateTabGroupInputSchema = z.object({ | ||
| name: z.string().min(1).max(50).describe("Name for the new tab group"), | ||
| color: z.enum(VALID_COLORS).default(DEFAULT_COLOR).describe("Color for the tab group (grey, blue, red, yellow, green, pink, purple, cyan, orange)"), | ||
| tabIds: z.array(z.number()).min(1).optional().describe("Optional: Tab IDs to add to the group immediately") | ||
| }) | ||
|
|
||
| export type CreateTabGroupInput = z.infer<typeof CreateTabGroupInputSchema> | ||
|
|
||
| export class CreateTabGroupToolImpl { | ||
| constructor(private executionContext: ExecutionContext) {} | ||
|
|
||
| async execute(input: CreateTabGroupInput): Promise<ToolOutput> { | ||
| try { | ||
| this.executionContext.getPubSub().publishMessage( | ||
| PubSub.createMessage(`Creating tab group "${input.name}"`, 'thinking') | ||
| ) | ||
|
|
||
| // If no tabIds provided, we need at least one tab to create a group | ||
| // Chrome requires at least one tab to create a group | ||
| let tabIdsToGroup = input.tabIds || [] | ||
|
|
||
| if (tabIdsToGroup.length === 0) { | ||
| // Get current tab as default | ||
| const currentTab = await chrome.tabs.getCurrent() | ||
| if (currentTab?.id) { | ||
| tabIdsToGroup = [currentTab.id] | ||
| } else { | ||
| // Get any tab from current window | ||
| const tabs = await chrome.tabs.query({ currentWindow: true }) | ||
| if (tabs.length > 0 && tabs[0].id) { | ||
| tabIdsToGroup = [tabs[0].id] | ||
| } else { | ||
| return toolError("Cannot create tab group: No tabs available") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Validate tab IDs exist | ||
| const tabs = await chrome.tabs.query({}) | ||
| const existingTabIds = new Set(tabs.map(t => t.id)) | ||
| const validTabIds = tabIdsToGroup.filter(id => existingTabIds.has(id)) | ||
|
|
||
| if (validTabIds.length === 0) { | ||
| return toolError(`No valid tabs found with IDs: ${tabIdsToGroup.join(", ")}`) | ||
| } | ||
|
|
||
| // Create the group | ||
| const groupId = await chrome.tabs.group({ tabIds: validTabIds }) | ||
|
|
||
| // Update group properties | ||
| await chrome.tabGroups.update(groupId, { | ||
| title: input.name, | ||
| color: input.color | ||
| }) | ||
|
|
||
| const tabCount = validTabIds.length | ||
| const tabText = tabCount === 1 ? "tab" : "tabs" | ||
|
|
||
| return toolSuccess(`Created tab group "${input.name}" (ID: ${groupId}) with ${tabCount} ${tabText}`) | ||
|
|
||
| } catch (error) { | ||
| return toolError(`Failed to create tab group: ${error instanceof Error ? error.message : String(error)}`) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // LangChain wrapper factory function | ||
| export function CreateTabGroupTool(executionContext: ExecutionContext): DynamicStructuredTool { | ||
| const tool = new CreateTabGroupToolImpl(executionContext) | ||
|
|
||
| return new DynamicStructuredTool({ | ||
| name: "create_tab_group", | ||
| description: "Create a new tab group with a name and color. Optionally add specific tabs to it immediately. Returns the group ID.", | ||
| schema: CreateTabGroupInputSchema, | ||
| func: async (args): Promise<string> => { | ||
| const result = await tool.execute(args) | ||
| return JSON.stringify(result) | ||
| } | ||
| }) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import { z } from "zod" | ||
| import { DynamicStructuredTool } from "@langchain/core/tools" | ||
| import { ExecutionContext } from "@/lib/runtime/ExecutionContext" | ||
| import { toolSuccess, type ToolOutput } from "@/lib/tools/ToolInterface" | ||
| import { PubSub } from "@/lib/pubsub" | ||
|
|
||
| // Input schema (empty for list operation) | ||
| export const ListTabGroupsInputSchema = z.object({}) | ||
|
|
||
| export type ListTabGroupsInput = z.infer<typeof ListTabGroupsInputSchema> | ||
|
|
||
| interface TabGroupInfo { | ||
| id: number | ||
| title: string | ||
| color: string | ||
| collapsed: boolean | ||
| windowId: number | ||
| } | ||
|
|
||
| export class ListTabGroupsToolImpl { | ||
| constructor(private executionContext: ExecutionContext) {} | ||
|
|
||
| async execute(input: ListTabGroupsInput): Promise<ToolOutput> { | ||
| try { | ||
| this.executionContext.getPubSub().publishMessage( | ||
| PubSub.createMessage("Listing tab groups...", 'thinking') | ||
| ) | ||
|
|
||
| // Get all tab groups | ||
| const groups = await chrome.tabGroups.query({}) | ||
|
|
||
| if (groups.length === 0) { | ||
| return toolSuccess("No tab groups found") | ||
| } | ||
|
|
||
| // Format group information | ||
| const groupsInfo: TabGroupInfo[] = groups.map(group => ({ | ||
| id: group.id, | ||
| title: group.title || `Unnamed Group ${group.id}`, | ||
| color: group.color, | ||
| collapsed: group.collapsed, | ||
| windowId: group.windowId | ||
| })) | ||
|
|
||
| // Get tab count for each group | ||
| const groupsWithCounts = await Promise.all( | ||
| groupsInfo.map(async (group) => { | ||
| const tabsInGroup = await chrome.tabs.query({ groupId: group.id }) | ||
| return { | ||
| ...group, | ||
| tabCount: tabsInGroup.length | ||
| } | ||
| }) | ||
| ) | ||
|
|
||
| return toolSuccess(JSON.stringify(groupsWithCounts, null, 2)) | ||
|
|
||
| } catch (error) { | ||
| return toolSuccess("[]") // Return empty array on error | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // LangChain wrapper factory function | ||
| export function ListTabGroupsTool(executionContext: ExecutionContext): DynamicStructuredTool { | ||
| const tool = new ListTabGroupsToolImpl(executionContext) | ||
|
|
||
| return new DynamicStructuredTool({ | ||
| name: "list_tab_groups", | ||
| description: "List all existing tab groups with their IDs, names, colors, and tab counts. Use this to find group IDs before adding tabs to groups.", | ||
| schema: ListTabGroupsInputSchema, | ||
| func: async (args): Promise<string> => { | ||
| const result = await tool.execute(args) | ||
| return JSON.stringify(result) | ||
| } | ||
| }) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.