mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-03 13:19:16 +00:00
Include agent personality through subtasks and support custom agents (#916)
Currently, the personality of the agent is only included in the final response that it returns to the user. Historically, this was because models were quite bad at navigating the additional context of personality, and there was a bias towards having more control over certain operations (e.g., tool selection, question extraction). Going forward, it should be more approachable to have prompts included in the sub tasks that Khoj runs in order to response to a given query. Make this possible in this PR. This also sets us up for agent creation becoming available soon. Create custom agents in #928 Agents are useful insofar as you can personalize them to fulfill specific subtasks you need to accomplish. In this PR, we add support for using custom agents that can be configured with a custom system prompt (aka persona) and knowledge base (from your own indexed documents). Once created, private agents can be accessible only to the creator, and protected agents can be accessible via a direct link. Custom tool selection for agents in #930 Expose the functionality to select which tools a given agent has access to. By default, they have all. Can limit both information sources and output modes. Add new tools to the agent modification form
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,15 @@ export interface SyncedContent {
|
||||
github: boolean;
|
||||
notion: boolean;
|
||||
}
|
||||
|
||||
export enum SubscriptionStates {
|
||||
EXPIRED = "expired",
|
||||
TRIAL = "trial",
|
||||
SUBSCRIBED = "subscribed",
|
||||
UNSUBSCRIBED = "unsubscribed",
|
||||
INVALID = "invalid",
|
||||
}
|
||||
|
||||
export interface UserConfig {
|
||||
// user info
|
||||
username: string;
|
||||
@@ -58,7 +67,7 @@ export interface UserConfig {
|
||||
voice_model_options: ModelOptions[];
|
||||
selected_voice_model_config: number;
|
||||
// user billing info
|
||||
subscription_state: string;
|
||||
subscription_state: SubscriptionStates;
|
||||
subscription_renewal_date: string;
|
||||
// server settings
|
||||
khoj_cloud_subscription_url: string | undefined;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const tailwindColors = [
|
||||
export const tailwindColors = [
|
||||
"red",
|
||||
"yellow",
|
||||
"green",
|
||||
|
||||
@@ -26,6 +26,28 @@ import {
|
||||
Wallet,
|
||||
PencilLine,
|
||||
Chalkboard,
|
||||
Gps,
|
||||
Question,
|
||||
Browser,
|
||||
Notebook,
|
||||
Shapes,
|
||||
ChatsTeardrop,
|
||||
GlobeSimple,
|
||||
ArrowRight,
|
||||
Cigarette,
|
||||
CraneTower,
|
||||
Heart,
|
||||
Leaf,
|
||||
NewspaperClipping,
|
||||
OrangeSlice,
|
||||
Rainbow,
|
||||
SmileyMelting,
|
||||
YinYang,
|
||||
SneakerMove,
|
||||
Student,
|
||||
Oven,
|
||||
Gavel,
|
||||
Broadcast,
|
||||
} from "@phosphor-icons/react";
|
||||
import { Markdown, OrgMode, Pdf, Word } from "@/app/components/logo/fileLogo";
|
||||
|
||||
@@ -103,8 +125,92 @@ const iconMap: IconMap = {
|
||||
Chalkboard: (color: string, width: string, height: string) => (
|
||||
<Chalkboard className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
Cigarette: (color: string, width: string, height: string) => (
|
||||
<Cigarette className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
CraneTower: (color: string, width: string, height: string) => (
|
||||
<CraneTower className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
Heart: (color: string, width: string, height: string) => (
|
||||
<Heart className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
Leaf: (color: string, width: string, height: string) => (
|
||||
<Leaf className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
NewspaperClipping: (color: string, width: string, height: string) => (
|
||||
<NewspaperClipping className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
OrangeSlice: (color: string, width: string, height: string) => (
|
||||
<OrangeSlice className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
SmileyMelting: (color: string, width: string, height: string) => (
|
||||
<SmileyMelting className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
YinYang: (color: string, width: string, height: string) => (
|
||||
<YinYang className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
SneakerMove: (color: string, width: string, height: string) => (
|
||||
<SneakerMove className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
Student: (color: string, width: string, height: string) => (
|
||||
<Student className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
Oven: (color: string, width: string, height: string) => (
|
||||
<Oven className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
Gavel: (color: string, width: string, height: string) => (
|
||||
<Gavel className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
Broadcast: (color: string, width: string, height: string) => (
|
||||
<Broadcast className={`${width} ${height} ${color} mr-2`} />
|
||||
),
|
||||
};
|
||||
|
||||
export function getIconForSlashCommand(command: string, customClassName: string | null = null) {
|
||||
const className = customClassName ?? "h-4 w-4";
|
||||
if (command.includes("summarize")) {
|
||||
return <Gps className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("help")) {
|
||||
return <Question className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("automation")) {
|
||||
return <Robot className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("webpage")) {
|
||||
return <Browser className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("notes")) {
|
||||
return <Notebook className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("image")) {
|
||||
return <Image className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("default")) {
|
||||
return <Shapes className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("general")) {
|
||||
return <ChatsTeardrop className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("online")) {
|
||||
return <GlobeSimple className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("text")) {
|
||||
return <PencilLine className={className} />;
|
||||
}
|
||||
|
||||
return <ArrowRight className={className} />;
|
||||
}
|
||||
|
||||
function getIconFromIconName(
|
||||
iconName: string,
|
||||
color: string = "gray",
|
||||
@@ -141,4 +247,8 @@ function getIconFromFilename(
|
||||
}
|
||||
}
|
||||
|
||||
export { getIconFromIconName, getIconFromFilename };
|
||||
function getAvailableIcons() {
|
||||
return Object.keys(iconMap);
|
||||
}
|
||||
|
||||
export { getIconFromIconName, getIconFromFilename, getAvailableIcons };
|
||||
|
||||
@@ -15,7 +15,7 @@ import { InlineLoading } from "../loading/loading";
|
||||
|
||||
import { Lightbulb, ArrowDown } from "@phosphor-icons/react";
|
||||
|
||||
import ProfileCard from "../profileCard/profileCard";
|
||||
import AgentProfileCard from "../profileCard/profileCard";
|
||||
import { getIconFromIconName } from "@/app/common/iconUtils";
|
||||
import { AgentData } from "@/app/agents/page";
|
||||
import React from "react";
|
||||
@@ -350,7 +350,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
||||
{data && (
|
||||
<div className={`${styles.agentIndicator} pb-4`}>
|
||||
<div className="relative group mx-2 cursor-pointer">
|
||||
<ProfileCard
|
||||
<AgentProfileCard
|
||||
name={constructAgentName()}
|
||||
link={constructAgentLink()}
|
||||
avatar={
|
||||
|
||||
@@ -50,6 +50,7 @@ import { convertToBGClass } from "@/app/common/colorUtils";
|
||||
import LoginPrompt from "../loginPrompt/loginPrompt";
|
||||
import { uploadDataForIndexing } from "../../common/chatFunctions";
|
||||
import { InlineLoading } from "../loading/loading";
|
||||
import { getIconForSlashCommand } from "@/app/common/iconUtils";
|
||||
|
||||
export interface ChatOptions {
|
||||
[key: string]: string;
|
||||
@@ -193,46 +194,6 @@ export default function ChatInputArea(props: ChatInputProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function getIconForSlashCommand(command: string) {
|
||||
const className = "h-4 w-4 mr-2";
|
||||
if (command.includes("summarize")) {
|
||||
return <Gps className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("help")) {
|
||||
return <Question className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("automation")) {
|
||||
return <Robot className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("webpage")) {
|
||||
return <Browser className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("notes")) {
|
||||
return <Notebook className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("image")) {
|
||||
return <Image className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("default")) {
|
||||
return <Shapes className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("general")) {
|
||||
return <ChatsTeardrop className={className} />;
|
||||
}
|
||||
|
||||
if (command.includes("online")) {
|
||||
return <GlobeSimple className={className} />;
|
||||
}
|
||||
return <ArrowRight className={className} />;
|
||||
}
|
||||
|
||||
// Assuming this function is added within the same context as the provided excerpt
|
||||
async function startRecordingAndTranscribe() {
|
||||
try {
|
||||
@@ -426,7 +387,11 @@ export default function ChatInputArea(props: ChatInputProps) {
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-1">
|
||||
<div className="font-bold flex items-center">
|
||||
{getIconForSlashCommand(key)}/{key}
|
||||
{getIconForSlashCommand(
|
||||
key,
|
||||
"h-4 w-4 mr-2",
|
||||
)}
|
||||
/{key}
|
||||
</div>
|
||||
<div>{value}</div>
|
||||
</div>
|
||||
|
||||
@@ -11,11 +11,11 @@ interface ProfileCardProps {
|
||||
description?: string; // Optional description field
|
||||
}
|
||||
|
||||
const ProfileCard: React.FC<ProfileCardProps> = ({ name, avatar, link, description }) => {
|
||||
const AgentProfileCard: React.FC<ProfileCardProps> = ({ name, avatar, link, description }) => {
|
||||
return (
|
||||
<div className="relative group flex">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" className="flex items-center justify-center">
|
||||
{avatar}
|
||||
@@ -24,7 +24,6 @@ const ProfileCard: React.FC<ProfileCardProps> = ({ name, avatar, link, descripti
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="w-80 h-30">
|
||||
{/* <div className="absolute left-0 bottom-full w-80 h-30 p-2 pb-4 bg-white border border-gray-300 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"> */}
|
||||
<a
|
||||
href={link}
|
||||
target="_blank"
|
||||
@@ -52,4 +51,4 @@ const ProfileCard: React.FC<ProfileCardProps> = ({ name, avatar, link, descripti
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileCard;
|
||||
export default AgentProfileCard;
|
||||
|
||||
@@ -17,8 +17,16 @@ interface ShareLinkProps {
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
onShare: () => void;
|
||||
buttonVariant?: keyof typeof buttonVariants;
|
||||
onShare?: () => void;
|
||||
buttonVariant?:
|
||||
| "default"
|
||||
| "destructive"
|
||||
| "outline"
|
||||
| "secondary"
|
||||
| "ghost"
|
||||
| "link"
|
||||
| null
|
||||
| undefined;
|
||||
includeIcon?: boolean;
|
||||
buttonClassName?: string;
|
||||
}
|
||||
@@ -38,7 +46,7 @@ export default function ShareLink(props: ShareLinkProps) {
|
||||
<Button
|
||||
size="sm"
|
||||
className={`${props.buttonClassName || "px-3"}`}
|
||||
variant={props.buttonVariant ?? ("default" as const)}
|
||||
variant={props.buttonVariant ?? "default"}
|
||||
>
|
||||
{props.includeIcon && <Share className="w-4 h-4 mr-2" />}
|
||||
{props.buttonTitle}
|
||||
|
||||
@@ -63,7 +63,6 @@ interface ChatHistory {
|
||||
conversation_id: string;
|
||||
slug: string;
|
||||
agent_name: string;
|
||||
agent_avatar: string;
|
||||
compressed: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
@@ -435,7 +434,6 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
|
||||
chatHistory.conversation_id
|
||||
}
|
||||
slug={chatHistory.slug}
|
||||
agent_avatar={chatHistory.agent_avatar}
|
||||
agent_name={chatHistory.agent_name}
|
||||
showSidePanel={props.setEnabled}
|
||||
/>
|
||||
@@ -713,7 +711,6 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
|
||||
key={chatHistory.conversation_id}
|
||||
conversation_id={chatHistory.conversation_id}
|
||||
slug={chatHistory.slug}
|
||||
agent_avatar={chatHistory.agent_avatar}
|
||||
agent_name={chatHistory.agent_name}
|
||||
showSidePanel={showSidePanel}
|
||||
/>
|
||||
|
||||
@@ -123,18 +123,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||
//generate colored icons for the selected agents
|
||||
const agentIcons = agents
|
||||
.filter((agent) => agent !== null && agent !== undefined)
|
||||
.map(
|
||||
(agent) =>
|
||||
getIconFromIconName(agent.icon, agent.color) || (
|
||||
<Image
|
||||
key={agent.name}
|
||||
src={agent.avatar}
|
||||
alt={agent.name}
|
||||
width={50}
|
||||
height={50}
|
||||
/>
|
||||
),
|
||||
);
|
||||
.map((agent) => getIconFromIconName(agent.icon, agent.color)!);
|
||||
setAgentIcons(agentIcons);
|
||||
}, [agentsData, props.isMobileWidth]);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import "intl-tel-input/styles";
|
||||
import { Suspense, useEffect, useRef, useState } from "react";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
|
||||
import { useUserConfig, ModelOptions, UserConfig } from "../common/auth";
|
||||
import { useUserConfig, ModelOptions, UserConfig, SubscriptionStates } from "../common/auth";
|
||||
import { toTitleCase, useIsMobileWidth } from "../common/utils";
|
||||
|
||||
import { isValidPhoneNumber } from "libphonenumber-js";
|
||||
@@ -276,7 +276,7 @@ const ManageFilesModal: React.FC<{ onClose: () => void }> = ({ onClose }) => {
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`flex-none p-4 bg-secondary border-b ${isDragAndDropping ? "animate-pulse" : ""}`}
|
||||
className={`flex-none p-4 bg-secondary border-b ${isDragAndDropping ? "animate-pulse" : ""} rounded-lg`}
|
||||
>
|
||||
<div className="flex items-center justify-center w-full h-32 border-2 border-dashed border-gray-300 rounded-lg">
|
||||
{isDragAndDropping ? (
|
||||
@@ -294,7 +294,6 @@ const ManageFilesModal: React.FC<{ onClose: () => void }> = ({ onClose }) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-none p-4">Synced files</div>
|
||||
<div className="flex-none p-4 bg-background border-b">
|
||||
<CommandInput
|
||||
placeholder="Find synced files"
|
||||
@@ -615,7 +614,9 @@ export default function SettingsView() {
|
||||
if (userConfig) {
|
||||
let newUserConfig = userConfig;
|
||||
newUserConfig.subscription_state =
|
||||
state === "cancel" ? "unsubscribed" : "subscribed";
|
||||
state === "cancel"
|
||||
? SubscriptionStates.UNSUBSCRIBED
|
||||
: SubscriptionStates.SUBSCRIBED;
|
||||
setUserConfig(newUserConfig);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user