Compare commits
2 Commits
8acac07c04
...
97533daa91
| Author | SHA1 | Date |
|---|---|---|
|
|
97533daa91 | |
|
|
d5481e1a13 |
9
bun.lock
9
bun.lock
|
|
@ -10,6 +10,7 @@
|
|||
"@huggingface/jinja": "0.5.5",
|
||||
"@huggingface/tokenizers": "0.1.1",
|
||||
"@inquirer/select": "2.3.10",
|
||||
"@ironarachne/made-up-names": "^3.0.0",
|
||||
"@types/node": "^25.3.3",
|
||||
"ace-builds": "1.36.3",
|
||||
"clsx": "2.1.1",
|
||||
|
|
@ -21,7 +22,7 @@
|
|||
"@types/bun": "^1.3.14",
|
||||
"@types/html-minifier": "4.0.5",
|
||||
"@types/inquirer": "9.0.7",
|
||||
"@types/web-bluetooth": "^0.0.21",
|
||||
"@types/web-bluetooth": "0.0.21",
|
||||
"browser-detect": "0.2.28",
|
||||
"eruda": "3.2.3",
|
||||
"html-minifier": "4.0.0",
|
||||
|
|
@ -52,6 +53,12 @@
|
|||
|
||||
"@inquirer/type": ["@inquirer/type@1.4.0", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-AjOqykVyjdJQvtfkNDGUyMYGF8xN50VUxftCQWsOyIo4DFRLr6VQhW0VItGI1JIyQGCGgIpKa7hMMwNhZb4OIw=="],
|
||||
|
||||
"@ironarachne/made-up-names": ["@ironarachne/made-up-names@3.0.0", "", { "dependencies": { "@ironarachne/rng": "^3.0.1", "@ironarachne/word-generator": "^3.0.1" } }, "sha512-EYwGC1bi9r8/VVbeDgm6jzp8/igC0vs1C2KGK0LIrBcSWg10UNbd9ue6+pXrKQeuCPVdpph2I7SRAnFQ80vIFA=="],
|
||||
|
||||
"@ironarachne/rng": ["@ironarachne/rng@3.0.1", "", {}, "sha512-256DtrY7UDMnSp9jTz2aM0JvOuXExcGd/SZlZwSutKR0Aehcq42Q/rGj4SpxxrOu6INb/jeiB6w2eIxHNQEA8Q=="],
|
||||
|
||||
"@ironarachne/word-generator": ["@ironarachne/word-generator@3.0.1", "", { "dependencies": { "@ironarachne/rng": "^3.0.1" } }, "sha512-A5NurF35phpZENXDy+DWUSjnRG/o8nhs1p2JCd5tBy01Ka0t2ANpYJUnDwMNXuZT80F7sLtx2wr9Ekmgxpre+g=="],
|
||||
|
||||
"@jimp/core": ["@jimp/core@1.6.1", "", { "dependencies": { "@jimp/file-ops": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^21.3.3", "mime": "3" } }, "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A=="],
|
||||
|
||||
"@jimp/diff": ["@jimp/diff@1.6.1", "", { "dependencies": { "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "pixelmatch": "^5.3.0" } }, "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ=="],
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
"@huggingface/jinja": "0.5.5",
|
||||
"@huggingface/tokenizers": "0.1.1",
|
||||
"@inquirer/select": "2.3.10",
|
||||
"@ironarachne/made-up-names": "^3.0.0",
|
||||
"@types/node": "^25.3.3",
|
||||
"ace-builds": "1.36.3",
|
||||
"clsx": "2.1.1",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
}
|
||||
|
||||
.root {
|
||||
position: relative;
|
||||
white-space: pre-wrap;
|
||||
outline: none;
|
||||
word-wrap: break-word;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
import type { JSX } from "preact";
|
||||
import clsx from "clsx";
|
||||
import styles from "../assets/content-editable.module.css";
|
||||
|
|
@ -9,6 +9,7 @@ type Props = Omit<JSX.HTMLAttributes<HTMLDivElement>, 'value' | 'onInput'> & {
|
|||
placeholder?: string;
|
||||
autoLines?: boolean;
|
||||
highlight?: boolean;
|
||||
disabled?: boolean;
|
||||
onInput?: JSX.EventHandler<JSX.TargetedInputEvent<HTMLDivElement>>;
|
||||
};
|
||||
|
||||
|
|
@ -83,6 +84,7 @@ export const ContentEditable = ({
|
|||
placeholder,
|
||||
autoLines,
|
||||
highlight: enableHighlight,
|
||||
disabled,
|
||||
onInput,
|
||||
class: externalClass,
|
||||
...props
|
||||
|
|
@ -118,10 +120,10 @@ export const ContentEditable = ({
|
|||
<div
|
||||
ref={ref}
|
||||
{...props}
|
||||
contentEditable
|
||||
contentEditable={!disabled}
|
||||
data-placeholder={value?.replaceAll('\n', '').length ? undefined : placeholder}
|
||||
class={clsx(styles.root, autoLines && styles.autoLines, externalClass)}
|
||||
onInput={handleInput}
|
||||
class={clsx(styles.root, autoLines && styles.autoLines, externalClass, disabled && styles.disabled)}
|
||||
onInput={disabled ? undefined : handleInput}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -194,7 +194,8 @@ export const ChatPanel = ({ visible }: { visible: boolean }) => {
|
|||
const charName = currentWorld.title ?? 'Assistant';
|
||||
const prefix = `${charName}: `;
|
||||
let accumulatedContent = continuedMessage?.content ?? '';
|
||||
let accumulatedReasoning = continuedMessage?.role === 'assistant' ? continuedMessage.reasoning_content ?? '' : '';
|
||||
let accumulatedReasoning = continuedMessage?.role === 'assistant'
|
||||
? continuedMessage.reasoning_content ?? continuedMessage.reasoning ?? '' : '';
|
||||
let tool_calls: LLM.ToolCall[] | undefined;
|
||||
|
||||
for await (const chunk of LLM.generateStream(connection, request)) {
|
||||
|
|
@ -207,7 +208,7 @@ export const ChatPanel = ({ visible }: { visible: boolean }) => {
|
|||
const delta = chunk.choices[0]?.delta;
|
||||
|
||||
if (delta?.tool_calls) {
|
||||
tool_calls = delta.tool_calls;
|
||||
tool_calls = Prompt.applyDelta(tool_calls, delta.tool_calls);
|
||||
}
|
||||
|
||||
const content = delta?.content;
|
||||
|
|
@ -217,7 +218,7 @@ export const ChatPanel = ({ visible }: { visible: boolean }) => {
|
|||
accumulatedContent = accumulatedContent.trimStart().slice(prefix.length).trimStart();
|
||||
}
|
||||
}
|
||||
const reasoningContent = delta?.reasoning_content;
|
||||
const reasoningContent = delta?.reasoning_content ?? delta?.reasoning;
|
||||
if (reasoningContent) {
|
||||
accumulatedReasoning += reasoningContent;
|
||||
}
|
||||
|
|
@ -231,6 +232,7 @@ export const ChatPanel = ({ visible }: { visible: boolean }) => {
|
|||
role: targetRole,
|
||||
content: accumulatedContent,
|
||||
reasoning_content: accumulatedReasoning,
|
||||
reasoning: accumulatedReasoning,
|
||||
tool_calls,
|
||||
},
|
||||
});
|
||||
|
|
@ -241,6 +243,7 @@ export const ChatPanel = ({ visible }: { visible: boolean }) => {
|
|||
role: targetRole,
|
||||
content: accumulatedContent,
|
||||
reasoning_content: accumulatedReasoning,
|
||||
reasoning: accumulatedReasoning,
|
||||
tool_calls,
|
||||
};
|
||||
dispatch({
|
||||
|
|
@ -251,6 +254,7 @@ export const ChatPanel = ({ visible }: { visible: boolean }) => {
|
|||
});
|
||||
|
||||
if (tool_calls) {
|
||||
console.log(tool_calls);
|
||||
const toolMessages: ChatMessage[] = [];
|
||||
for (const tool of tool_calls) {
|
||||
if (abortControllerRef.current?.signal.aborted) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useInputState } from "@common/hooks/useInputState";
|
|||
import { useUpdate } from "@common/hooks/useUpdate";
|
||||
import { fuzzyMatch } from "@common/utils";
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useMemo, useRef } from "preact/hooks";
|
||||
import { useCallback, useEffect, useMemo, useRef } from "preact/hooks";
|
||||
import styles from "../../assets/settings-modal.module.css";
|
||||
import { useAppState } from "../../contexts/state";
|
||||
import LLM from "../../utils/llm";
|
||||
|
|
@ -40,13 +40,15 @@ export const ConnectionSettings = () => {
|
|||
const textModelsData = useMemo(() => modelsData?.filter(LLM.isTextModel), [modelsData]);
|
||||
const imageModelsData = useMemo(() => modelsData?.filter(LLM.isImageModel), [modelsData]);
|
||||
const isLoadingModels = connectionToFetch != null && textModelsData == undefined;
|
||||
const param = useCallback((m: LLM.ModelInfoText, param: string) =>
|
||||
(m.supported_parameters ?? m.top_provider?.supported_parameters)?.includes(param), []);
|
||||
|
||||
const [modelFilter, setModelFilter] = useInputState("");
|
||||
|
||||
const groupedModels = useMemo(() => {
|
||||
const sorted = (textModelsData ?? []).sort((a, b) => {
|
||||
const aWeight = Number(a.supported_parameters.includes('tools')) * 2 + Number(a.supported_parameters.includes('reasoning'));
|
||||
const bWeight = Number(b.supported_parameters.includes('tools')) * 2 + Number(b.supported_parameters.includes('reasoning'));
|
||||
const aWeight = Number(param(a, 'tools')) * 2 + Number(param(a, 'reasoning'));
|
||||
const bWeight = Number(param(b, 'tools')) * 2 + Number(param(b, 'reasoning'));
|
||||
if (aWeight !== bWeight) return bWeight - aWeight;
|
||||
if (a.context_length !== b.context_length) return b.context_length - a.context_length;
|
||||
return a.id.localeCompare(b.id);
|
||||
|
|
@ -55,7 +57,7 @@ export const ConnectionSettings = () => {
|
|||
return Array.from(groups.entries())
|
||||
.sort((a, b) => b[0] - a[0])
|
||||
.map(([context, models]) => ({ context, models }));
|
||||
}, [textModelsData]);
|
||||
}, [textModelsData, param]);
|
||||
|
||||
const filteredGroupedModels = useMemo(() => {
|
||||
if (!modelFilter) return groupedModels;
|
||||
|
|
@ -167,7 +169,7 @@ export const ConnectionSettings = () => {
|
|||
<optgroup key={context} label={`${context} context`}>
|
||||
{models.map(m => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.supported_parameters.includes('tools') ? '🔨' : ''}{m.supported_parameters.includes('reasoning') ? '🧠' : ''}{m.id} {m.top_provider.max_completion_tokens ? `(len: ${m.top_provider.max_completion_tokens})` : ''}
|
||||
{param(m, 'tools') ? '🔨' : ''}{param(m, 'reasoning') ? '🧠' : ''}{m.id} {m.top_provider.max_completion_tokens ? `(len: ${m.top_provider.max_completion_tokens})` : ''}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ namespace LLM {
|
|||
role: 'assistant';
|
||||
content: string;
|
||||
reasoning_content?: string;
|
||||
reasoning?: string;
|
||||
tool_calls?: ToolCall[];
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +102,7 @@ namespace LLM {
|
|||
role?: string;
|
||||
content?: string;
|
||||
reasoning_content?: string;
|
||||
reasoning?: string;
|
||||
tool_calls?: ToolCall[];
|
||||
};
|
||||
finish_reason: 'stop' | 'tool_calls' | null;
|
||||
|
|
@ -168,7 +170,7 @@ namespace LLM {
|
|||
object: 'model';
|
||||
created: number;
|
||||
owned_by: string;
|
||||
supported_parameters: string[];
|
||||
supported_parameters?: string[];
|
||||
architecture?: {
|
||||
input_modalities: Modality[];
|
||||
output_modalities: Modality[];
|
||||
|
|
@ -181,6 +183,7 @@ namespace LLM {
|
|||
context_length: number;
|
||||
max_completion_tokens: number;
|
||||
is_moderated: boolean;
|
||||
supported_parameters?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +235,7 @@ namespace LLM {
|
|||
};
|
||||
|
||||
const url = new URL(connection.url);
|
||||
url.pathname = path;
|
||||
url.pathname = url.pathname.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '');
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
|
|
@ -256,7 +259,7 @@ namespace LLM {
|
|||
};
|
||||
|
||||
const url = new URL(connection.url);
|
||||
url.pathname = path;
|
||||
url.pathname = url.pathname.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '');
|
||||
|
||||
using sse = new SSE(url.toString(), {
|
||||
headers,
|
||||
|
|
@ -270,7 +273,7 @@ namespace LLM {
|
|||
if (isMessageEvent(e)) {
|
||||
if (e.data === '[DONE]') {
|
||||
controller.close();
|
||||
} else {
|
||||
} else if (e.data) {
|
||||
controller.enqueue(e.data);
|
||||
}
|
||||
}
|
||||
|
|
@ -310,7 +313,7 @@ namespace LLM {
|
|||
}
|
||||
|
||||
function isMessageEvent(e: unknown): e is { data: string } {
|
||||
return e != null && typeof e === 'object' && 'data' in e && typeof e.data === 'string';
|
||||
return e != null && typeof e === 'object' && 'data' in e && typeof e.data === 'string' && Boolean(e.data);
|
||||
}
|
||||
|
||||
export async function getModels(connection: Connection): Promise<ModelsResponse<ModelInfo>> {
|
||||
|
|
|
|||
|
|
@ -365,11 +365,12 @@ namespace Prompt {
|
|||
newMessages?: Iterable<ChatMessage>;
|
||||
excludedMessageIds?: Iterable<string>;
|
||||
continueLast?: boolean;
|
||||
keepThinking?: boolean;
|
||||
}
|
||||
|
||||
export function compilePrompt(
|
||||
state: AppState,
|
||||
config: CompileConfig = {},
|
||||
config: CompileConfig = { keepThinking: true },
|
||||
): LLM.ChatCompletionRequest | null {
|
||||
const { currentStory, model, enableThinking, currentWorld } = state;
|
||||
const {
|
||||
|
|
@ -401,7 +402,13 @@ namespace Prompt {
|
|||
const charName = currentWorld?.title || 'Assistant';
|
||||
const userName = state.userName || 'User';
|
||||
const applyVars = (msgs: ChatMessage[]) =>
|
||||
msgs.map(m => ({ ...m, content: stripDataUrlImages(substituteVars(state, m.content)) }));
|
||||
msgs.map(m => ({
|
||||
...m,
|
||||
content: stripDataUrlImages(substituteVars(state, m.content)),
|
||||
reasoning_content: config.keepThinking && ('reasoning_content' in m)
|
||||
? m.reasoning_content
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
// Chat-only world: format messages with name prefixes
|
||||
if (currentWorld?.chatOnly) {
|
||||
|
|
@ -455,6 +462,27 @@ namespace Prompt {
|
|||
...state.generationSettings,
|
||||
};
|
||||
}
|
||||
|
||||
export function applyDelta(input: any, delta: any) {
|
||||
if (Array.isArray(delta)) {
|
||||
const result = [...(input ?? [])];
|
||||
for (const item of delta) {
|
||||
result[item.index] = applyDelta(result[item.index], item);
|
||||
}
|
||||
return result;
|
||||
} else if (typeof delta === 'object') {
|
||||
const result = { ...input };
|
||||
for (const key of Object.keys(delta)) {
|
||||
const value = delta[key];
|
||||
result[key] = applyDelta(result[key], value);
|
||||
}
|
||||
return result;
|
||||
} else if (typeof delta === 'string') {
|
||||
return (input ?? '') + delta;
|
||||
} else {
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Prompt;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { formatErrorMessage } from "@common/errors";
|
|||
import { Type, type Static, type TObject } from '@common/typebox';
|
||||
import { CharacterRole, LocationScale, type AppState, type Character, type Location } from "../contexts/state";
|
||||
import LLM from "./llm";
|
||||
import { getClassicRaceNamePatternSet, getNameGeneratorForPatternSet, getSupportedClassicRaceNamePatternSets, type NameGeneratorPatternSet } from "@ironarachne/made-up-names";
|
||||
|
||||
const VALID_SCALES = Object.values(LocationScale);
|
||||
const VALID_ROLES = Object.values(CharacterRole);
|
||||
|
|
@ -101,7 +102,7 @@ export namespace Tools {
|
|||
appState.dispatch({
|
||||
type: 'ADD_CHARACTER',
|
||||
worldId: appState.currentWorld.id,
|
||||
storyId: appState.currentStory.id,
|
||||
storyId: args.world ? null : appState.currentStory.id,
|
||||
character: {
|
||||
id: crypto.randomUUID(),
|
||||
name: args.name.trim(),
|
||||
|
|
@ -134,6 +135,7 @@ export namespace Tools {
|
|||
}),
|
||||
{ description: 'Optional list of relationships with other characters (allowed only when adding a new character)' }
|
||||
)),
|
||||
world: Type.Optional(Type.Boolean({ description: "If `true`, entry would be visible to all stories under that world" })),
|
||||
}),
|
||||
}),
|
||||
'set_character_relation': tool({
|
||||
|
|
@ -150,7 +152,7 @@ export namespace Tools {
|
|||
return `Error: Target character "${args.target_name.trim()}" not found, please add it first.`;
|
||||
}
|
||||
const inStory = appState.currentStory.characters.some(c => c.id === character.id);
|
||||
const storyId = inStory ? appState.currentStory.id : null;
|
||||
const storyId = inStory && !args.world ? appState.currentStory.id : null;
|
||||
const existingRelationIndex = character.relations.findIndex(r => r.name === args.target_name.trim());
|
||||
if (existingRelationIndex !== -1) {
|
||||
appState.dispatch({
|
||||
|
|
@ -183,6 +185,7 @@ export namespace Tools {
|
|||
character_name: Type.String({ description: "The character's full name to add/edit the relation for" }),
|
||||
target_name: Type.String({ description: "The target character's full name" }),
|
||||
relation: Type.String({ description: 'The relationship type (e.g., friend, enemy, sibling)' }),
|
||||
world: Type.Optional(Type.Boolean({ description: "If `true`, entry would be visible to all stories under that world" })),
|
||||
}),
|
||||
}),
|
||||
'get_location': tool({
|
||||
|
|
@ -244,7 +247,7 @@ export namespace Tools {
|
|||
appState.dispatch({
|
||||
type: 'ADD_LOCATION',
|
||||
worldId: appState.currentWorld.id,
|
||||
storyId: appState.currentStory.id,
|
||||
storyId: args.world ? null : appState.currentStory.id,
|
||||
location: {
|
||||
id: crypto.randomUUID(),
|
||||
name: args.name.trim(),
|
||||
|
|
@ -265,6 +268,7 @@ export namespace Tools {
|
|||
scale: Type.Optional(Type.Enum(VALID_SCALES, {
|
||||
description: `Location scale (enum): ${VALID_SCALES.join(', ')}`,
|
||||
})),
|
||||
world: Type.Optional(Type.Boolean({ description: "If `true`, entry would be visible to all stories under that world" })),
|
||||
}),
|
||||
}),
|
||||
'set_lore_entry': tool({
|
||||
|
|
@ -289,7 +293,7 @@ export namespace Tools {
|
|||
appState.dispatch({
|
||||
type: 'ADD_LORE_ENTRY',
|
||||
worldId: appState.currentWorld.id,
|
||||
storyId: appState.currentStory.id,
|
||||
storyId: args.world ? null : appState.currentStory.id,
|
||||
entry: {
|
||||
id: crypto.randomUUID(),
|
||||
title: args.title.trim(),
|
||||
|
|
@ -304,6 +308,7 @@ export namespace Tools {
|
|||
parameters: Type.Object({
|
||||
title: Type.String({ description: "The lore entry's title" }),
|
||||
text: Type.String({ description: 'The lore entry content.' }),
|
||||
world: Type.Optional(Type.Boolean({ description: "If `true`, entry would be visible to all stories under that world" })),
|
||||
}),
|
||||
}),
|
||||
'edit_text': tool({
|
||||
|
|
@ -351,15 +356,14 @@ export namespace Tools {
|
|||
}
|
||||
}
|
||||
if (!hasUserMessageSinceLastAppend) {
|
||||
return `Error: You cannot add new text to the story until user approves your last edit. Stop right there.`;
|
||||
return `Error: You're not allowed to add more text now. Do not retry nor continue.`;
|
||||
}
|
||||
}
|
||||
|
||||
const numLines = args.new_text.split('\n').filter(l => l.trim()).length;
|
||||
const lines = args.new_text.split(/\n+/ig).filter(l => l.trim());
|
||||
const numLines = lines.length;
|
||||
|
||||
let cropped = false;
|
||||
if (!isScratchpad && numLines > LINES_LIMIT) {
|
||||
const lines = args.new_text.split('\n');
|
||||
let kept = 0;
|
||||
const croppedLines: string[] = [];
|
||||
for (const line of lines) {
|
||||
|
|
@ -373,13 +377,13 @@ export namespace Tools {
|
|||
cropped = true;
|
||||
}
|
||||
|
||||
dispatchEdit(currentText + '\n' + args.new_text);
|
||||
dispatchEdit(currentText.trim() + '\n' + args.new_text.trim());
|
||||
appState.dispatch({ type: 'SET_CURRENT_TAB', tab });
|
||||
let message = cropped
|
||||
? `Added text:\n${args.new_text.split('\n').filter(l => l.trim()).map(l => '> ' + l).join('\n')}\n\nNote: The rest was cropped due to ${LINES_LIMIT} lines limit!`
|
||||
: `Text appended to ${target} successfully.`;
|
||||
|
||||
message += `\nNote: you can't continue nor add more text until user's approval. Stop any attempts and wait.`
|
||||
message += `\nTool note: you're not allowed to add more text now. Do not retry nor continue.`
|
||||
|
||||
return message;
|
||||
}
|
||||
|
|
@ -504,6 +508,31 @@ export namespace Tools {
|
|||
prompt: Type.String({ description: 'The image generation prompt' }),
|
||||
}),
|
||||
}),
|
||||
'generate_name': tool({
|
||||
handler: async (args) => {
|
||||
const gen = getClassicRaceNamePatternSet(args.family);
|
||||
const kind = gen[args.kind];
|
||||
if (!kind) {
|
||||
return `Error: unknown kind: '${args.kind}'`;
|
||||
}
|
||||
const subGen = getNameGeneratorForPatternSet(`${args.family}_${args.kind}`, gen[args.kind]);
|
||||
const names = subGen.generate(args.amount ?? 1);
|
||||
|
||||
return names;
|
||||
},
|
||||
description: 'Generate a name. Use this tool when you need a new name, instead of inventing the name yourself.',
|
||||
parameters: Type.Object({
|
||||
family: Type.Enum(getSupportedClassicRaceNamePatternSets(), { description: 'Theme or race to which name belongs to' }),
|
||||
kind: Type.Enum((() => {
|
||||
type NameGenField = keyof Omit<NameGeneratorPatternSet, 'name'>;
|
||||
const gen = getClassicRaceNamePatternSet('elf');
|
||||
const keys = Object.keys(gen);
|
||||
|
||||
return keys.filter(k => k != 'name') as NameGenField[];
|
||||
})(), { description: 'What kind of name to generate' }),
|
||||
amount: Type.Optional(Type.Number({ description: "Specify the amount of names to generate. You can generate multiple names and select the most fitting. Default: 1" })),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
export type ToolName = keyof typeof TOOLS;
|
||||
|
|
|
|||
Loading…
Reference in New Issue