@markprompt/react
Advanced tools
Comparing version 0.43.0 to 0.44.0
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import { DEFAULT_OPTIONS, isToolCalls } from '@markprompt/core'; | ||
@@ -32,7 +31,7 @@ import { useMemo } from 'react'; | ||
(chatOptions.showCopy || feedbackOptions?.enabled) && | ||
message.state === 'done' && (_jsx(Feedback, { message: message.content ?? '', variant: "icons", "data-show-feedback-always": showFeedbackAlways, className: "MarkpromptPromptFeedback", submitFeedback: (feedback, promptId) => { | ||
submitFeedback(feedback, promptId); | ||
feedbackOptions.onFeedbackSubmit?.(feedback, messages, promptId); | ||
}, abortFeedbackRequest: abortFeedbackRequest, promptId: message.promptId, heading: feedbackOptions.heading, showFeedback: !!feedbackOptions?.enabled, showVotes: feedbackOptions.votes, showCopy: chatOptions.showCopy }))] })] })); | ||
message.state === 'done' && (_jsx(Feedback, { message: message.content ?? '', variant: "icons", "data-show-feedback-always": showFeedbackAlways, className: "MarkpromptPromptFeedback", submitFeedback: (feedback, messageId) => { | ||
submitFeedback(feedback, messageId); | ||
feedbackOptions.onFeedbackSubmit?.(feedback, messages, messageId); | ||
}, abortFeedbackRequest: abortFeedbackRequest, messageId: message.messageId, heading: feedbackOptions.heading, showFeedback: !!feedbackOptions?.enabled, showVotes: feedbackOptions.votes, showCopy: chatOptions.showCopy }))] })] })); | ||
} | ||
//# sourceMappingURL=AssistantMessage.js.map |
@@ -5,5 +5,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; | ||
import { ChatViewForm } from './ChatViewForm.js'; | ||
import { ConversationSidebar } from './ConversationSidebar.js'; | ||
import { Messages } from './Messages.js'; | ||
import { useChatStore } from './store.js'; | ||
import { ThreadSidebar } from './ThreadSidebar.js'; | ||
import { DEFAULT_MARKPROMPT_OPTIONS } from '../constants.js'; | ||
@@ -25,3 +25,3 @@ import { ChevronLeftIcon } from '../icons.js'; | ||
const setDidAcceptDisclaimer = useChatStore((state) => state.setDidAcceptDisclaimer); | ||
return (_jsxs("div", { className: "MarkpromptChatView", children: [_jsx(ConversationSidebar, { display: display }), _jsx("div", { className: "MarkpromptChatViewChatContainer", children: _jsxs("div", { className: "MarkpromptChatViewChat", children: [showBack ? (_jsx("div", { className: "MarkpromptChatViewNavigation", children: _jsx("button", { className: "MarkpromptGhostButton", onClick: onDidPressBack, children: _jsx(ChevronLeftIcon, { style: { width: 16, height: 16 }, strokeWidth: 2.5 }) }) })) : ( | ||
return (_jsxs("div", { className: "MarkpromptChatView", children: [_jsx(ThreadSidebar, { display: display }), _jsx("div", { className: "MarkpromptChatViewChatContainer", children: _jsxs("div", { className: "MarkpromptChatViewChat", children: [showBack ? (_jsx("div", { className: "MarkpromptChatViewNavigation", children: _jsx("button", { className: "MarkpromptGhostButton", onClick: onDidPressBack, children: _jsx(ChevronLeftIcon, { style: { width: 16, height: 16 }, strokeWidth: 2.5 }) }) })) : ( | ||
// Keep this for the grid template rows layout | ||
@@ -28,0 +28,0 @@ _jsx("div", {})), !didAcceptDisclaimer && chatOptions?.disclaimerView ? (_jsx("div", { className: "MarkpromptDisclaimerView", children: _jsxs("div", { className: "MarkpromptDisclaimerViewMessage", children: [_jsx(RichText, { children: chatOptions.disclaimerView.message }), _jsx("button", { className: "MarkpromptButton", "data-variant": "primary", type: "submit", onClick: () => { |
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; | ||
import { useCallback, useContext, useEffect, useRef, useState, useMemo, } from 'react'; | ||
import { ConversationSelect } from './ConversationSelect.js'; | ||
import { ChatContext, useChatStore } from './store.js'; | ||
import { ThreadSelect } from './ThreadSelect.js'; | ||
import { LoadingIcon, SendIcon } from '../icons.js'; | ||
@@ -18,6 +18,2 @@ import * as BaseMarkprompt from '../primitives/headless.js'; | ||
const lastMessageState = useChatStore((state) => state.messages[state.messages.length - 1]?.state); | ||
// const regenerateLastAnswer = useChatStore( | ||
// (state) => state.regenerateLastAnswer, | ||
// ); | ||
// const conversations = useChatStore(selectProjectConversations); | ||
const formRef = useRef(null); | ||
@@ -64,4 +60,4 @@ const textAreaRef = useRef(null); | ||
formRef.current?.requestSubmit(); | ||
} }), chatOptions.history && (_jsx(ConversationSelect, { disabled: !didAcceptDisclaimer })), _jsx("div", {})] }) })); | ||
} }), chatOptions.history && (_jsx(ThreadSelect, { disabled: !didAcceptDisclaimer })), _jsx("div", {})] }) })); | ||
} | ||
//# sourceMappingURL=ChatViewForm.js.map |
@@ -16,3 +16,3 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; | ||
const messages = useChatStore((state) => state.messages); | ||
const threadId = useChatStore((state) => state.conversationId); | ||
const threadId = useChatStore((state) => state.threadId); | ||
const submitChat = useChatStore((state) => state.submitChat); | ||
@@ -19,0 +19,0 @@ const welcomeMessage = useMemo(() => { |
@@ -10,3 +10,3 @@ import { type ChatCompletionMessageToolCall, type ChatCompletionTool, type SubmitChatOptions, type SubmitChatYield, type ChatCompletionChunk } from '@markprompt/core'; | ||
} | ||
export interface ChatViewMessage extends Omit<SubmitChatYield, 'conversationId'> { | ||
export interface ChatViewMessage extends Omit<SubmitChatYield, 'threadId'> { | ||
/** | ||
@@ -95,3 +95,3 @@ * Message id. | ||
}; | ||
export interface ConversationData { | ||
export interface ThreadData { | ||
lastUpdated: string; | ||
@@ -114,15 +114,15 @@ messages: ChatViewMessage[]; | ||
/** | ||
* The current conversation id. | ||
* The current thread id. | ||
**/ | ||
conversationId?: string; | ||
threadId?: string; | ||
/** | ||
* Set a conversation id. | ||
* Set a thread id. | ||
**/ | ||
setConversationId: (conversationId: string) => void; | ||
setThreadId: (threadId: string) => void; | ||
/** | ||
* Select a conversation. | ||
* Select a thread. | ||
**/ | ||
selectConversation: (conversationId?: string) => void; | ||
selectThread: (threadId?: string) => void; | ||
/** | ||
* The messages in the current conversation. | ||
* The messages in the current thread. | ||
**/ | ||
@@ -147,12 +147,12 @@ messages: ChatViewMessage[]; | ||
/** | ||
* Dictionary of conversations by project id. | ||
* Dictionary of threads by project id. | ||
**/ | ||
conversationIdsByProjectKey: { | ||
threadIdsByProjectKey: { | ||
[projectKey: string]: string[]; | ||
}; | ||
/** | ||
* Dictionary of messages by conversation id. | ||
* Dictionary of messages by thread id. | ||
**/ | ||
messagesByConversationId: { | ||
[conversationId: string]: ConversationData; | ||
messagesByThreadId: { | ||
[threadId: string]: ThreadData; | ||
}; | ||
@@ -209,3 +209,3 @@ /** | ||
* Creates a chat store for a given project key. | ||
* Keeps track of messages by project key and conversation id. | ||
* Keeps track of messages by project key and thread id. | ||
* | ||
@@ -220,5 +220,5 @@ * @param projectKey - Markprompt project key | ||
abort?: (() => void) | undefined; | ||
conversationId?: string | undefined; | ||
setConversationId: (conversationId: string) => void; | ||
selectConversation: (conversationId?: string) => void; | ||
threadId?: string | undefined; | ||
setThreadId: (threadId: string) => void; | ||
selectThread: (threadId?: string) => void; | ||
messages: { | ||
@@ -271,3 +271,3 @@ id: ReturnType<typeof crypto.randomUUID>; | ||
}[] | undefined; | ||
promptId?: string | undefined; | ||
messageId?: string | undefined; | ||
}[]; | ||
@@ -278,6 +278,6 @@ setMessages: (messages: ChatViewMessage[]) => void; | ||
setToolCallById: (toolCallId: string, next: Partial<ToolCall>) => void; | ||
conversationIdsByProjectKey: { | ||
threadIdsByProjectKey: { | ||
[x: string]: string[]; | ||
}; | ||
messagesByConversationId: { | ||
messagesByThreadId: { | ||
[x: string]: { | ||
@@ -332,3 +332,3 @@ lastUpdated: string; | ||
}[] | undefined; | ||
promptId?: string | undefined; | ||
messageId?: string | undefined; | ||
}[]; | ||
@@ -347,11 +347,18 @@ }; | ||
options?: { | ||
conversationId?: string | undefined; | ||
conversationMetadata?: any; | ||
debug?: boolean | undefined; | ||
iDontKnowMessage?: string | undefined; | ||
doNotInjectContext?: boolean | undefined; | ||
allowFollowUpQuestions?: boolean | undefined; | ||
excludeFromInsights?: boolean | undefined; | ||
systemPrompt?: string | undefined; | ||
context?: any; | ||
model?: import("@markprompt/core").OpenAIModelId | undefined; | ||
systemPrompt?: string | undefined; | ||
policiesOptions?: { | ||
enabled?: boolean | undefined; | ||
useAll?: boolean | undefined; | ||
ids?: string[] | undefined; | ||
} | undefined; | ||
retrievalOptions?: { | ||
enabled?: boolean | undefined; | ||
useAll?: boolean | undefined; | ||
ids?: string[] | undefined; | ||
} | undefined; | ||
outputFormat?: "html" | "markdown" | "slack" | undefined; | ||
jsonOutput?: boolean | undefined; | ||
redact?: boolean | undefined; | ||
temperature?: number | undefined; | ||
@@ -364,4 +371,4 @@ topP?: number | undefined; | ||
sectionsMatchThreshold?: number | undefined; | ||
stream?: boolean | undefined; | ||
tool_choice?: "none" | "auto" | { | ||
threadId?: string | undefined; | ||
toolChoice?: "none" | "auto" | { | ||
function: { | ||
@@ -372,4 +379,8 @@ name: string; | ||
} | undefined; | ||
outputFormat?: "slack" | "markdown" | undefined; | ||
redact?: boolean | undefined; | ||
doNotInjectContext?: boolean | undefined; | ||
allowFollowUpQuestions?: boolean | undefined; | ||
excludeFromInsights?: boolean | undefined; | ||
debug?: boolean | undefined; | ||
iDontKnowMessage?: string | undefined; | ||
stream?: boolean | undefined; | ||
tools?: { | ||
@@ -445,5 +456,5 @@ tool: { | ||
abort?: (() => void) | undefined; | ||
conversationId?: string | undefined; | ||
setConversationId: (conversationId: string) => void; | ||
selectConversation: (conversationId?: string) => void; | ||
threadId?: string | undefined; | ||
setThreadId: (threadId: string) => void; | ||
selectThread: (threadId?: string) => void; | ||
messages: { | ||
@@ -496,3 +507,3 @@ id: ReturnType<typeof crypto.randomUUID>; | ||
}[] | undefined; | ||
promptId?: string | undefined; | ||
messageId?: string | undefined; | ||
}[]; | ||
@@ -503,6 +514,6 @@ setMessages: (messages: ChatViewMessage[]) => void; | ||
setToolCallById: (toolCallId: string, next: Partial<ToolCall>) => void; | ||
conversationIdsByProjectKey: { | ||
threadIdsByProjectKey: { | ||
[x: string]: string[]; | ||
}; | ||
messagesByConversationId: { | ||
messagesByThreadId: { | ||
[x: string]: { | ||
@@ -557,3 +568,3 @@ lastUpdated: string; | ||
}[] | undefined; | ||
promptId?: string | undefined; | ||
messageId?: string | undefined; | ||
}[]; | ||
@@ -572,11 +583,18 @@ }; | ||
options?: { | ||
conversationId?: string | undefined; | ||
conversationMetadata?: any; | ||
debug?: boolean | undefined; | ||
iDontKnowMessage?: string | undefined; | ||
doNotInjectContext?: boolean | undefined; | ||
allowFollowUpQuestions?: boolean | undefined; | ||
excludeFromInsights?: boolean | undefined; | ||
systemPrompt?: string | undefined; | ||
context?: any; | ||
model?: import("@markprompt/core").OpenAIModelId | undefined; | ||
systemPrompt?: string | undefined; | ||
policiesOptions?: { | ||
enabled?: boolean | undefined; | ||
useAll?: boolean | undefined; | ||
ids?: string[] | undefined; | ||
} | undefined; | ||
retrievalOptions?: { | ||
enabled?: boolean | undefined; | ||
useAll?: boolean | undefined; | ||
ids?: string[] | undefined; | ||
} | undefined; | ||
outputFormat?: "html" | "markdown" | "slack" | undefined; | ||
jsonOutput?: boolean | undefined; | ||
redact?: boolean | undefined; | ||
temperature?: number | undefined; | ||
@@ -589,4 +607,4 @@ topP?: number | undefined; | ||
sectionsMatchThreshold?: number | undefined; | ||
stream?: boolean | undefined; | ||
tool_choice?: "none" | "auto" | { | ||
threadId?: string | undefined; | ||
toolChoice?: "none" | "auto" | { | ||
function: { | ||
@@ -597,4 +615,8 @@ name: string; | ||
} | undefined; | ||
outputFormat?: "slack" | "markdown" | undefined; | ||
redact?: boolean | undefined; | ||
doNotInjectContext?: boolean | undefined; | ||
allowFollowUpQuestions?: boolean | undefined; | ||
excludeFromInsights?: boolean | undefined; | ||
debug?: boolean | undefined; | ||
iDontKnowMessage?: string | undefined; | ||
stream?: boolean | undefined; | ||
tools?: { | ||
@@ -666,3 +688,3 @@ tool: { | ||
interface ChatProviderProps { | ||
chatOptions: MarkpromptOptions['chat']; | ||
chatOptions?: MarkpromptOptions['chat']; | ||
children: ReactNode; | ||
@@ -675,4 +697,4 @@ debug?: boolean; | ||
export declare function useChatStore<T>(selector: (state: ChatStoreState) => T): T; | ||
export declare const selectProjectConversations: (state: ChatStoreState) => [ | ||
conversationId: string, | ||
export declare const selectProjectThreads: (state: ChatStoreState) => [ | ||
threadId: string, | ||
{ | ||
@@ -679,0 +701,0 @@ lastUpdated: string; |
import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { isAbortError, isToolCall, isToolCalls, submitChat, } from '@markprompt/core'; | ||
import { isAbortError, isToolCall, submitChat, } from '@markprompt/core'; | ||
import { createContext, useContext, useEffect, useRef, } from 'react'; | ||
@@ -7,60 +7,7 @@ import { createStore, useStore } from 'zustand'; | ||
import { immer } from 'zustand/middleware/immer'; | ||
import { toValidApiMessages } from './utils.js'; | ||
import { hasValueAtKey, isIterable, isPresent, isStoredError, } from '../utils.js'; | ||
function toApiMessages(messages) { | ||
return (messages | ||
.map(({ content, role, tool_calls, tool_call_id, name }) => { | ||
if (!content && !tool_calls) { | ||
// Ignore empty messages unless it's a tool_call | ||
return undefined; | ||
} | ||
switch (role) { | ||
case 'assistant': { | ||
const msg = { | ||
content: content ?? null, | ||
role, | ||
}; | ||
if (isToolCalls(tool_calls)) { | ||
msg.tool_calls = tool_calls; | ||
} | ||
return msg; | ||
} | ||
// case 'system': { | ||
// return { | ||
// content: content ?? null, | ||
// role, | ||
// } satisfies ChatCompletionSystemMessageParam; | ||
// } | ||
case 'tool': { | ||
if (!tool_call_id) | ||
throw new Error('tool_call_id is required'); | ||
if (!content) | ||
throw new Error('content is required'); | ||
return { | ||
content, | ||
role, | ||
tool_call_id, | ||
}; | ||
} | ||
case 'user': { | ||
if (!content) | ||
throw new Error('content is required'); | ||
return { | ||
content, | ||
role, | ||
...(name ? { name } : {}), | ||
}; | ||
} | ||
} | ||
}) | ||
.filter(isPresent) | ||
// remove the last message if role is assistant and content is null | ||
// we add this message locally as a placeholder for ourself and OpenAI errors out | ||
// if we send it to them | ||
.filter((m, i, arr) => !(i === arr.length - 1 && | ||
m.role === 'assistant' && | ||
m.content === null))); | ||
} | ||
/** | ||
* Creates a chat store for a given project key. | ||
* Keeps track of messages by project key and conversation id. | ||
* Keeps track of messages by project key and thread id. | ||
* | ||
@@ -70,4 +17,3 @@ * @param projectKey - Markprompt project key | ||
*/ | ||
export const createChatStore = ({ chatOptions, debug, persistChatHistory, projectKey, apiUrl, | ||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
export const createChatStore = ({ chatOptions, debug, persistChatHistory, projectKey, apiUrl, // eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
}) => { | ||
@@ -82,26 +28,26 @@ if (!projectKey) { | ||
didAcceptDisclaimer: false, | ||
conversationIdsByProjectKey: { | ||
threadIdsByProjectKey: { | ||
[projectKey]: [], | ||
}, | ||
messagesByConversationId: {}, | ||
messagesByThreadId: {}, | ||
toolCallsByToolCallId: {}, | ||
didAcceptDisclaimerByProjectKey: {}, | ||
setConversationId: (conversationId) => { | ||
setThreadId: (threadId) => { | ||
set((state) => { | ||
// set the conversation id for this session | ||
state.conversationIdsByProjectKey[projectKey] ??= []; | ||
state.conversationId = conversationId; | ||
if (!isIterable(state.conversationIdsByProjectKey[projectKey])) { | ||
// Set the thread id for this session | ||
state.threadIdsByProjectKey[projectKey] ??= []; | ||
state.threadId = threadId; | ||
if (!isIterable(state.threadIdsByProjectKey[projectKey])) { | ||
// Backward-compatibility | ||
state.conversationIdsByProjectKey[projectKey] = []; | ||
state.threadIdsByProjectKey[projectKey] = []; | ||
} | ||
// save the conversation id for this project, for later sessions | ||
state.conversationIdsByProjectKey[projectKey] = [ | ||
// Save the thread id for this project, for later sessions | ||
state.threadIdsByProjectKey[projectKey] = [ | ||
...new Set([ | ||
...state.conversationIdsByProjectKey[projectKey], | ||
conversationId, | ||
...state.threadIdsByProjectKey[projectKey], | ||
threadId, | ||
]), | ||
]; | ||
// save the messages for this conversation | ||
state.messagesByConversationId[conversationId] = { | ||
// Save the messages for this thread | ||
state.messagesByThreadId[threadId] = { | ||
lastUpdated: new Date().toISOString(), | ||
@@ -112,4 +58,4 @@ messages: state.messages, | ||
}, | ||
selectConversation: (conversationId) => { | ||
if (conversationId && conversationId === get().conversationId) { | ||
selectThread: (threadId) => { | ||
if (threadId && threadId === get().threadId) { | ||
return; | ||
@@ -120,12 +66,12 @@ } | ||
set((state) => { | ||
if (!conversationId) { | ||
// start a new conversation | ||
state.conversationId = undefined; | ||
if (!threadId) { | ||
// Start a new thread | ||
state.threadId = undefined; | ||
state.messages = []; | ||
return; | ||
} | ||
// restore an existing conversation | ||
state.conversationId = conversationId; | ||
// Restore an existing thread | ||
state.threadId = threadId; | ||
state.messages = | ||
state.messagesByConversationId[conversationId]?.messages ?? []; | ||
state.messagesByThreadId[threadId]?.messages ?? []; | ||
}); | ||
@@ -136,7 +82,7 @@ }, | ||
state.messages = messages; | ||
const conversationId = state.conversationId; | ||
if (!conversationId) | ||
const threadId = state.threadId; | ||
if (!threadId) | ||
return; | ||
// save the message to local storage | ||
state.messagesByConversationId[conversationId] = { | ||
state.messagesByThreadId[threadId] = { | ||
lastUpdated: new Date().toISOString(), | ||
@@ -153,7 +99,7 @@ messages, | ||
state.messages[index] = currentMessage; | ||
const conversationId = state.conversationId; | ||
if (!conversationId) | ||
const threadId = state.threadId; | ||
if (!threadId) | ||
return; | ||
// save the message to local storage | ||
state.messagesByConversationId[conversationId] = { | ||
state.messagesByThreadId[threadId] = { | ||
lastUpdated: new Date().toISOString(), | ||
@@ -174,7 +120,7 @@ messages: state.messages, | ||
state.messages[index] = currentMessage; | ||
const conversationId = state.conversationId; | ||
if (!conversationId) | ||
const threadId = state.threadId; | ||
if (!threadId) | ||
return; | ||
// save the message to local storage | ||
state.messagesByConversationId[conversationId] = { | ||
state.messagesByThreadId[threadId] = { | ||
lastUpdated: new Date().toISOString(), | ||
@@ -235,3 +181,3 @@ messages: state.messages, | ||
// Get ready to do the request | ||
const apiMessages = toApiMessages(get().messages); | ||
const apiMessages = toValidApiMessages(get().messages); | ||
for (const id of [...messageIds, responseId]) { | ||
@@ -244,3 +190,3 @@ get().setMessageById(id, { | ||
apiUrl: get().apiUrl, | ||
conversationId: get().conversationId, | ||
threadId: get().threadId, | ||
signal: controller.signal, | ||
@@ -256,4 +202,4 @@ debug, | ||
continue; | ||
if (chunk.conversationId) { | ||
get().setConversationId(chunk.conversationId); | ||
if (chunk.threadId) { | ||
get().setThreadId(chunk.threadId); | ||
} | ||
@@ -423,7 +369,7 @@ for (const id of messageIds) { | ||
}), | ||
// only store conversationsByProjectKey in local storage | ||
// Only store threadsByProjectKey in local storage | ||
partialize: (state) => { | ||
return { | ||
conversationIdsByProjectKey: state.conversationIdsByProjectKey, | ||
messagesByConversationId: state.messagesByConversationId, | ||
threadIdsByProjectKey: state.threadIdsByProjectKey, | ||
messagesByThreadId: state.messagesByThreadId, | ||
toolCallsByToolCallId: state.toolCallsByToolCallId, | ||
@@ -433,3 +379,3 @@ didAcceptDisclaimerByProjectKey: state.didAcceptDisclaimerByProjectKey, | ||
}, | ||
// restore the last conversation for this project if it's < 4 hours old | ||
// Restore the last tjread for this project if it's < 4 hours old | ||
onRehydrateStorage: () => (state) => { | ||
@@ -443,10 +389,11 @@ if (!state || typeof state !== 'object') | ||
} | ||
const { conversationIdsByProjectKey, messagesByConversationId } = state; | ||
const conversationIds = conversationIdsByProjectKey?.[projectKey] ?? []; | ||
const { threadIdsByProjectKey, messagesByThreadId } = state; | ||
const threadIds = threadIdsByProjectKey?.[projectKey] ?? []; | ||
const now = new Date(); | ||
const fourHoursAgo = new Date(now.getTime() - 4 * 60 * 60 * 1000); | ||
const projectConversations = Object.entries(messagesByConversationId) | ||
// filter out conversations that are not in the list of conversations for this project | ||
.filter(([id]) => conversationIds.includes(id)) | ||
// filter out conversations older than 4 hours | ||
const projectThreads = Object.entries(messagesByThreadId) | ||
// Filter out threads that are not in the list of threads for | ||
// this project | ||
.filter(([id]) => threadIds.includes(id)) | ||
// Filter out threads older than 4 hours | ||
.filter(([, { lastUpdated }]) => { | ||
@@ -458,8 +405,7 @@ const lastUpdatedDate = new Date(lastUpdated); | ||
.sort(([, { lastUpdated: a }], [, { lastUpdated: b }]) => b.localeCompare(a)); | ||
if (projectConversations.length === 0 || | ||
!isPresent(projectConversations[0])) { | ||
if (projectThreads.length === 0 || !isPresent(projectThreads[0])) { | ||
return; | ||
} | ||
const [conversationId, { messages }] = projectConversations[0]; | ||
state.setConversationId(conversationId); | ||
const [threadId, { messages }] = projectThreads[0]; | ||
state.setThreadId(threadId); | ||
state.setMessages(messages.map((x) => ({ | ||
@@ -503,15 +449,16 @@ ...x, | ||
} | ||
export const selectProjectConversations = (state) => { | ||
export const selectProjectThreads = (state) => { | ||
const projectKey = state.projectKey; | ||
const conversationIds = state.conversationIdsByProjectKey[projectKey]; | ||
if (!conversationIds || conversationIds.length === 0) | ||
const threadIds = state.threadIdsByProjectKey[projectKey]; | ||
if (!threadIds || threadIds.length === 0) | ||
return []; | ||
const messagesByConversationId = Object.entries(state.messagesByConversationId) | ||
.filter(([id]) => conversationIds.includes(id)) | ||
// ascending order, so the newest conversation will be closest to the dropdown toggle | ||
const messagesByThreadId = Object.entries(state.messagesByThreadId) | ||
.filter(([id]) => threadIds.includes(id)) | ||
// Ascending order, so the newest thread will be closest to the | ||
// dropdown toggle | ||
.sort(([, { lastUpdated: a }], [, { lastUpdated: b }]) => a.localeCompare(b)); | ||
if (!messagesByConversationId) | ||
if (!messagesByThreadId) | ||
return []; | ||
return messagesByConversationId; | ||
return messagesByThreadId; | ||
}; | ||
//# sourceMappingURL=store.js.map |
import { type ChatCompletionMessageParam } from '@markprompt/core'; | ||
import type { ChatViewMessage } from './store.js'; | ||
export declare function toApiMessages(messages: (ChatViewMessage & { | ||
export declare function toValidApiMessages(messages: (ChatViewMessage & { | ||
tool_call_id?: string; | ||
})[]): ChatCompletionMessageParam[]; | ||
//# sourceMappingURL=utils.d.ts.map |
import { isToolCalls, } from '@markprompt/core'; | ||
import { isPresent } from '../utils.js'; | ||
export function toApiMessages(messages) { | ||
export function toValidApiMessages(messages) { | ||
return (messages | ||
.map(({ content, role, tool_calls, tool_call_id, name }) => { | ||
if (!content) { | ||
// Ignore empty messages | ||
return undefined; | ||
} | ||
.map(({ content, role, tool_calls, tool_call_id, name }, i) => { | ||
switch (role) { | ||
case 'assistant': { | ||
const msg = { | ||
content: content ?? null, | ||
content: content ?? '', | ||
role, | ||
}; | ||
if (isToolCalls(tool_calls)) | ||
if (isToolCalls(tool_calls)) { | ||
msg.tool_calls = tool_calls; | ||
// If this is a tool_calls assistant message and the next | ||
// message is not a tool message, ignore it, as it will | ||
// result in an invalid API request. | ||
const nextMessage = messages[i + 1]; | ||
if (nextMessage && nextMessage.role !== 'tool') { | ||
return undefined; | ||
} | ||
} | ||
return msg; | ||
@@ -24,3 +28,3 @@ } | ||
return { | ||
content: content ?? null, | ||
content: content ?? '', | ||
role, | ||
@@ -32,3 +36,3 @@ tool_call_id, | ||
return { | ||
content: content ?? null, | ||
content: content ?? '', | ||
role, | ||
@@ -41,9 +45,10 @@ ...(name ? { name } : {}), | ||
.filter(isPresent) | ||
// remove the last message if role is assistant and content is null | ||
// we add this message locally as a placeholder for ourself and OpenAI errors out | ||
// if we send it to them | ||
// Remove the last message if role is assistant and content is null | ||
// and is not a tool call. We add this message locally as a placeholder | ||
// for ourselves, and our API will error if we send it. | ||
.filter((m, i, arr) => !(i === arr.length - 1 && | ||
m.role === 'assistant' && | ||
isToolCalls(m.tool_calls) && | ||
m.content === null))); | ||
} | ||
//# sourceMappingURL=utils.js.map |
@@ -6,3 +6,3 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; | ||
import { useId, useMemo, useRef, useState } from 'react'; | ||
import { toApiMessages } from './chat/utils.js'; | ||
import { toValidApiMessages } from './chat/utils.js'; | ||
import { ChevronDownIcon, ChevronLeftIcon, LoadingIcon } from './icons.js'; | ||
@@ -15,8 +15,6 @@ import { useChatStore, } from './index.js'; | ||
const projectKey = useGlobalStore((state) => state.options.projectKey); | ||
const conversationId = useChatStore((state) => state.conversationId); | ||
const threadId = useChatStore((state) => state.threadId); | ||
const provider = useGlobalStore((state) => state.options.integrations?.createTicket?.provider); | ||
const apiUrl = useGlobalStore((state) => state.options?.apiUrl); | ||
const summary = useGlobalStore((state) => conversationId | ||
? state.tickets?.summaryByConversationId[conversationId] | ||
: undefined); | ||
const summary = useGlobalStore((state) => threadId ? state.tickets?.summaryByThreadId[threadId] : undefined); | ||
const messages = useChatStore((state) => state.messages); | ||
@@ -74,3 +72,3 @@ const [totalFileSize, setTotalFileSize] = useState(0); | ||
} | ||
const transcript = toApiMessages(messages) | ||
const transcript = toValidApiMessages(messages) | ||
.map((m) => { | ||
@@ -77,0 +75,0 @@ return `${m.role === 'user' ? 'Me' : 'AI'}: ${m.content}`; |
@@ -9,3 +9,3 @@ import { type ReactElement, type ComponentPropsWithoutRef } from 'react'; | ||
variant: 'text' | 'icons'; | ||
promptId?: string; | ||
messageId?: string; | ||
showFeedback?: boolean; | ||
@@ -12,0 +12,0 @@ showVotes?: boolean; |
@@ -11,6 +11,6 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; | ||
export function Feedback(props) { | ||
const { message, heading = DEFAULT_MARKPROMPT_OPTIONS.feedback.heading, submitFeedback, abortFeedbackRequest, variant, promptId, showFeedback = true, showVotes = true, showCopy, ...divProps } = props; | ||
const { message, heading = DEFAULT_MARKPROMPT_OPTIONS.feedback.heading, submitFeedback, abortFeedbackRequest, variant, messageId, showFeedback = true, showVotes = true, showCopy, ...divProps } = props; | ||
const [feedback, setFeedback] = useState(); | ||
function handleFeedback(feedback) { | ||
submitFeedback(feedback, promptId); | ||
submitFeedback(feedback, messageId); | ||
setFeedback(feedback); | ||
@@ -17,0 +17,0 @@ } |
@@ -15,3 +15,3 @@ import { type PromptFeedback, type SubmitFeedbackOptions } from '@markprompt/core'; | ||
/** Submit feedback for the current message */ | ||
submitFeedback: (feedback: PromptFeedback, promptId?: string) => void; | ||
submitFeedback: (feedback: PromptFeedback, messageId?: string) => void; | ||
/** Submit CSAT for a thread */ | ||
@@ -18,0 +18,0 @@ submitThreadCSAT: (threadId: string, csat: CSAT) => void; |
@@ -9,10 +9,10 @@ import { submitFeedback as submitFeedbackCore, submitCSAT as submitCSATCore, } from '@markprompt/core'; | ||
const { ref: controllerRef, abort } = useAbortController(); | ||
const submitFeedback = useCallback(async (feedback, promptId) => { | ||
const submitFeedback = useCallback(async (feedback, messageId) => { | ||
abort(); | ||
// we need to be able to associate the feedback to a prompt | ||
if (!promptId) | ||
if (!messageId) | ||
return; | ||
const controller = new AbortController(); | ||
controllerRef.current = controller; | ||
const promise = submitFeedbackCore({ feedback, promptId }, projectKey, { | ||
const promise = submitFeedbackCore({ feedback, messageId }, projectKey, { | ||
...feedbackOptions, | ||
@@ -19,0 +19,0 @@ signal: controller.signal, |
@@ -13,6 +13,6 @@ import { type ReactNode } from 'react'; | ||
tickets?: { | ||
summaryByConversationId: { | ||
[conversationId: string]: ChatViewMessage; | ||
summaryByThreadId: { | ||
[threadId: string]: ChatViewMessage; | ||
}; | ||
createTicketSummary: (conversationId: string, messages: ChatViewMessage[]) => void; | ||
createTicketSummary: (threadId: string, messages: ChatViewMessage[]) => void; | ||
}; | ||
@@ -19,0 +19,0 @@ } |
@@ -6,3 +6,3 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { immer } from 'zustand/middleware/immer'; | ||
import { toApiMessages } from './chat/utils.js'; | ||
import { toValidApiMessages } from './chat/utils.js'; | ||
import { getDefaultView } from './utils.js'; | ||
@@ -35,7 +35,7 @@ function getInitialView(options) { | ||
tickets: { | ||
summaryByConversationId: {}, | ||
createTicketSummary: async (conversationId, messages) => { | ||
summaryByThreadId: {}, | ||
createTicketSummary: async (threadId, messages) => { | ||
const summaryId = crypto.randomUUID(); | ||
set((state) => { | ||
state.tickets.summaryByConversationId[conversationId] = { | ||
state.tickets.summaryByThreadId[threadId] = { | ||
id: summaryId, | ||
@@ -47,3 +47,3 @@ references: [], | ||
const options = { | ||
conversationId: conversationId, | ||
threadId: threadId, | ||
...get().options.chat, | ||
@@ -58,3 +58,3 @@ apiUrl: get().options.apiUrl, | ||
}; | ||
const conversation = toApiMessages(messages) | ||
const conversation = toValidApiMessages(messages) | ||
.map((m) => { | ||
@@ -71,4 +71,3 @@ return `${m.role === 'user' ? 'User' : 'AI'}:\n\n${m.content}`; | ||
set((state) => { | ||
state.tickets.summaryByConversationId[conversationId].state = | ||
'preload'; | ||
state.tickets.summaryByThreadId[threadId].state = 'preload'; | ||
}); | ||
@@ -78,4 +77,4 @@ try { | ||
set((state) => { | ||
state.tickets.summaryByConversationId[conversationId] = { | ||
...state.tickets.summaryByConversationId[conversationId], | ||
state.tickets.summaryByThreadId[threadId] = { | ||
...state.tickets.summaryByThreadId[threadId], | ||
state: 'streaming-answer', | ||
@@ -89,4 +88,4 @@ ...chunk, | ||
set((state) => { | ||
state.tickets.summaryByConversationId[conversationId] = { | ||
...state.tickets.summaryByConversationId[conversationId], | ||
state.tickets.summaryByThreadId[threadId] = { | ||
...state.tickets.summaryByThreadId[threadId], | ||
state: 'cancelled', | ||
@@ -102,4 +101,3 @@ }; | ||
set((state) => { | ||
state.tickets.summaryByConversationId[conversationId].state = | ||
'done'; | ||
state.tickets.summaryByThreadId[threadId].state = 'done'; | ||
}); | ||
@@ -106,0 +104,0 @@ }, |
@@ -15,10 +15,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; | ||
const [isCreatingTicketSummary, setIsCreatingTicketSummary] = useState(false); | ||
const conversationId = useChatStore((state) => state.conversationId); | ||
const threadId = useChatStore((state) => state.threadId); | ||
const messages = useChatStore((state) => state.messages); | ||
const selectConversation = useChatStore((state) => state.selectConversation); | ||
const selectThread = useChatStore((state) => state.selectThread); | ||
const createTicketSummary = useGlobalStore((state) => state.tickets?.createTicketSummary); | ||
useEffect(() => { | ||
// Clear past conversations | ||
selectConversation(undefined); | ||
}, [selectConversation]); | ||
// Clear past thread | ||
selectThread(undefined); | ||
}, [selectThread]); | ||
const placeholder = useMemo(() => { | ||
@@ -43,3 +43,3 @@ const _placeholder = integrations?.createTicket?.chat?.placeholder; | ||
} | ||
if (!messages || messages.length === 0 || !conversationId) { | ||
if (!messages || messages.length === 0 || !threadId) { | ||
setView('ticket'); | ||
@@ -49,7 +49,7 @@ return; | ||
setIsCreatingTicketSummary(true); | ||
await createTicketSummary?.(conversationId, messages); | ||
await createTicketSummary?.(threadId, messages); | ||
setIsCreatingTicketSummary(false); | ||
setView('ticket'); | ||
}, [ | ||
conversationId, | ||
threadId, | ||
createTicketSummary, | ||
@@ -56,0 +56,0 @@ integrations?.createTicket?.enabled, |
@@ -133,3 +133,3 @@ import { type AlgoliaDocSearchHit, type FileSectionReference, type PromptFeedback, type SearchResult, type SubmitFeedbackOptions, type SubmitSearchQueryOptions } from '@markprompt/core'; | ||
*/ | ||
onFeedbackSubmit?: (feedback: PromptFeedback, messages: ChatViewMessage[], promptId?: string) => void; | ||
onFeedbackSubmit?: (feedback: PromptFeedback, messages: ChatViewMessage[], messageId?: string) => void; | ||
} | ||
@@ -136,0 +136,0 @@ export interface AvatarsOptions { |
{ | ||
"name": "@markprompt/react", | ||
"version": "0.43.0", | ||
"version": "0.44.0", | ||
"description": "A headless React component for adding GPT-4 powered search using the Markprompt API.", | ||
@@ -26,3 +26,3 @@ "repository": { | ||
"@floating-ui/react-dom": "^2.0.8", | ||
"@markprompt/core": "^0.27.0", | ||
"@markprompt/core": "^0.28.0", | ||
"@radix-ui/react-accessible-icon": "^1.0.3", | ||
@@ -29,0 +29,0 @@ "@radix-ui/react-dialog": "^1.0.5", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
456802
5287
+ Added@markprompt/core@0.28.3(transitive)
- Removed@markprompt/core@0.27.0(transitive)
Updated@markprompt/core@^0.28.0