feat: Experimental Prompt Library Added

This commit is contained in:
Anirban Kar 2024-12-15 16:47:16 +05:30
parent 79ce87ee5d
commit 70f88f981a
11 changed files with 253 additions and 13 deletions

View File

@ -1 +1 @@
{ "commit": "a71cfba660f04a8440960ab772670b192e2d066f" }
{ "commit": "79ce87ee5d729185bad73a37ebf77964f1488f59" }

View File

@ -93,7 +93,7 @@ export const ChatImpl = memo(
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]); // Move here
const [imageDataList, setImageDataList] = useState<string[]>([]); // Move here
const files = useStore(workbenchStore.files);
const { activeProviders } = useSettings();
const { activeProviders, promptId } = useSettings();
const [model, setModel] = useState(() => {
const savedModel = Cookies.get('selectedModel');
@ -115,6 +115,7 @@ export const ChatImpl = memo(
body: {
apiKeys,
files,
promptId,
},
onError: (error) => {
logger.error('Request failed\n\n', error);

View File

@ -1,18 +1,20 @@
import React from 'react';
import { Switch } from '~/components/ui/Switch';
import { PromptLibrary } from '~/lib/common/prompt-library';
import { useSettings } from '~/lib/hooks/useSettings';
export default function FeaturesTab() {
const { debug, enableDebugMode, isLocalModel, enableLocalModels, eventLogs, enableEventLogs } = useSettings();
const { debug, enableDebugMode, isLocalModel, enableLocalModels, eventLogs, enableEventLogs, promptId, setPromptId } =
useSettings();
return (
<div className="p-4 bg-bolt-elements-bg-depth-2 border border-bolt-elements-borderColor rounded-lg mb-4">
<div className="mb-6">
<h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">Optional Features</h3>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center justify-between mb-6">
<span className="text-bolt-elements-textPrimary">Debug Info</span>
<Switch className="ml-auto" checked={debug} onCheckedChange={enableDebugMode} />
</div>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center justify-between mb-6">
<span className="text-bolt-elements-textPrimary">Event Logs</span>
<Switch className="ml-auto" checked={eventLogs} onCheckedChange={enableEventLogs} />
</div>
@ -23,10 +25,27 @@ export default function FeaturesTab() {
<p className="text-sm text-bolt-elements-textSecondary mb-4">
Disclaimer: Experimental features may be unstable and are subject to change.
</p>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center justify-between pt-4 mb-4">
<span className="text-bolt-elements-textPrimary">Enable Local Models</span>
<Switch className="ml-auto" checked={isLocalModel} onCheckedChange={enableLocalModels} />
</div>
<div className="flex items-start justify-between pt-4 mb-2 gap-2">
<div className="flex-1 max-w-[200px]">
<span className="text-bolt-elements-textPrimary">Prompt Library</span>
<p className="text-sm text-bolt-elements-textSecondary mb-4">
Choose a prompt from the library to use as the system prompt.
</p>
</div>
<select
value={promptId}
onChange={(e) => setPromptId(e.target.value)}
className="flex-1 p-2 ml-auto rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all text-sm min-w-[100px]"
>
{PromptLibrary.getList().map((x) => (
<option value={x.id}>{x.label}</option>
))}
</select>
</div>
</div>
</div>
);

View File

@ -1,10 +1,20 @@
import { convertToCoreMessages, streamText as _streamText } from 'ai';
import { getModel } from '~/lib/.server/llm/model';
import { MAX_TOKENS } from './constants';
import { getSystemPrompt } from './prompts';
import { DEFAULT_MODEL, DEFAULT_PROVIDER, getModelList, MODEL_REGEX, PROVIDER_REGEX } from '~/utils/constants';
import { getSystemPrompt } from '~/lib/common/prompts/prompts';
import {
DEFAULT_MODEL,
DEFAULT_PROVIDER,
getModelList,
MODEL_REGEX,
MODIFICATIONS_TAG_NAME,
PROVIDER_REGEX,
WORK_DIR,
} from '~/utils/constants';
import ignore from 'ignore';
import type { IProviderSetting } from '~/types/model';
import { PromptLibrary } from '~/lib/common/prompt-library';
import { allowedHTMLElements } from '~/utils/markdown';
interface ToolResult<Name extends string, Args, Result> {
toolCallId: string;
@ -139,8 +149,9 @@ export async function streamText(props: {
apiKeys?: Record<string, string>;
files?: FileMap;
providerSettings?: Record<string, IProviderSetting>;
promptId?: string;
}) {
const { messages, env, options, apiKeys, files, providerSettings } = props;
const { messages, env, options, apiKeys, files, providerSettings, promptId } = props;
let currentModel = DEFAULT_MODEL;
let currentProvider = DEFAULT_PROVIDER.name;
const MODEL_LIST = await getModelList(apiKeys || {}, providerSettings);
@ -170,11 +181,17 @@ export async function streamText(props: {
const dynamicMaxTokens = modelDetails && modelDetails.maxTokenAllowed ? modelDetails.maxTokenAllowed : MAX_TOKENS;
let systemPrompt = getSystemPrompt();
let systemPrompt =
PromptLibrary.getPropmtFromLibrary(promptId || 'default', {
cwd: WORK_DIR,
allowedHtmlElements: allowedHTMLElements,
modificationTagName: MODIFICATIONS_TAG_NAME,
}) ?? getSystemPrompt();
let codeContext = '';
if (files) {
codeContext = createFilesContext(files);
codeContext = '';
systemPrompt = `${systemPrompt}\n\n ${codeContext}`;
}

View File

@ -0,0 +1,49 @@
import { getSystemPrompt } from './prompts/prompts';
import optimized from './prompts/optimized';
export interface PromptOptions {
cwd: string;
allowedHtmlElements: string[];
modificationTagName: string;
}
export class PromptLibrary {
static library: Record<
string,
{
label: string;
description: string;
get: (options: PromptOptions) => string;
}
> = {
default: {
label: 'Default Prompt',
description: 'This is the battle tested default system Prompt',
get: (options) => getSystemPrompt(options.cwd),
},
optimized: {
label: 'Optimized Prompt (experimental)',
description: 'an Experimental version of the prompt for lower token usage',
get: (options) => optimized(options),
},
};
static getList() {
return Object.entries(this.library).map(([key, value]) => {
const { label, description } = value;
return {
id: key,
label,
description,
};
});
}
static getPropmtFromLibrary(promptId: string, options: PromptOptions) {
const prompt = this.library[promptId];
if (!prompt) {
throw 'Prompt Now Found';
}
return this.library[promptId]?.get(options);
}
}

View File

@ -0,0 +1,135 @@
import type { PromptOptions } from '~/lib/common/prompt-library';
export default (options: PromptOptions) => {
const { cwd, allowedHtmlElements, modificationTagName } = options;
return `
You are Bolt, an expert AI assistant and senior software developer.
You have access to a shell and access to write files through the use of artifacts.
Your artifacts will be parsed by automated parser to perform actions on your behalf and will not be visible to the user
<system_constraints>
- Operating in WebContainer, an in-browser Node.js runtime
- Limited Python support: standard library only, no pip
- No C/C++ compiler, native binaries, or Git
- Prefer Node.js scripts over shell scripts
- Use Vite for web servers
- Databases: prefer libsql, sqlite, or non-native solutions
- When for react dont forget to write vite config and index.html to the project
Available shell commands: cat, cp, ls, mkdir, mv, rm, rmdir, touch, hostname, ps, pwd, uptime, env, node, python3, code, jq, curl, head, sort, tail, clear, which, export, chmod, scho, kill, ln, xxd, alias, getconf, loadenv, wasm, xdg-open, command, exit, source
</system_constraints>
<code_formatting_info>
Use 2 spaces for indentation
</code_formatting_info>
<message_formatting_info>
Available HTML elements: ${allowedHtmlElements.join(', ')}
</message_formatting_info>
<diff_spec>
File modifications in \`<${modificationTagName}>\` section:
- \`<diff path="/path/to/file">\`: GNU unified diff format
- \`<file path="/path/to/file">\`: Full new content
</diff_spec>
<chain_of_thought_instructions>
do not mention the phrase "chain of thought"
Before solutions, briefly outline implementation steps (2-4 lines max):
- List concrete steps
- Identify key components
- Note potential challenges
- Do not write the actual code just the plan and structure if needed
- Once completed planning start writing the artifacts
</chain_of_thought_instructions>
<artifact_info>
Create a single, comprehensive artifact for each project:
- Use \`<boltArtifact>\` tags with \`title\` and \`id\` attributes
- Use \`<boltAction>\` tags with \`type\` attribute:
- shell: Run commands
- file: Write/update files (use \`filePath\` attribute)
- start: Start dev server (only when necessary)
- Order actions logically
- Install dependencies first
- Provide full, updated content for all files
- Use coding best practices: modular, clean, readable code
</artifact_info>
Key points:
- Always use artifacts for file contents and commands
- Use markdown, avoid HTML tags except in artifacts
- Be concise, explain only when asked
- Think first, then provide comprehensive artifact
- Never use the word "artifact" in responses
- Current working directory: \`${cwd}\`
Examples:
<examples>
<example>
<user_query>Create a JavaScript factorial function</user_query>
<assistant_response>
Certainly, I'll create a JavaScript factorial function for you.
<boltArtifact id="factorial-function" title="JavaScript Factorial Function">
<boltAction type="file" filePath="factorial.js">
function factorial(n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
console.log(factorial(5));
</boltAction>
<boltAction type="shell">
node factorial.js
</boltAction>
</boltArtifact>
This creates a factorial function and tests it with the value 5.
</assistant_response>
</example>
<example>
<user_query>Set up a basic React project</user_query>
<assistant_response>
Sure, I'll set up a basic React project for you.
<boltArtifact id="react-setup" title="Basic React Project Setup">
<boltAction type="file" filePath="package.json">
{
"name": "react-project",
"version": "1.0.0",
"scripts": {
"dev": "vite"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"vite": "^4.3.9"
}
}
</boltAction>
<boltAction type="shell">
npm install
</boltAction>
<boltAction type="file" filePath="src/App.jsx">
import React from 'react';
function App() {
return <h1>Hello, React!</h1>;
}
export default App;
</boltAction>
<boltAction type="start">
npm run dev
</boltAction>
</boltArtifact>
This sets up a basic React project with Vite as the build tool.
</assistant_response>
</example>
</examples>
Always use artifacts for file contents and commands, following the format shown in these examples.
`;
};

View File

@ -4,6 +4,7 @@ import {
isEventLogsEnabled,
isLocalModelsEnabled,
LOCAL_PROVIDERS,
promptStore,
providersStore,
} from '~/lib/stores/settings';
import { useCallback, useEffect, useState } from 'react';
@ -15,6 +16,7 @@ export function useSettings() {
const providers = useStore(providersStore);
const debug = useStore(isDebugMode);
const eventLogs = useStore(isEventLogsEnabled);
const promptId = useStore(promptStore);
const isLocalModel = useStore(isLocalModelsEnabled);
const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
@ -60,6 +62,12 @@ export function useSettings() {
if (savedLocalModels) {
isLocalModelsEnabled.set(savedLocalModels === 'true');
}
const promptId = Cookies.get('promptId');
if (promptId) {
promptStore.set(promptId);
}
}, []);
// writing values to cookies on change
@ -111,6 +119,11 @@ export function useSettings() {
Cookies.set('isLocalModelsEnabled', String(enabled));
}, []);
const setPromptId = useCallback((promptId: string) => {
promptStore.set(promptId);
Cookies.set('promptId', promptId);
}, []);
return {
providers,
activeProviders,
@ -121,5 +134,7 @@ export function useSettings() {
enableEventLogs,
isLocalModel,
enableLocalModels,
promptId,
setPromptId,
};
}

View File

@ -46,3 +46,5 @@ export const isDebugMode = atom(false);
export const isEventLogsEnabled = atom(false);
export const isLocalModelsEnabled = atom(true);
export const promptStore = atom<string>('default');

View File

@ -297,7 +297,6 @@ export class WorkbenchStore {
const action = artifact.runner.actions.get()[data.actionId];
if (!action || action.executed) {
return;
}

View File

@ -1,6 +1,6 @@
import { type ActionFunctionArgs } from '@remix-run/cloudflare';
import { MAX_RESPONSE_SEGMENTS, MAX_TOKENS } from '~/lib/.server/llm/constants';
import { CONTINUE_PROMPT } from '~/lib/.server/llm/prompts';
import { CONTINUE_PROMPT } from '~/lib/common/prompts/prompts';
import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text';
import SwitchableStream from '~/lib/.server/llm/switchable-stream';
import type { IProviderSetting } from '~/types/model';
@ -30,9 +30,10 @@ function parseCookies(cookieHeader: string) {
}
async function chatAction({ context, request }: ActionFunctionArgs) {
const { messages, files } = await request.json<{
const { messages, files, promptId } = await request.json<{
messages: Messages;
files: any;
promptId?: string;
}>();
const cookieHeader = request.headers.get('Cookie');
@ -71,6 +72,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
apiKeys,
files,
providerSettings,
promptId,
});
return stream.switchSource(result.toAIStream());
@ -84,6 +86,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
apiKeys,
files,
providerSettings,
promptId,
});
stream.switchSource(result.toAIStream());