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
112 changes: 103 additions & 9 deletions components/agent-learning-path-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ import {
type ChatMessage,
type ActionData,
} from "@/lib/chat-agent";
import { EnterpriseSignalChain } from "@/lib/enterprise-service";
import {
Briefcase,
ChevronDown,
Sparkles,

Code,
Palette,
Database,
Users
} from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

interface Message {
role: "user" | "assistant";
Expand All @@ -24,6 +43,7 @@ export function LearningPathChat() {
const [isLoading, setIsLoading] = useState(false);
const [sessionId, setSessionId] = useState<string>("");
const [agent, setAgent] = useState<LearningPathAgent | null>(null);
const [selectedRole, setSelectedRole] = useState<string>("default");
const [, forceUpdate] = useState({}); // Force re-render when memory updates
const scrollRef = useRef<HTMLDivElement>(null);

Expand All @@ -36,14 +56,18 @@ export function LearningPathChat() {
try {
// API key is now handled server-side, just pass empty string
const newAgent = new LearningPathAgent(newSessionId);

// Apply default enterprise context
EnterpriseSignalChain.apply(newAgent, "default");

setAgent(newAgent);

// Get initial messages from agent
const agentMessages = newAgent.getMessages();
setMessages(
agentMessages
.filter((m) => m.role !== "system")
.map((m) => ({
.filter((m: ChatMessage) => m.role !== "system")
.map((m: ChatMessage) => ({
role: m.role as "user" | "assistant",
content: m.content,
})),
Expand Down Expand Up @@ -150,6 +174,29 @@ export function LearningPathChat() {
return `Gathering: ${missing.join(", ")}`;
};

const handleRoleChange = (roleId: string) => {
if (!agent) return;
setSelectedRole(roleId);
EnterpriseSignalChain.apply(agent, roleId);

// Add a system-like message to the UI to show the context change
const roleNames: Record<string, string> = {
"sr-frontend-123": "Senior Frontend Developer",
"jr-ux-456": "Junior UI/UX Designer",
"backend-ai-789": "Backend Engineer",
"cs-manager-000": "Customer Success Manager",
"default": "Default Profile"
};

setMessages(prev => [...prev, {
role: "assistant",
content: `*System: Enterprise context switched to ${roleNames[roleId]}. The agent now has access to your internal growth goals and history.*`
}]);

// Force update to refresh the profile panel with seeded memory
forceUpdate({});
};
Comment on lines +177 to +198
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When switching enterprise contexts using handleRoleChange, the conversation history is not reset. This means that if a user switches from one persona to another, the chat history will contain messages from both personas mixed together. Consider clearing the chat messages or creating a new agent instance when switching personas to avoid confusion and ensure clean conversation context for each role.

Copilot uses AI. Check for mistakes.

return (
<div className="flex gap-4 w-full max-w-6xl mx-auto">
{/* Left Panel - Profile Form */}
Expand Down Expand Up @@ -198,7 +245,7 @@ export function LearningPathChat() {
Skills You Have
</label>
<div className="flex flex-wrap gap-1.5">
{agent.getMemory().relevant_skills.map((skill, idx) => (
{agent.getMemory().relevant_skills.map((skill: string, idx: number) => (
<span
key={idx}
className="text-xs bg-green-500/10 text-green-700 dark:text-green-400 px-2 py-1 rounded-full"
Expand All @@ -217,7 +264,7 @@ export function LearningPathChat() {
Skills to Learn
</label>
<div className="flex flex-wrap gap-1.5">
{agent.getMemory().required_skills.map((skill, idx) => (
{agent.getMemory().required_skills.map((skill: string, idx: number) => (
<span
key={idx}
className="text-xs bg-primary/10 text-primary px-2 py-1 rounded-full"
Expand Down Expand Up @@ -258,11 +305,58 @@ export function LearningPathChat() {
{/* Right Panel - Chat */}
<Card className="flex flex-col h-[600px] flex-1">
{/* Header */}
<div className="border-b p-4">
<h2 className="text-xl font-semibold">Learning Path Advisor</h2>
<p className="text-sm text-muted-foreground mt-1">
Chat with the AI to build your learning path
</p>
<div className="border-b p-4 flex justify-between items-center bg-muted/30">
<div>
<h2 className="text-xl font-semibold flex items-center gap-2">
<Sparkles className="h-5 w-5 text-primary" />
Learning Path Advisor
</h2>
<p className="text-sm text-muted-foreground">
Personalized with Enterprise Context
</p>
</div>

<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="gap-2 bg-background/50 backdrop-blur-sm border-primary/20 hover:border-primary/50 transition-all">
<Briefcase className="h-4 w-4 text-primary" />
<span className="font-medium">
{selectedRole === "default" ? "Demo Persona" : {
"sr-frontend-123": "Senior Frontend",
"jr-ux-456": "Junior UI/UX",
"backend-ai-789": "Backend Engineer",
"cs-manager-000": "Customer Success"
}[selectedRole] || "Demo Persona"}
</span>
<ChevronDown className="h-4 w-4 opacity-50" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel>Switch Enterprise Context</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => handleRoleChange("sr-frontend-123")}>
<Code className="mr-2 h-4 w-4" />
<span>Senior Frontend</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleRoleChange("jr-ux-456")}>
<Palette className="mr-2 h-4 w-4" />
<span>Junior UI/UX</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleRoleChange("backend-ai-789")}>
<Database className="mr-2 h-4 w-4" />
<span>Backend Engineer</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleRoleChange("cs-manager-000")}>
<Users className="mr-2 h-4 w-4" />
<span>Customer Success</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => handleRoleChange("default")}>
<User className="mr-2 h-4 w-4" />
<span>Default Profile</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>

{/* Messages */}
Expand Down
11 changes: 10 additions & 1 deletion lib/chat-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const EXTRACTION_SCHEMA = {
export class LearningPathAgent {
private state: AgentState;
private actions: AgentActions;
private enterpriseContext: string = "";

constructor(sessionId: string) {
this.state = new AgentState(sessionId);
Expand All @@ -103,6 +104,10 @@ export class LearningPathAgent {
}
}

public setEnterpriseContext(context: string): void {
this.enterpriseContext = context;
}

private getSystemPrompt(): string {
const base = `You are an expert AI learning advisor that helps users build personalized learning paths.

Expand All @@ -120,7 +125,11 @@ Guidelines:
- The learning path should have 3-6 milestones with specific projects and resources
- Keep the questions to a minimum and only ask for missing information that is essential to creating the minimum learning path`;

const contextParts = this.buildContextParts();
const contextParts = [
...this.buildContextParts(),
this.enterpriseContext ? `INTERNAL DATA: ${this.enterpriseContext}` : "",
].filter(Boolean);

const context =
"\n\nCurrent context:\n" + contextParts.map((p) => `- ${p}`).join("\n");
return base + context;
Expand Down
100 changes: 100 additions & 0 deletions lib/enterprise-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { LearningPathAgent } from "./chat-agent";

/**
* Enterprise Signal Chain
* This service manages mock "internal company" data.
* It's designed to be a separate layer ("Signal") that can be plugged
* into the main Agent without modifying its core logic.
*/

interface InternalUserProfile {
role: string;
focus: string;
certs: string[];
objective?: string;
skills?: string[];
}

// Mock "Internal Company Database"
const COMPANY_DB: Record<string, InternalUserProfile> = {
"sr-frontend-123": {
role: "Senior Frontend Developer",
focus: "Transitioning into Technical Leadership and mastering System Design",
certs: ["Cloud Architecture 101", "Security Best Practices", "Team Management Essentials"],
objective: "Step into a Staff Engineer role by mastering distributed systems and leading cross-functional teams.",
skills: ["React", "TypeScript", "Next.js", "Performance Optimization"]
},
"jr-ux-456": {
role: "Junior UI/UX Designer",
focus: "Mastering Design Systems and Interactive Prototyping in Figma",
certs: ["Accessibility Standards", "Typography & Layout"],
objective: "To become a Lead Product Designer specialized in accessible and inclusive design systems.",
skills: ["Figma", "Sketch", "Prototyping", "User Research"]
},
"backend-ai-789": {
role: "Backend Engineer",
focus: "Integrating LLMs into existing microservices and learning PyTorch",
certs: ["Advanced Python", "Kubernetes Certified"],
objective: "Specialize in AI Engineering to bridge the gap between traditional backend and machine learning models.",
skills: ["Node.js", "Go", "Docker", "PostgreSQL"]
},
"cs-manager-000": {
role: "Customer Success Manager",
focus: "Learning SQL and Data Visualization to build automated client reports",
certs: ["Relationship Management", "Product Analytics"],
objective: "Master data analytics to drive proactive customer success strategies and reduce churn.",
skills: ["Salesforce", "Intercom", "Customer Journey Mapping"]
},
"default": {
role: "Employee",
focus: "General Professional Development",
certs: [],
objective: "",
skills: []
}
};

export class EnterpriseSignalChain {
/**
* Applies internal enterprise data to an agent instance.
* This mimics an automatic fetch from a backend corporate DB on session start.
*/
static apply(agent: LearningPathAgent, sessionId: string): void {
const internalData = COMPANY_DB[sessionId] || COMPANY_DB["default"];

if (internalData) {
const signal = `
Current Role: ${internalData.role}
Growth Focus: ${internalData.focus}
Internal Certifications: ${internalData.certs.join(", ")}
`.trim();

agent.setEnterpriseContext(signal);

// Reset and Seed the agent's memory for the new persona
const memory = agent.getMemory();

// 1. Clear existing conversational data (to avoid mixing personas)
memory.objective = internalData.objective || null;
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When internalData.objective is an empty string (as in the "default" profile on line 52), this will set memory.objective to an empty string instead of null. This is inconsistent with the expected null value for missing data. Consider using: memory.objective = internalData.objective && internalData.objective !== "" ? internalData.objective : null; or simply set the default profile's objective to undefined instead of an empty string.

Suggested change
memory.objective = internalData.objective || null;
memory.objective = internalData.objective && internalData.objective !== "" ? internalData.objective : null;

Copilot uses AI. Check for mistakes.
memory.relevant_skills = internalData.skills ? [...internalData.skills] : [];
memory.required_skills = [];
memory.learning_path_created = false;
memory.scheduled_actions = []; // Reset scheduled actions for the new persona

// Note: We keep background and experience as null or seed them if we had that data,
// but for this demo, objective and skills are the primary visuals.
memory.background = null;
memory.skill_level = null;
memory.relevant_experience = null;

// Persist the updated agent state so enterprise context is not lost on refresh.
if (typeof (agent as any).saveState === "function") {
(agent as any).saveState();
}
}
}

static getProfiles() {
return COMPANY_DB;
}
}
24 changes: 24 additions & 0 deletions plan-todo/INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 🔌 Integration Strategy Guide

This document outlines the Big Picture vision for connecting our **SKYE** agent with internal company systems.

## 🧠 The Big Picture
Our tool shouldn't require anyone to manually upload files. Instead, **SKYE** acts as a **Smart Bridge** that automatically talks to:
- **Company Knowledge**: Discord, Slack, and internal wikis.
- **HR Systems**: To see what skills you have and who can mentor you.
- **Learning Platforms**: To connect with training your company already pays for.

## 🌉 How it Works: The "Signal Bridge"
We want the tool to "listen" to different signals from your company. We currently have a demo of this in [enterprise-service.ts](../lib/enterprise-service.ts).

### Future API Hook Points:
1. **User Identity Signal**: Fetches role, current skills, and manager-defined objectives.
2. **Context Signal**: Scans internal documentation for specific processes or "The [Company] Way" of doing things.
3. **People Signal**: Identifies internal mentors who have already mastered the target skill.

## 🛠️ Step 1 Strategy: "SKYE First"
We will focus on **SKYE** first.
- Solve the problem of finding the best free guides and videos online.
- Use "Mock Data" (simulated info) to show how company systems *would* look once connected.

> "The internal context is probably more complex... my prio would be: find the free quality content from internet from trusted sources." — *Teemu*
37 changes: 37 additions & 0 deletions plan-todo/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 🚀 Project Roadmap Summary

This document synthesizes the recent feedback from clients (Katarina, Teemu) into a clear comparison of our current solution versus the target for the next iteration.

## 📊 Current State vs. Future Vision

### WHAT WE CURRENTLY HAVE:
- **Foundational SKYE Agent**: A working chat interface with a Python backend and 3D avatar.
- **Enterprise Signal Chain (Demo)**: A mock system that "pre-fills" user data (role, skills, goals) so the agent "knows" who you are.
- **Base Learning Paths**: Simple extraction of skills and initial course suggestions.
- **Modern Infrastructure**: Next.js 15, Tailwind v4, and database persistence (Neon).

### WHAT DO WE NEED:
- **Dedicated Hardware Scenarios**: Building the specific "Developer to ASIC/FPGA" re-skilling path for Katarina.
- **SKYE Knowledge Harvester**: Automating the "vacuum cleaning" of the internet for high-quality, free videos and docs.
- **Time-Commitment Logic**: Allowing users to set 25% to 100% time allowance and seeing the timeline to "Beginner Productivity" adjust instantly.
- **Internal System Bridges**: Moving from "Mock Data" to real API connections with internal Company Knowledge and HR platforms.
- **Active Progress Checks**: Self-test exercises to verify learning and skip content the user already knows.

---

| Feature | CURRENTLY HAVE | SKYE TARGET |
| :--- | :--- | :--- |
| **User Profiles** | Basic info (Role/Skills) | **Real Scenarios**: Re-skilling path (Web to Hardware). |
| **Smart Connections** | Manual mock data | **Live Links**: Auto-connect to internal wikis/HR. |
| **Content Discovery** | Basic lists | **SKYE**: Smart search for free web guides. |
| **Timing** | Simple flow | **Custom Schedule**: 1 day vs 5 days per week. |
| **Progress Checks** | Text-based extraction | **Quick Tests**: Prove you've learned to trim the path. |

## 🎯 Key Differentiators
As Daniel noted, traditional learning platforms have strict "Zero AI" policies. Our tool fills this gap by:
1. **Empowering Employees**: Allowing the use of AI to enhance learning and application.
2. **Contextual Learning**: Bridging the gap between external resources and internal company procedures.
3. **Speed to Action**: Providing the fastest path from learning to application in a specific role.

---
*Generated for the Team Meeting on February 17, 2026*
35 changes: 35 additions & 0 deletions plan-todo/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# ✅ Project To-Do List

Use this list to track progress for the upcoming iteration. These items are based on client requirements for re-skilling and enterprise integration.

## 🏗️ Learning Scenarios
- [ ] **Technical Career Shift** (Re-skilling)
- [ ] Create a "Welcome" flow for developers moving into specialized hardware roles (ASIC/FPGA).
- [ ] List the primary skills needed to go from "Beginner" to "Productive" in the new role.
- [ ] **Telia Up-skilling Case**
- [ ] Leverage existing mock profiles to demo deep-skill transitions.

## ⚙️ Smart Features
- [ ] **Personalized Timeline**
- [ ] Add a "How much time can you spend?" selector (e.g., 1 to 5 days/week).
- [ ] Make the learning plan longer or shorter based on this choice.
- [ ] **Knowledge Checks**
- [ ] Add simple practice tasks to each module.
- [ ] Skip content if the user already knows it.

## 🌐 Finding the Best Resources
- [ ] **SKYE (Smart Search)**
- [ ] Set up the tool to find high-quality, free videos and docs online.
- [ ] Prioritize "trusted" sources like official documentation.
- [ ] **The "All-in-One" View**
- [ ] Show company documents and internet guides in one single list.
- [ ] Suggest internal colleagues (mentors) who can help.

## 🎨 UI/UX Improvements
- [ ] **Commitment Dashboard**
- [ ] Visual indicator of "Time to Beginner Productivity".
- [ ] **Skill Gap Visualization**
- [ ] Show "curated gap" between current strength and target role.

---
*Generated for the Team Meeting on February 17, 2026*