codacus commited on
Commit
70f88f9
·
1 Parent(s): 79ce87e

feat: Experimental Prompt Library Added

Browse files
app/commit.json CHANGED
@@ -1 +1 @@
1
- { "commit": "a71cfba660f04a8440960ab772670b192e2d066f" }
 
1
+ { "commit": "79ce87ee5d729185bad73a37ebf77964f1488f59" }
app/components/chat/Chat.client.tsx CHANGED
@@ -93,7 +93,7 @@ export const ChatImpl = memo(
93
  const [uploadedFiles, setUploadedFiles] = useState<File[]>([]); // Move here
94
  const [imageDataList, setImageDataList] = useState<string[]>([]); // Move here
95
  const files = useStore(workbenchStore.files);
96
- const { activeProviders } = useSettings();
97
 
98
  const [model, setModel] = useState(() => {
99
  const savedModel = Cookies.get('selectedModel');
@@ -115,6 +115,7 @@ export const ChatImpl = memo(
115
  body: {
116
  apiKeys,
117
  files,
 
118
  },
119
  onError: (error) => {
120
  logger.error('Request failed\n\n', error);
 
93
  const [uploadedFiles, setUploadedFiles] = useState<File[]>([]); // Move here
94
  const [imageDataList, setImageDataList] = useState<string[]>([]); // Move here
95
  const files = useStore(workbenchStore.files);
96
+ const { activeProviders, promptId } = useSettings();
97
 
98
  const [model, setModel] = useState(() => {
99
  const savedModel = Cookies.get('selectedModel');
 
115
  body: {
116
  apiKeys,
117
  files,
118
+ promptId,
119
  },
120
  onError: (error) => {
121
  logger.error('Request failed\n\n', error);
app/components/settings/features/FeaturesTab.tsx CHANGED
@@ -1,18 +1,20 @@
1
  import React from 'react';
2
  import { Switch } from '~/components/ui/Switch';
 
3
  import { useSettings } from '~/lib/hooks/useSettings';
4
 
5
  export default function FeaturesTab() {
6
- const { debug, enableDebugMode, isLocalModel, enableLocalModels, eventLogs, enableEventLogs } = useSettings();
 
7
  return (
8
  <div className="p-4 bg-bolt-elements-bg-depth-2 border border-bolt-elements-borderColor rounded-lg mb-4">
9
  <div className="mb-6">
10
  <h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">Optional Features</h3>
11
- <div className="flex items-center justify-between mb-2">
12
  <span className="text-bolt-elements-textPrimary">Debug Info</span>
13
  <Switch className="ml-auto" checked={debug} onCheckedChange={enableDebugMode} />
14
  </div>
15
- <div className="flex items-center justify-between mb-2">
16
  <span className="text-bolt-elements-textPrimary">Event Logs</span>
17
  <Switch className="ml-auto" checked={eventLogs} onCheckedChange={enableEventLogs} />
18
  </div>
@@ -23,10 +25,27 @@ export default function FeaturesTab() {
23
  <p className="text-sm text-bolt-elements-textSecondary mb-4">
24
  Disclaimer: Experimental features may be unstable and are subject to change.
25
  </p>
26
- <div className="flex items-center justify-between mb-2">
27
  <span className="text-bolt-elements-textPrimary">Enable Local Models</span>
28
  <Switch className="ml-auto" checked={isLocalModel} onCheckedChange={enableLocalModels} />
29
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  </div>
31
  </div>
32
  );
 
1
  import React from 'react';
2
  import { Switch } from '~/components/ui/Switch';
3
+ import { PromptLibrary } from '~/lib/common/prompt-library';
4
  import { useSettings } from '~/lib/hooks/useSettings';
5
 
6
  export default function FeaturesTab() {
7
+ const { debug, enableDebugMode, isLocalModel, enableLocalModels, eventLogs, enableEventLogs, promptId, setPromptId } =
8
+ useSettings();
9
  return (
10
  <div className="p-4 bg-bolt-elements-bg-depth-2 border border-bolt-elements-borderColor rounded-lg mb-4">
11
  <div className="mb-6">
12
  <h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">Optional Features</h3>
13
+ <div className="flex items-center justify-between mb-6">
14
  <span className="text-bolt-elements-textPrimary">Debug Info</span>
15
  <Switch className="ml-auto" checked={debug} onCheckedChange={enableDebugMode} />
16
  </div>
17
+ <div className="flex items-center justify-between mb-6">
18
  <span className="text-bolt-elements-textPrimary">Event Logs</span>
19
  <Switch className="ml-auto" checked={eventLogs} onCheckedChange={enableEventLogs} />
20
  </div>
 
25
  <p className="text-sm text-bolt-elements-textSecondary mb-4">
26
  Disclaimer: Experimental features may be unstable and are subject to change.
27
  </p>
28
+ <div className="flex items-center justify-between pt-4 mb-4">
29
  <span className="text-bolt-elements-textPrimary">Enable Local Models</span>
30
  <Switch className="ml-auto" checked={isLocalModel} onCheckedChange={enableLocalModels} />
31
  </div>
32
+ <div className="flex items-start justify-between pt-4 mb-2 gap-2">
33
+ <div className="flex-1 max-w-[200px]">
34
+ <span className="text-bolt-elements-textPrimary">Prompt Library</span>
35
+ <p className="text-sm text-bolt-elements-textSecondary mb-4">
36
+ Choose a prompt from the library to use as the system prompt.
37
+ </p>
38
+ </div>
39
+ <select
40
+ value={promptId}
41
+ onChange={(e) => setPromptId(e.target.value)}
42
+ 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]"
43
+ >
44
+ {PromptLibrary.getList().map((x) => (
45
+ <option value={x.id}>{x.label}</option>
46
+ ))}
47
+ </select>
48
+ </div>
49
  </div>
50
  </div>
51
  );
app/lib/.server/llm/stream-text.ts CHANGED
@@ -1,10 +1,20 @@
1
  import { convertToCoreMessages, streamText as _streamText } from 'ai';
2
  import { getModel } from '~/lib/.server/llm/model';
3
  import { MAX_TOKENS } from './constants';
4
- import { getSystemPrompt } from './prompts';
5
- import { DEFAULT_MODEL, DEFAULT_PROVIDER, getModelList, MODEL_REGEX, PROVIDER_REGEX } from '~/utils/constants';
 
 
 
 
 
 
 
 
6
  import ignore from 'ignore';
7
  import type { IProviderSetting } from '~/types/model';
 
 
8
 
9
  interface ToolResult<Name extends string, Args, Result> {
10
  toolCallId: string;
@@ -139,8 +149,9 @@ export async function streamText(props: {
139
  apiKeys?: Record<string, string>;
140
  files?: FileMap;
141
  providerSettings?: Record<string, IProviderSetting>;
 
142
  }) {
143
- const { messages, env, options, apiKeys, files, providerSettings } = props;
144
  let currentModel = DEFAULT_MODEL;
145
  let currentProvider = DEFAULT_PROVIDER.name;
146
  const MODEL_LIST = await getModelList(apiKeys || {}, providerSettings);
@@ -170,11 +181,17 @@ export async function streamText(props: {
170
 
171
  const dynamicMaxTokens = modelDetails && modelDetails.maxTokenAllowed ? modelDetails.maxTokenAllowed : MAX_TOKENS;
172
 
173
- let systemPrompt = getSystemPrompt();
 
 
 
 
 
174
  let codeContext = '';
175
 
176
  if (files) {
177
  codeContext = createFilesContext(files);
 
178
  systemPrompt = `${systemPrompt}\n\n ${codeContext}`;
179
  }
180
 
 
1
  import { convertToCoreMessages, streamText as _streamText } from 'ai';
2
  import { getModel } from '~/lib/.server/llm/model';
3
  import { MAX_TOKENS } from './constants';
4
+ import { getSystemPrompt } from '~/lib/common/prompts/prompts';
5
+ import {
6
+ DEFAULT_MODEL,
7
+ DEFAULT_PROVIDER,
8
+ getModelList,
9
+ MODEL_REGEX,
10
+ MODIFICATIONS_TAG_NAME,
11
+ PROVIDER_REGEX,
12
+ WORK_DIR,
13
+ } from '~/utils/constants';
14
  import ignore from 'ignore';
15
  import type { IProviderSetting } from '~/types/model';
16
+ import { PromptLibrary } from '~/lib/common/prompt-library';
17
+ import { allowedHTMLElements } from '~/utils/markdown';
18
 
19
  interface ToolResult<Name extends string, Args, Result> {
20
  toolCallId: string;
 
149
  apiKeys?: Record<string, string>;
150
  files?: FileMap;
151
  providerSettings?: Record<string, IProviderSetting>;
152
+ promptId?: string;
153
  }) {
154
+ const { messages, env, options, apiKeys, files, providerSettings, promptId } = props;
155
  let currentModel = DEFAULT_MODEL;
156
  let currentProvider = DEFAULT_PROVIDER.name;
157
  const MODEL_LIST = await getModelList(apiKeys || {}, providerSettings);
 
181
 
182
  const dynamicMaxTokens = modelDetails && modelDetails.maxTokenAllowed ? modelDetails.maxTokenAllowed : MAX_TOKENS;
183
 
184
+ let systemPrompt =
185
+ PromptLibrary.getPropmtFromLibrary(promptId || 'default', {
186
+ cwd: WORK_DIR,
187
+ allowedHtmlElements: allowedHTMLElements,
188
+ modificationTagName: MODIFICATIONS_TAG_NAME,
189
+ }) ?? getSystemPrompt();
190
  let codeContext = '';
191
 
192
  if (files) {
193
  codeContext = createFilesContext(files);
194
+ codeContext = '';
195
  systemPrompt = `${systemPrompt}\n\n ${codeContext}`;
196
  }
197
 
app/lib/common/prompt-library.ts ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getSystemPrompt } from './prompts/prompts';
2
+ import optimized from './prompts/optimized';
3
+
4
+ export interface PromptOptions {
5
+ cwd: string;
6
+ allowedHtmlElements: string[];
7
+ modificationTagName: string;
8
+ }
9
+
10
+ export class PromptLibrary {
11
+ static library: Record<
12
+ string,
13
+ {
14
+ label: string;
15
+ description: string;
16
+ get: (options: PromptOptions) => string;
17
+ }
18
+ > = {
19
+ default: {
20
+ label: 'Default Prompt',
21
+ description: 'This is the battle tested default system Prompt',
22
+ get: (options) => getSystemPrompt(options.cwd),
23
+ },
24
+ optimized: {
25
+ label: 'Optimized Prompt (experimental)',
26
+ description: 'an Experimental version of the prompt for lower token usage',
27
+ get: (options) => optimized(options),
28
+ },
29
+ };
30
+ static getList() {
31
+ return Object.entries(this.library).map(([key, value]) => {
32
+ const { label, description } = value;
33
+ return {
34
+ id: key,
35
+ label,
36
+ description,
37
+ };
38
+ });
39
+ }
40
+ static getPropmtFromLibrary(promptId: string, options: PromptOptions) {
41
+ const prompt = this.library[promptId];
42
+
43
+ if (!prompt) {
44
+ throw 'Prompt Now Found';
45
+ }
46
+
47
+ return this.library[promptId]?.get(options);
48
+ }
49
+ }
app/lib/common/prompts/optimized.ts ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { PromptOptions } from '~/lib/common/prompt-library';
2
+
3
+ export default (options: PromptOptions) => {
4
+ const { cwd, allowedHtmlElements, modificationTagName } = options;
5
+ return `
6
+ You are Bolt, an expert AI assistant and senior software developer.
7
+ You have access to a shell and access to write files through the use of artifacts.
8
+ Your artifacts will be parsed by automated parser to perform actions on your behalf and will not be visible to the user
9
+
10
+ <system_constraints>
11
+ - Operating in WebContainer, an in-browser Node.js runtime
12
+ - Limited Python support: standard library only, no pip
13
+ - No C/C++ compiler, native binaries, or Git
14
+ - Prefer Node.js scripts over shell scripts
15
+ - Use Vite for web servers
16
+ - Databases: prefer libsql, sqlite, or non-native solutions
17
+ - When for react dont forget to write vite config and index.html to the project
18
+
19
+ 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
20
+ </system_constraints>
21
+
22
+ <code_formatting_info>
23
+ Use 2 spaces for indentation
24
+ </code_formatting_info>
25
+
26
+ <message_formatting_info>
27
+ Available HTML elements: ${allowedHtmlElements.join(', ')}
28
+ </message_formatting_info>
29
+
30
+ <diff_spec>
31
+ File modifications in \`<${modificationTagName}>\` section:
32
+ - \`<diff path="/path/to/file">\`: GNU unified diff format
33
+ - \`<file path="/path/to/file">\`: Full new content
34
+ </diff_spec>
35
+
36
+ <chain_of_thought_instructions>
37
+ do not mention the phrase "chain of thought"
38
+ Before solutions, briefly outline implementation steps (2-4 lines max):
39
+ - List concrete steps
40
+ - Identify key components
41
+ - Note potential challenges
42
+ - Do not write the actual code just the plan and structure if needed
43
+ - Once completed planning start writing the artifacts
44
+ </chain_of_thought_instructions>
45
+
46
+ <artifact_info>
47
+ Create a single, comprehensive artifact for each project:
48
+ - Use \`<boltArtifact>\` tags with \`title\` and \`id\` attributes
49
+ - Use \`<boltAction>\` tags with \`type\` attribute:
50
+ - shell: Run commands
51
+ - file: Write/update files (use \`filePath\` attribute)
52
+ - start: Start dev server (only when necessary)
53
+ - Order actions logically
54
+ - Install dependencies first
55
+ - Provide full, updated content for all files
56
+ - Use coding best practices: modular, clean, readable code
57
+ </artifact_info>
58
+
59
+ Key points:
60
+ - Always use artifacts for file contents and commands
61
+ - Use markdown, avoid HTML tags except in artifacts
62
+ - Be concise, explain only when asked
63
+ - Think first, then provide comprehensive artifact
64
+ - Never use the word "artifact" in responses
65
+ - Current working directory: \`${cwd}\`
66
+
67
+ Examples:
68
+
69
+ <examples>
70
+ <example>
71
+ <user_query>Create a JavaScript factorial function</user_query>
72
+ <assistant_response>
73
+ Certainly, I'll create a JavaScript factorial function for you.
74
+
75
+ <boltArtifact id="factorial-function" title="JavaScript Factorial Function">
76
+ <boltAction type="file" filePath="factorial.js">
77
+ function factorial(n) {
78
+ return n <= 1 ? 1 : n * factorial(n - 1);
79
+ }
80
+ console.log(factorial(5));
81
+ </boltAction>
82
+ <boltAction type="shell">
83
+ node factorial.js
84
+ </boltAction>
85
+ </boltArtifact>
86
+
87
+ This creates a factorial function and tests it with the value 5.
88
+ </assistant_response>
89
+ </example>
90
+
91
+ <example>
92
+ <user_query>Set up a basic React project</user_query>
93
+ <assistant_response>
94
+ Sure, I'll set up a basic React project for you.
95
+
96
+ <boltArtifact id="react-setup" title="Basic React Project Setup">
97
+ <boltAction type="file" filePath="package.json">
98
+ {
99
+ "name": "react-project",
100
+ "version": "1.0.0",
101
+ "scripts": {
102
+ "dev": "vite"
103
+ },
104
+ "dependencies": {
105
+ "react": "^18.2.0",
106
+ "react-dom": "^18.2.0"
107
+ },
108
+ "devDependencies": {
109
+ "vite": "^4.3.9"
110
+ }
111
+ }
112
+ </boltAction>
113
+ <boltAction type="shell">
114
+ npm install
115
+ </boltAction>
116
+ <boltAction type="file" filePath="src/App.jsx">
117
+ import React from 'react';
118
+ function App() {
119
+ return <h1>Hello, React!</h1>;
120
+ }
121
+ export default App;
122
+ </boltAction>
123
+ <boltAction type="start">
124
+ npm run dev
125
+ </boltAction>
126
+ </boltArtifact>
127
+
128
+ This sets up a basic React project with Vite as the build tool.
129
+ </assistant_response>
130
+ </example>
131
+ </examples>
132
+
133
+ Always use artifacts for file contents and commands, following the format shown in these examples.
134
+ `;
135
+ };
app/lib/{.server/llm → common/prompts}/prompts.ts RENAMED
File without changes
app/lib/hooks/useSettings.tsx CHANGED
@@ -4,6 +4,7 @@ import {
4
  isEventLogsEnabled,
5
  isLocalModelsEnabled,
6
  LOCAL_PROVIDERS,
 
7
  providersStore,
8
  } from '~/lib/stores/settings';
9
  import { useCallback, useEffect, useState } from 'react';
@@ -15,6 +16,7 @@ export function useSettings() {
15
  const providers = useStore(providersStore);
16
  const debug = useStore(isDebugMode);
17
  const eventLogs = useStore(isEventLogsEnabled);
 
18
  const isLocalModel = useStore(isLocalModelsEnabled);
19
  const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
20
 
@@ -60,6 +62,12 @@ export function useSettings() {
60
  if (savedLocalModels) {
61
  isLocalModelsEnabled.set(savedLocalModels === 'true');
62
  }
 
 
 
 
 
 
63
  }, []);
64
 
65
  // writing values to cookies on change
@@ -111,6 +119,11 @@ export function useSettings() {
111
  Cookies.set('isLocalModelsEnabled', String(enabled));
112
  }, []);
113
 
 
 
 
 
 
114
  return {
115
  providers,
116
  activeProviders,
@@ -121,5 +134,7 @@ export function useSettings() {
121
  enableEventLogs,
122
  isLocalModel,
123
  enableLocalModels,
 
 
124
  };
125
  }
 
4
  isEventLogsEnabled,
5
  isLocalModelsEnabled,
6
  LOCAL_PROVIDERS,
7
+ promptStore,
8
  providersStore,
9
  } from '~/lib/stores/settings';
10
  import { useCallback, useEffect, useState } from 'react';
 
16
  const providers = useStore(providersStore);
17
  const debug = useStore(isDebugMode);
18
  const eventLogs = useStore(isEventLogsEnabled);
19
+ const promptId = useStore(promptStore);
20
  const isLocalModel = useStore(isLocalModelsEnabled);
21
  const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
22
 
 
62
  if (savedLocalModels) {
63
  isLocalModelsEnabled.set(savedLocalModels === 'true');
64
  }
65
+
66
+ const promptId = Cookies.get('promptId');
67
+
68
+ if (promptId) {
69
+ promptStore.set(promptId);
70
+ }
71
  }, []);
72
 
73
  // writing values to cookies on change
 
119
  Cookies.set('isLocalModelsEnabled', String(enabled));
120
  }, []);
121
 
122
+ const setPromptId = useCallback((promptId: string) => {
123
+ promptStore.set(promptId);
124
+ Cookies.set('promptId', promptId);
125
+ }, []);
126
+
127
  return {
128
  providers,
129
  activeProviders,
 
134
  enableEventLogs,
135
  isLocalModel,
136
  enableLocalModels,
137
+ promptId,
138
+ setPromptId,
139
  };
140
  }
app/lib/stores/settings.ts CHANGED
@@ -46,3 +46,5 @@ export const isDebugMode = atom(false);
46
  export const isEventLogsEnabled = atom(false);
47
 
48
  export const isLocalModelsEnabled = atom(true);
 
 
 
46
  export const isEventLogsEnabled = atom(false);
47
 
48
  export const isLocalModelsEnabled = atom(true);
49
+
50
+ export const promptStore = atom<string>('default');
app/lib/stores/workbench.ts CHANGED
@@ -297,7 +297,6 @@ export class WorkbenchStore {
297
 
298
  const action = artifact.runner.actions.get()[data.actionId];
299
 
300
-
301
  if (!action || action.executed) {
302
  return;
303
  }
 
297
 
298
  const action = artifact.runner.actions.get()[data.actionId];
299
 
 
300
  if (!action || action.executed) {
301
  return;
302
  }
app/routes/api.chat.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { type ActionFunctionArgs } from '@remix-run/cloudflare';
2
  import { MAX_RESPONSE_SEGMENTS, MAX_TOKENS } from '~/lib/.server/llm/constants';
3
- import { CONTINUE_PROMPT } from '~/lib/.server/llm/prompts';
4
  import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text';
5
  import SwitchableStream from '~/lib/.server/llm/switchable-stream';
6
  import type { IProviderSetting } from '~/types/model';
@@ -30,9 +30,10 @@ function parseCookies(cookieHeader: string) {
30
  }
31
 
32
  async function chatAction({ context, request }: ActionFunctionArgs) {
33
- const { messages, files } = await request.json<{
34
  messages: Messages;
35
  files: any;
 
36
  }>();
37
 
38
  const cookieHeader = request.headers.get('Cookie');
@@ -71,6 +72,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
71
  apiKeys,
72
  files,
73
  providerSettings,
 
74
  });
75
 
76
  return stream.switchSource(result.toAIStream());
@@ -84,6 +86,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
84
  apiKeys,
85
  files,
86
  providerSettings,
 
87
  });
88
 
89
  stream.switchSource(result.toAIStream());
 
1
  import { type ActionFunctionArgs } from '@remix-run/cloudflare';
2
  import { MAX_RESPONSE_SEGMENTS, MAX_TOKENS } from '~/lib/.server/llm/constants';
3
+ import { CONTINUE_PROMPT } from '~/lib/common/prompts/prompts';
4
  import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text';
5
  import SwitchableStream from '~/lib/.server/llm/switchable-stream';
6
  import type { IProviderSetting } from '~/types/model';
 
30
  }
31
 
32
  async function chatAction({ context, request }: ActionFunctionArgs) {
33
+ const { messages, files, promptId } = await request.json<{
34
  messages: Messages;
35
  files: any;
36
+ promptId?: string;
37
  }>();
38
 
39
  const cookieHeader = request.headers.get('Cookie');
 
72
  apiKeys,
73
  files,
74
  providerSettings,
75
+ promptId,
76
  });
77
 
78
  return stream.switchSource(result.toAIStream());
 
86
  apiKeys,
87
  files,
88
  providerSettings,
89
+ promptId,
90
  });
91
 
92
  stream.switchSource(result.toAIStream());