Think parsing & styles
This commit is contained in:
parent
d32f675db8
commit
268e5cf5ea
|
|
@ -43,6 +43,12 @@
|
|||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.message[data-role="tool"] .content {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.role {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
|
|
@ -50,6 +56,17 @@
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.reasoningContent {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
opacity: 0.5;
|
||||
border-left: 2px solid currentColor;
|
||||
padding-left: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
|
|
@ -57,6 +74,22 @@
|
|||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.toolCalls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.toolBadge {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
background: var(--bg-active);
|
||||
color: var(--text-dim);
|
||||
border-radius: var(--radius);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.loading {
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
|
|
@ -83,6 +116,21 @@
|
|||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.toggleContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
|
|
@ -128,10 +176,10 @@
|
|||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
align-self: center;
|
||||
margin-top: 8px;
|
||||
margin: 8px 0 28px;
|
||||
|
||||
&:hover {
|
||||
color: var(--text);
|
||||
border-color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,9 +18,10 @@
|
|||
--radius: 4px;
|
||||
--transition: 0.15s ease;
|
||||
|
||||
--textColor: #DCDCD2;
|
||||
--textColor: #DCDCD2;
|
||||
--italicColor: #AFAFAF;
|
||||
--quoteColor: #D4E5FF;
|
||||
--quoteColor: #D4E5FF;
|
||||
--codeBg: #49483e;
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useInputState } from "@common/hooks/useInputState";
|
||||
import { Sidebar } from "./sidebar";
|
||||
import { useAppState, type ChatMessage } from "../contexts/state";
|
||||
import styles from '../assets/chat-sidebar.module.css';
|
||||
|
|
@ -10,9 +11,11 @@ import clsx from "clsx";
|
|||
|
||||
export const ChatSidebar = () => {
|
||||
const appState = useAppState();
|
||||
const { currentStory, dispatch, connection, model } = appState;
|
||||
const [input, setInput] = useState('');
|
||||
const { currentStory, dispatch, connection, model, enableThinking } = appState;
|
||||
const [input, setInput] = useInputState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isCollapsed, setCollapsed] = useState(false);
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const messagesRef = useRef<HTMLDivElement>(null);
|
||||
const abortControllerRef = useRef<AbortController>(new AbortController());
|
||||
|
|
@ -26,6 +29,12 @@ export const ChatSidebar = () => {
|
|||
}
|
||||
}, [currentStory?.chatMessages.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (messagesRef.current) {
|
||||
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
|
||||
}
|
||||
}, [isCollapsed]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
abortControllerRef.current?.abort();
|
||||
|
|
@ -50,7 +59,8 @@ export const ChatSidebar = () => {
|
|||
message: {
|
||||
id: assistantMessageId,
|
||||
role: 'assistant',
|
||||
content: 'Generating...',
|
||||
content: '',
|
||||
reasoning_content: 'Generating...',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -64,6 +74,7 @@ export const ChatSidebar = () => {
|
|||
|
||||
try {
|
||||
let accumulatedContent = '';
|
||||
let accumulatedReasoning = '';
|
||||
let tool_calls: LLM.ToolCall[] | undefined;
|
||||
|
||||
for await (const chunk of LLM.generateStream(connection, request)) {
|
||||
|
|
@ -76,6 +87,12 @@ export const ChatSidebar = () => {
|
|||
const content = delta?.content;
|
||||
if (content) {
|
||||
accumulatedContent += content;
|
||||
}
|
||||
const reasoningContent = delta?.reasoning_content;
|
||||
if (reasoningContent) {
|
||||
accumulatedReasoning += reasoningContent;
|
||||
}
|
||||
if (content || reasoningContent) {
|
||||
dispatch({
|
||||
type: 'ADD_CHAT_MESSAGE',
|
||||
storyId: currentStory.id,
|
||||
|
|
@ -83,6 +100,7 @@ export const ChatSidebar = () => {
|
|||
id: assistantMessageId,
|
||||
role: 'assistant',
|
||||
content: accumulatedContent,
|
||||
reasoning_content: accumulatedReasoning,
|
||||
tool_calls,
|
||||
},
|
||||
});
|
||||
|
|
@ -95,6 +113,7 @@ export const ChatSidebar = () => {
|
|||
id: assistantMessageId,
|
||||
role: 'assistant',
|
||||
content: accumulatedContent,
|
||||
reasoning_content: accumulatedReasoning,
|
||||
tool_calls,
|
||||
};
|
||||
dispatch({
|
||||
|
|
@ -171,7 +190,7 @@ export const ChatSidebar = () => {
|
|||
const isDisabled = !currentStory || !connection || !model || isLoading;
|
||||
|
||||
return (
|
||||
<Sidebar side="right">
|
||||
<Sidebar side="right" onCollapseChanged={setCollapsed}>
|
||||
<div class={styles.chat}>
|
||||
{!currentStory ? (
|
||||
<div class={styles.placeholder}>
|
||||
|
|
@ -190,10 +209,27 @@ export const ChatSidebar = () => {
|
|||
{currentStory.chatMessages.map((message) => (
|
||||
<div key={message.id} class={styles.message} data-role={message.role}>
|
||||
<div class={styles.role}>{message.role}</div>
|
||||
|
||||
{message.role === 'assistant' && message.reasoning_content && (
|
||||
<div class={styles.reasoningContent}>
|
||||
{message.reasoning_content}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
class={styles.content}
|
||||
dangerouslySetInnerHTML={{ __html: highlight(message.content) }}
|
||||
dangerouslySetInnerHTML={{ __html: highlight(message.content, false).trim() }}
|
||||
/>
|
||||
|
||||
{message.role === 'assistant' && message.tool_calls && (
|
||||
<div class={styles.toolCalls}>
|
||||
{message.tool_calls.map((tool) => (
|
||||
<span key={tool.id} class={styles.toolBadge}>
|
||||
{tool.function.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{error && (
|
||||
|
|
@ -209,10 +245,24 @@ export const ChatSidebar = () => {
|
|||
)}
|
||||
{currentStory && (
|
||||
<div class={styles.inputContainer}>
|
||||
{model?.support_thinking &&
|
||||
<label class={styles.toggleContainer}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={enableThinking}
|
||||
onChange={(e) => dispatch({
|
||||
type: 'SET_ENABLE_THINKING',
|
||||
enable: (e.target as HTMLInputElement).checked,
|
||||
})}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<span>Enable thinking</span>
|
||||
</label>
|
||||
}
|
||||
<textarea
|
||||
class={styles.input}
|
||||
value={input}
|
||||
onInput={(e) => setInput((e.target as HTMLTextAreaElement).value)}
|
||||
onInput={setInput}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={isDisabled ? 'Connect to an LLM server to chat' : 'Type a message...'}
|
||||
rows={3}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const SettingsModal = ({ onClose }: Props) => {
|
|||
const { connection, model, dispatch } = useAppState();
|
||||
const [url, setUrl] = useInputState(connection?.url ?? "");
|
||||
const [apiKey, setApiKey] = useInputState(connection?.apiKey ?? "");
|
||||
const [selectedModel, setSelectedModel] = useInputState(model ?? "");
|
||||
const [selectedModel, setSelectedModel] = useInputState(model?.id ?? "");
|
||||
const [update, triggerFetch] = useUpdate();
|
||||
|
||||
const urlRef = useRef(url);
|
||||
|
|
@ -43,17 +43,18 @@ export const SettingsModal = ({ onClose }: Props) => {
|
|||
const isLoadingModels = connectionToFetch != null && modelsData == undefined;
|
||||
const groupedModels = useMemo(() => {
|
||||
const sorted = (modelsData ?? []).sort((a, b) => {
|
||||
// Sort by tool support first (true before false)
|
||||
if (a.support_tools !== b.support_tools) {
|
||||
return a.support_tools ? -1 : 1;
|
||||
const aWeight = Number(a.support_tools) * 2 + Number(a.support_thinking);
|
||||
const bWeight = Number(b.support_tools) * 2 + Number(b.support_thinking);
|
||||
if (aWeight !== bWeight) {
|
||||
return bWeight - aWeight;
|
||||
}
|
||||
// Then by max context (bigger first, undefined treated as 0)
|
||||
|
||||
const aContext = a.max_context ?? 0;
|
||||
const bContext = b.max_context ?? 0;
|
||||
if (aContext !== bContext) {
|
||||
return bContext - aContext;
|
||||
}
|
||||
// Then by name (alphabetically)
|
||||
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
|
||||
|
|
@ -83,9 +84,10 @@ export const SettingsModal = ({ onClose }: Props) => {
|
|||
type: 'SET_CONNECTION',
|
||||
connection: connectionToFetch,
|
||||
});
|
||||
const selectedModelInfo = modelsData?.find(m => m.id === selectedModel) ?? null;
|
||||
dispatch({
|
||||
type: 'SET_MODEL',
|
||||
model: selectedModel || null,
|
||||
model: selectedModelInfo,
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
|
@ -153,7 +155,7 @@ export const SettingsModal = ({ onClose }: Props) => {
|
|||
<optgroup key={context} label={`${context} context`}>
|
||||
{models.map(m => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.support_tools ? '🔧 ' : ''}{m.id} {m.max_length ? `(len: ${m.max_length})` : ''}
|
||||
{m.support_tools ? '🔨' : ''}{m.support_thinking ? '🧠' : ''}{m.id} {m.max_length ? `(len: ${m.max_length})` : ''}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
|
|
|
|||
|
|
@ -6,14 +6,20 @@ import styles from '../assets/sidebar.module.css';
|
|||
interface Props {
|
||||
side: 'left' | 'right';
|
||||
children?: ComponentChildren;
|
||||
onCollapseChanged?: (collapsed: boolean) => void;
|
||||
}
|
||||
|
||||
export const Sidebar = ({ side, children }: Props) => {
|
||||
export const Sidebar = ({ side, children, onCollapseChanged }: Props) => {
|
||||
const open = useBool(true);
|
||||
|
||||
const handleToggle = () => {
|
||||
open.toggle();
|
||||
onCollapseChanged?.(!open.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={clsx(styles.sidebar, open.value ? styles.open : styles.closed)} data-side={side}>
|
||||
<button class={styles.toggle} onClick={open.toggle}>
|
||||
<button class={styles.toggle} onClick={handleToggle}>
|
||||
{side === 'left' ? (open.value ? '◀' : '▶') : (open.value ? '▶' : '◀')}
|
||||
</button>
|
||||
{open.value && (
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ interface IState {
|
|||
stories: Story[];
|
||||
currentStoryId: string | null;
|
||||
connection: LLM.Connection | null;
|
||||
model: string | null;
|
||||
model: LLM.ModelInfo | null;
|
||||
enableThinking: boolean;
|
||||
}
|
||||
|
||||
// ─── Actions ─────────────────────────────────────────────────────────────────
|
||||
|
|
@ -32,12 +33,14 @@ type Action =
|
|||
| { type: 'CREATE_STORY'; title: string }
|
||||
| { type: 'RENAME_STORY'; id: string; title: string }
|
||||
| { type: 'EDIT_STORY'; id: string; text: string }
|
||||
| { type: 'APPEND_TO_STORY'; id: string; text: string }
|
||||
| { type: 'DELETE_STORY'; id: string }
|
||||
| { type: 'SELECT_STORY'; id: string }
|
||||
| { type: 'ADD_CHAT_MESSAGE'; storyId: string; message: ChatMessage }
|
||||
| { type: 'CLEAR_CHAT'; storyId: string }
|
||||
| { type: 'SET_CONNECTION'; connection: LLM.Connection | null }
|
||||
| { type: 'SET_MODEL'; model: string | null };
|
||||
| { type: 'SET_MODEL'; model: LLM.ModelInfo | null }
|
||||
| { type: 'SET_ENABLE_THINKING'; enable: boolean };
|
||||
|
||||
// ─── Initial State ───────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -46,6 +49,7 @@ const DEFAULT_STATE: IState = {
|
|||
currentStoryId: null,
|
||||
connection: null,
|
||||
model: null,
|
||||
enableThinking: false,
|
||||
};
|
||||
|
||||
// ─── Reducer ─────────────────────────────────────────────────────────────────
|
||||
|
|
@ -121,6 +125,14 @@ function reducer(state: IState, action: Action): IState {
|
|||
),
|
||||
};
|
||||
}
|
||||
case 'APPEND_TO_STORY': {
|
||||
return {
|
||||
...state,
|
||||
stories: state.stories.map(s =>
|
||||
s.id === action.id ? { ...s, text: s.text + action.text } : s
|
||||
),
|
||||
};
|
||||
}
|
||||
case 'SET_CONNECTION': {
|
||||
return {
|
||||
...state,
|
||||
|
|
@ -133,6 +145,12 @@ function reducer(state: IState, action: Action): IState {
|
|||
model: action.model,
|
||||
};
|
||||
}
|
||||
case 'SET_ENABLE_THINKING': {
|
||||
return {
|
||||
...state,
|
||||
enableThinking: action.enable,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +160,8 @@ export interface AppState {
|
|||
stories: Story[];
|
||||
currentStory: Story | null;
|
||||
connection: LLM.Connection | null;
|
||||
model: string | null;
|
||||
model: LLM.ModelInfo | null;
|
||||
enableThinking: boolean;
|
||||
dispatch: (action: Action) => void;
|
||||
}
|
||||
|
||||
|
|
@ -160,6 +179,7 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
|||
currentStory: state.stories.find(s => s.id === state.currentStoryId) ?? null,
|
||||
connection: state.connection,
|
||||
model: state.model,
|
||||
enableThinking: state.enableThinking,
|
||||
dispatch,
|
||||
}), [state]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +1,60 @@
|
|||
export const highlight = (message: string): string => {
|
||||
const replaceRegex = /(\*\*?|")/ig;
|
||||
export const highlight = (message: string, keepMarkup = true): string => {
|
||||
let resultHTML = '';
|
||||
const replaceRegex = /(\*\*?|"|```|`)/ig;
|
||||
const splitToken = '___SPLIT_AWOORWA___';
|
||||
|
||||
const preparedMessage = message.replace(replaceRegex, `${splitToken}$1${splitToken}`);
|
||||
const parts = preparedMessage.split(splitToken);
|
||||
|
||||
const stack: string[] = [];
|
||||
|
||||
let resultHTML = '';
|
||||
let inCodeBlock = false;
|
||||
|
||||
for (const part of parts) {
|
||||
const isClose = stack.at(-1) === part;
|
||||
const keepPart = keepMarkup || part === '"';
|
||||
|
||||
if (inCodeBlock) {
|
||||
if (part === '```' && isClose) {
|
||||
inCodeBlock = false;
|
||||
stack.pop();
|
||||
resultHTML += `${keepPart ? part : ''}</span>`;
|
||||
} else {
|
||||
resultHTML += part;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isClose) {
|
||||
stack.pop();
|
||||
if (part === '*' || part === '**' || part === '"') {
|
||||
resultHTML += `${part}</span>`;
|
||||
if (part === '*' || part === '**' || part === '"' || part === '`' || part === '```') {
|
||||
resultHTML += `${keepPart ? part : ''}</span>`;
|
||||
}
|
||||
} else {
|
||||
if (part === '*') {
|
||||
stack.push(part);
|
||||
resultHTML += `<span style="font-style:italic;color:var(--italicColor)">`;
|
||||
resultHTML += `<span style="font-style:italic;color:var(--italicColor)">${keepPart ? part : ''}`;
|
||||
} else if (part === '**') {
|
||||
stack.push(part);
|
||||
resultHTML += `<span style="font-weight:bold">`;
|
||||
resultHTML += `<span style="font-weight:bold">${keepPart ? part : ''}`;
|
||||
} else if (part === '"') {
|
||||
stack.push(part);
|
||||
resultHTML += `<span style="color:var(--quoteColor)">`;
|
||||
resultHTML += `<span style="color:var(--quoteColor)">"`;
|
||||
} else if (part === '```') {
|
||||
stack.push(part);
|
||||
inCodeBlock = true;
|
||||
resultHTML += `<span style="font-family:monospace;background:var(--codeBg);padding:0.5em;display:block;border-radius:var(--radius)">`;
|
||||
} else if (part === '`') {
|
||||
stack.push(part);
|
||||
resultHTML += `<span style="font-family:monospace;background:var(--codeBg);padding:0.1em 0.3em;border-radius:0.2em">`;
|
||||
} else {
|
||||
resultHTML += part;
|
||||
}
|
||||
resultHTML += part;
|
||||
}
|
||||
}
|
||||
|
||||
while (stack.length) {
|
||||
const part = stack.pop();
|
||||
if (part === '*' || part === '**' || part === '"') {
|
||||
if (part === '*' || part === '**' || part === '"' || part === '`' || part === '```') {
|
||||
resultHTML += `</span>`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace LLM {
|
|||
interface ChatMessageAssistant {
|
||||
role: 'assistant';
|
||||
content: string;
|
||||
reasoning_content?: string;
|
||||
tool_calls?: ToolCall[];
|
||||
}
|
||||
|
||||
|
|
@ -78,11 +79,7 @@ namespace LLM {
|
|||
function: {
|
||||
name: string;
|
||||
description?: string;
|
||||
parameters: {
|
||||
type: 'object';
|
||||
properties: Record<string, ToolParameter>;
|
||||
required?: string[];
|
||||
};
|
||||
parameters: ToolObjectParameter;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -97,6 +94,7 @@ namespace LLM {
|
|||
top_p?: number;
|
||||
frequency_penalty?: number;
|
||||
presence_penalty?: number;
|
||||
enable_thinking?: boolean;
|
||||
}
|
||||
|
||||
export interface ChatCompletionChoice {
|
||||
|
|
@ -121,7 +119,12 @@ namespace LLM {
|
|||
|
||||
export interface ChatCompletionChunkChoice {
|
||||
index: number;
|
||||
delta: { role?: string; content?: string; tool_calls?: ToolCall[] };
|
||||
delta: {
|
||||
role?: string;
|
||||
content?: string;
|
||||
reasoning_content?: string;
|
||||
tool_calls?: ToolCall[];
|
||||
};
|
||||
finish_reason: 'stop' | 'tool_calls' | null;
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +142,8 @@ namespace LLM {
|
|||
created: number;
|
||||
owned_by: string;
|
||||
support_tools: boolean;
|
||||
support_infill: boolean;
|
||||
support_thinking: boolean;
|
||||
max_context?: number;
|
||||
max_length?: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Tools } from "./tools";
|
|||
|
||||
namespace Prompt {
|
||||
export function compilePrompt(state: AppState, newMessages: LLM.ChatMessage[] = []): LLM.ChatCompletionRequest | null {
|
||||
const { currentStory, model } = state;
|
||||
const { currentStory, model, enableThinking } = state;
|
||||
|
||||
if (!currentStory || !model) {
|
||||
return null;
|
||||
|
|
@ -19,10 +19,11 @@ namespace Prompt {
|
|||
messages.push(...newMessages);
|
||||
|
||||
return {
|
||||
model,
|
||||
model: model.id,
|
||||
messages,
|
||||
tools: Tools.getTools(),
|
||||
// TODO banned_tokens
|
||||
enable_thinking: enableThinking,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { formatError } from "@common/errors";
|
||||
import type { AppState } from "../contexts/state";
|
||||
import LLM from "./llm";
|
||||
import type LLM from "./llm";
|
||||
|
||||
export namespace Tools {
|
||||
interface Tool {
|
||||
|
|
@ -10,20 +10,35 @@ export namespace Tools {
|
|||
}
|
||||
|
||||
const TOOLS: Record<string, Tool> = {
|
||||
'test': {
|
||||
handler: async (args) => (
|
||||
`Test successful, received: ${JSON.stringify(args)}`
|
||||
),
|
||||
description: 'A simple test function',
|
||||
'append_to_story': {
|
||||
handler: async (args, appState) => {
|
||||
if (!args || typeof args !== 'object' || !('text' in args)) {
|
||||
return 'Error: Missing required argument "text"';
|
||||
}
|
||||
const { text } = args as { text: string };
|
||||
if (typeof text !== 'string') {
|
||||
return 'Error: Argument "text" must be a string';
|
||||
}
|
||||
if (!appState.currentStory) {
|
||||
return 'Error: No story selected';
|
||||
}
|
||||
appState.dispatch({
|
||||
type: 'APPEND_TO_STORY',
|
||||
id: appState.currentStory.id,
|
||||
text
|
||||
});
|
||||
return 'Text appended successfully';
|
||||
},
|
||||
description: 'Append text to the current story',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message: {
|
||||
text: {
|
||||
type: 'string',
|
||||
description: 'The test message',
|
||||
description: 'The text to append to the story',
|
||||
},
|
||||
},
|
||||
required: ['message'],
|
||||
required: ['text'],
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
@ -57,6 +72,9 @@ export namespace Tools {
|
|||
|
||||
try {
|
||||
const result = await handler(args, appState);
|
||||
if (typeof result === 'string') {
|
||||
return result;
|
||||
}
|
||||
return JSON.stringify(result);
|
||||
} catch (err) {
|
||||
return formatError(err, 'Error executing tool');
|
||||
|
|
|
|||
Loading…
Reference in New Issue