Skip to content
Open
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
104 changes: 104 additions & 0 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ export async function POST(request: Request) {
const crmContactsTool = toolRegistry.getTool("crm-contacts");
const googleMeetTool = toolRegistry.getTool("google-meet");
const slackTool = toolRegistry.getTool("slack");
const linkedInTool = toolRegistry.getTool("linkedin");

// Define the tools object
const toolsObject: any = {
Expand Down Expand Up @@ -907,6 +908,109 @@ export async function POST(request: Request) {
}
},
},
// Add LinkedIn tool
useLinkedIn: {
description:
"Interact with LinkedIn - send messages, create posts, and manage connections",
parameters: z.object({
action: z.enum([
"send_message",
"create_post",
"send_connection",
"search_people",
]),
recipientEmail: z
.string()
.optional()
.describe(
"Email of the LinkedIn recipient when sending messages or connection requests"
),
recipientId: z
.string()
.optional()
.describe("LinkedIn ID of the recipient when sending messages"),
message: z
.string()
.optional()
.describe("Message content for messages or connection requests"),
text: z.string().optional().describe("Content for LinkedIn posts"),
visibility: z
.enum(["public", "connections", "group"])
.optional()
.describe("Visibility setting for posts"),
profileUrl: z
.string()
.optional()
.describe("LinkedIn profile URL for connection requests"),
query: z
.string()
.optional()
.describe("Search query for finding people on LinkedIn"),
limit: z.number().optional().describe("Limit for search results"),
}),
execute: async (data: any) => {
try {
if (!linkedInTool) {
return {
success: false,
error: "LinkedIn tool not available",
ui: {
type: "connection_required",
service: "linkedin",
message:
"Please connect your LinkedIn account to use this feature",
connectButton: {
text: "Connect LinkedIn",
action: "connection://linkedin",
},
},
};
}

// Transform the flat parameters into the expected LinkedIn action format
let linkedInAction: any = { action: data.action };

// Add appropriate data based on action type
if (data.action === "send_message") {
linkedInAction.data = {
recipientEmail: data.recipientEmail,
recipientId: data.recipientId,
message: data.message,
};
} else if (data.action === "create_post") {
linkedInAction.data = {
text: data.text,
visibility: data.visibility,
};
} else if (data.action === "send_connection") {
linkedInAction.data = {
email: data.recipientEmail,
profileUrl: data.profileUrl,
message: data.message,
};
} else if (data.action === "search_people") {
linkedInAction.query = data.query;
linkedInAction.limit = data.limit;
}

return await linkedInTool.execute(userId, linkedInAction);
} catch (err) {
console.error("Error using LinkedIn:", err);
return {
success: false,
error:
err instanceof Error
? err.message
: "Unknown error using LinkedIn",
ui: {
type: "error",
message:
"There was an error with the LinkedIn operation. Please try again later.",
},
};
}
},
},
};

// Add calendar tool if available
Expand Down
6 changes: 6 additions & 0 deletions app/connections/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ export default function ConnectionsPage() {
icon: "/logos/slack-logo.svg",
connected: false,
},
{
id: "linkedin",
name: "LinkedIn",
icon: "/logos/linkedin-logo.png",
connected: false,
},
]);

// Function to fetch connections
Expand Down
59 changes: 55 additions & 4 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ type PromptType =
| "slack"
| "summarize-deal"
| "create-google-meet"
| "add-custom-tool";
| "add-custom-tool"
| "linkedin";

interface PromptExplanation {
title: string;
Expand Down Expand Up @@ -199,15 +200,55 @@ const promptExplanations: Record<PromptType, PromptExplanation> = {
],
apis: ["Slack API"],
},
linkedin: {
title: "LinkedIn Integration",
description: "Access LinkedIn content and interaction data",
logo: "/logos/linkedin-logo.png",
examples: [
"Find my recent LinkedIn posts",
"Show me the engagement stats on my last LinkedIn post",
"Check who interacted with my LinkedIn document",
"Get a summary of my LinkedIn content performance",
"View my LinkedIn media uploads from the past month",
],
steps: [
{
title: "User Requests LinkedIn Information",
description:
"The user asks to retrieve information about their LinkedIn content and interactions.",
},
{
title: "Authentication Check",
description:
"The assistant verifies the user is authenticated and has connected LinkedIn via OAuth with w_member_social scope.",
},
{
title: "LinkedIn API Access",
description:
"Using the stored OAuth token from Descope, the assistant makes a secure API call to LinkedIn's content endpoints.",
},
{
title: "Data Retrieval",
description:
"The assistant retrieves the requested information about posts, media, or interaction statistics.",
},
{
title: "Information Presentation",
description:
"The assistant presents the retrieved LinkedIn data in a clear, organized format.",
},
],
apis: ["LinkedIn Posts API", "LinkedIn Media API"],
},
"summarize-deal": {
title: "Summarize Deal to Google Docs",
description:
"Create comprehensive deal summaries and save them directly to Google Docs for sharing and collaboration",
logo: "/logos/google-docs.png",
examples: [
"Summarize the Enterprise Software License deal with John Doe",
"Create a deal report for the Cloud Migration Project with Jane Lane",
"Generate a summary of the IT Infrastructure Upgrade deal with Michael Chen",
// "Summarize the Enterprise Software License deal with John Doe",
// "Create a deal report for the Cloud Migration Project with Jane Lane",
// "Generate a summary of the IT Infrastructure Upgrade deal with Michael Chen",
],
steps: [
{
Expand Down Expand Up @@ -1053,6 +1094,16 @@ export default function Home() {
)
),
},
{
id: "linkedin",
title: "LinkedIn Integration",
description: "Access LinkedIn content and interaction data",
logo: "/logos/linkedin-logo.png",
action: () =>
checkOAuthAndPrompt(() =>
usePredefinedPrompt("Find my recent LinkedIn posts", "linkedin")
),
},
{
id: "add-custom-tool",
title: "Add Your Own Tool",
Expand Down
7 changes: 7 additions & 0 deletions components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ export default function Chat({
if (content.includes("meet")) service = "google-meet";
if (content.includes("slack")) service = "slack";
if (content.includes("zoom")) service = "zoom";
if (content.includes("linkedin")) service = "linkedin";

isReconnect =
content.includes("additional permission") ||
Expand Down Expand Up @@ -539,6 +540,8 @@ export default function Chat({
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/meetings.space.created"
);
} else if (service === "linkedin") {
requiredScopes.push("r_emailaddress", "r_liteprofile", "w_member_social");
}

let messageText = isReconnect
Expand Down Expand Up @@ -579,6 +582,8 @@ export default function Chat({
return "Slack";
case "zoom":
return "Zoom";
case "linkedin":
return "LinkedIn";
default:
return provider.replace(/-/g, " ");
}
Expand Down Expand Up @@ -644,6 +649,7 @@ export default function Chat({
else if (serviceName.includes("crm")) service = "custom-crm";
else if (serviceName.includes("slack")) service = "slack";
else if (serviceName.includes("zoom")) service = "zoom";
else if (serviceName.includes("linkedin")) service = "linkedin";

message = {
...message,
Expand Down Expand Up @@ -706,6 +712,7 @@ export default function Chat({
message.content.toLowerCase().includes("documents") ||
message.content.toLowerCase().includes("drive") ||
message.content.toLowerCase().includes("slack") ||
message.content.toLowerCase().includes("linkedin") ||
message.content.toLowerCase().includes("meet") ||
message.content.toLowerCase().includes("google meet"))) ||
message.content.includes("](connection:") ||
Expand Down
6 changes: 6 additions & 0 deletions components/user-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ export default function UserMenu({ onProfileClick }: UserMenuProps) {
icon: "/logos/slack-logo.svg",
connected: false,
},
{
id: "linkedin",
name: "LinkedIn",
icon: "/logos/linkedin-logo.png",
connected: false,
},
]);
const [isLoading, setIsLoading] = useState(false);
const [isConnecting, setIsConnecting] = useState<string | null>(null);
Expand Down
19 changes: 19 additions & 0 deletions hooks/use-connection-notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@ const providers = {
"team chat",
],
},
linkedin: {
id: "linkedin",
name: "LinkedIn",
icon: "/logos/linkedin-logo.png",
scopes: ["w_member_social"],
keywords: [
"linkedin",
"post",
"share",
"upload",
"image",
"document",
"content",
"professional",
"article",
"media",
"announcement",
],
},
};

type ProviderKey = keyof typeof providers;
Expand Down
1 change: 1 addition & 0 deletions lib/oauth-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const DEFAULT_SCOPES: Record<string, string[]> = {
],
"custom-crm": ["openid", "contacts:read", "deals:read"],
slack: ["chat:write", "channels:manage", "users:read"],
linkedin: ["r_emailaddress", "r_basicprofile", "w_member_social"],
};

export async function getOAuthTokenWithScopeValidation(
Expand Down
8 changes: 7 additions & 1 deletion lib/openapi-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,20 @@ export async function getRequiredScopes(
"custom-crm": {
"contacts.list": ["contacts.read"],
"deals.list": ["deals.read"],
connect: ["openid", "contacts:read", "deals:read"], // Both for connection
connect: ["openid", "contacts:read", "deals:read"],
},
slack: {
"chat:write": ["chat:write"],
"channels:manage": ["channels:manage"],
"users:read": ["users:read"],
connect: ["chat:write", "channels:manage", "users:read"],
},
linkedin: {
r_emailaddress: ["r_emailaddress"],
r_basicprofile: ["r_basicprofile"],
w_member_social: ["w_member_social"],
connect: ["r_emailaddress", "r_basicprofile", "w_member_social"],
},
};

// Check if we have scopes for this specific operation
Expand Down
5 changes: 4 additions & 1 deletion lib/tools/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,8 @@ export type OAuthProvider =
| "google-meet"
| "custom-crm"
| "slack"
| "zoom";
| "zoom"
| "linkedin";

// Create standardized connection request for OAuth providers
export function createConnectionRequest(options: {
Expand Down Expand Up @@ -446,6 +447,8 @@ export function createConnectionRequest(options: {
return "Slack";
case "zoom":
return "Zoom";
case "linkedin":
return "LinkedIn";
default:
return String(provider).replace(/-/g, " ");
}
Expand Down
1 change: 1 addition & 0 deletions lib/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "./calendar";
import "./calendar-list";
import "./google-meet";
import "./slack";
import "./linkedin";

// Export the tool registry for direct access
export { toolRegistry } from "./base";
Loading