feat: add user notes feature

This commit is contained in:
vgcman16 2025-06-05 20:37:16 -05:00
parent c47da2996b
commit a8c7520dd4
15 changed files with 171 additions and 17 deletions

View File

@ -38,6 +38,7 @@ import CloudProvidersTab from '~/components/@settings/tabs/providers/cloud/Cloud
import ServiceStatusTab from '~/components/@settings/tabs/providers/status/ServiceStatusTab';
import LocalProvidersTab from '~/components/@settings/tabs/providers/local/LocalProvidersTab';
import TaskManagerTab from '~/components/@settings/tabs/task-manager/TaskManagerTab';
import NotesTab from '~/components/@settings/tabs/notes/NotesTab';
interface ControlPanelProps {
open: boolean;
@ -78,6 +79,7 @@ const TAB_DESCRIPTIONS: Record<TabType, string> = {
connection: 'Check connection status and settings',
debug: 'Debug tools and system information',
'event-logs': 'View system events and logs',
notes: 'Persistent notes for the AI',
update: 'Check for updates and release notes',
'task-manager': 'Monitor system resources and processes',
'tab-management': 'Configure visible tabs and their order',
@ -329,6 +331,8 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
return <DebugTab />;
case 'event-logs':
return <EventLogsTab />;
case 'notes':
return <NotesTab />;
case 'update':
return <UpdateTab />;
case 'task-manager':

View File

@ -12,6 +12,7 @@ export const TAB_ICONS: Record<TabType, string> = {
connection: 'i-ph:wifi-high-fill',
debug: 'i-ph:bug-fill',
'event-logs': 'i-ph:list-bullets-fill',
notes: 'i-ph:note-pencil',
update: 'i-ph:arrow-clockwise-fill',
'task-manager': 'i-ph:chart-line-fill',
'tab-management': 'i-ph:squares-four-fill',
@ -29,6 +30,7 @@ export const TAB_LABELS: Record<TabType, string> = {
connection: 'Connection',
debug: 'Debug',
'event-logs': 'Event Logs',
notes: 'Notes',
update: 'Updates',
'task-manager': 'Task Manager',
'tab-management': 'Tab Management',
@ -46,6 +48,7 @@ export const TAB_DESCRIPTIONS: Record<TabType, string> = {
connection: 'Check connection status and settings',
debug: 'Debug tools and system information',
'event-logs': 'View system events and logs',
notes: 'Persistent notes for the AI',
update: 'Check for updates and release notes',
'task-manager': 'Monitor system resources and processes',
'tab-management': 'Configure visible tabs and their order',
@ -60,16 +63,17 @@ export const DEFAULT_TAB_CONFIG = [
{ id: 'connection', visible: true, window: 'user' as const, order: 4 },
{ id: 'notifications', visible: true, window: 'user' as const, order: 5 },
{ id: 'event-logs', visible: true, window: 'user' as const, order: 6 },
{ id: 'notes', visible: true, window: 'user' as const, order: 7 },
// User Window Tabs (In dropdown, initially hidden)
{ id: 'profile', visible: false, window: 'user' as const, order: 7 },
{ id: 'settings', visible: false, window: 'user' as const, order: 8 },
{ id: 'task-manager', visible: false, window: 'user' as const, order: 9 },
{ id: 'service-status', visible: false, window: 'user' as const, order: 10 },
{ id: 'profile', visible: false, window: 'user' as const, order: 8 },
{ id: 'settings', visible: false, window: 'user' as const, order: 9 },
{ id: 'task-manager', visible: false, window: 'user' as const, order: 10 },
{ id: 'service-status', visible: false, window: 'user' as const, order: 11 },
// User Window Tabs (Hidden, controlled by TaskManagerTab)
{ id: 'debug', visible: false, window: 'user' as const, order: 11 },
{ id: 'update', visible: false, window: 'user' as const, order: 12 },
{ id: 'debug', visible: false, window: 'user' as const, order: 12 },
{ id: 'update', visible: false, window: 'user' as const, order: 13 },
// Developer Window Tabs (All visible by default)
{ id: 'features', visible: true, window: 'developer' as const, order: 0 },
@ -79,10 +83,11 @@ export const DEFAULT_TAB_CONFIG = [
{ id: 'connection', visible: true, window: 'developer' as const, order: 4 },
{ id: 'notifications', visible: true, window: 'developer' as const, order: 5 },
{ id: 'event-logs', visible: true, window: 'developer' as const, order: 6 },
{ id: 'profile', visible: true, window: 'developer' as const, order: 7 },
{ id: 'settings', visible: true, window: 'developer' as const, order: 8 },
{ id: 'task-manager', visible: true, window: 'developer' as const, order: 9 },
{ id: 'service-status', visible: true, window: 'developer' as const, order: 10 },
{ id: 'debug', visible: true, window: 'developer' as const, order: 11 },
{ id: 'update', visible: true, window: 'developer' as const, order: 12 },
{ id: 'notes', visible: true, window: 'developer' as const, order: 7 },
{ id: 'profile', visible: true, window: 'developer' as const, order: 8 },
{ id: 'settings', visible: true, window: 'developer' as const, order: 9 },
{ id: 'task-manager', visible: true, window: 'developer' as const, order: 10 },
{ id: 'service-status', visible: true, window: 'developer' as const, order: 11 },
{ id: 'debug', visible: true, window: 'developer' as const, order: 12 },
{ id: 'update', visible: true, window: 'developer' as const, order: 13 },
];

View File

@ -14,6 +14,7 @@ export type TabType =
| 'connection'
| 'debug'
| 'event-logs'
| 'notes'
| 'update'
| 'task-manager'
| 'tab-management';

View File

@ -23,6 +23,7 @@ const TAB_ICONS: Record<TabType, string> = {
connection: 'i-ph:wifi-high-fill',
debug: 'i-ph:bug-fill',
'event-logs': 'i-ph:list-bullets-fill',
notes: 'i-ph:note-pencil',
update: 'i-ph:arrow-clockwise-fill',
'task-manager': 'i-ph:chart-line-fill',
'tab-management': 'i-ph:squares-four-fill',
@ -37,6 +38,7 @@ const DEFAULT_USER_TABS: TabType[] = [
'connection',
'notifications',
'event-logs',
'notes',
];
// Define which tabs can be added to user mode

View File

@ -0,0 +1,62 @@
import { useState } from 'react';
import { useStore } from '@nanostores/react';
import { notesStore, addNote, removeNote } from '~/lib/stores/notes';
import { Button } from '~/components/ui/Button';
import { classNames } from '~/utils/classNames';
export default function NotesTab() {
const notes = useStore(notesStore);
const [noteText, setNoteText] = useState('');
const handleAdd = () => {
const text = noteText.trim();
if (!text) return;
addNote(text);
setNoteText('');
};
return (
<div className="space-y-4">
<div>
<textarea
value={noteText}
onChange={(e) => setNoteText(e.target.value)}
className={classNames(
'w-full rounded-lg p-2',
'bg-bolt-elements-background-depth-1',
'border border-bolt-elements-borderColor',
'text-bolt-elements-textPrimary',
'focus:outline-none focus:ring-2 focus:ring-purple-500/30',
'min-h-[80px] resize-vertical'
)}
placeholder="Add a note for the AI"
/>
<Button className="mt-2" variant="secondary" onClick={handleAdd}>
Add Note
</Button>
</div>
<div className="space-y-2">
{notes.map((n) => (
<div
key={n.id}
className="flex items-start gap-2 p-2 rounded-lg bg-bolt-elements-background-depth-2"
>
<div className="flex-1 whitespace-pre-wrap text-sm text-bolt-elements-textPrimary">
{n.text}
</div>
<Button
size="sm"
variant="ghost"
onClick={() => removeNote(n.id)}
>
<div className="i-ph:trash w-4 h-4" />
</Button>
</div>
))}
{notes.length === 0 && (
<p className="text-sm text-bolt-elements-textSecondary">No notes added.</p>
)}
</div>
</div>
);
}

View File

@ -28,6 +28,7 @@ import { streamingState } from '~/lib/stores/streaming';
import { filesToArtifacts, uploadedFilesToArtifacts } from '~/utils/fileUtils';
import { escapeBoltTags } from '~/utils/projectCommands';
import { supabaseConnection } from '~/lib/stores/supabase';
import { notesStore } from '~/lib/stores/notes';
import { defaultDesignScheme, type DesignScheme } from '~/types/design-scheme';
import type { ElementInfo } from '~/components/workbench/Inspector';
@ -150,6 +151,7 @@ export const ChatImpl = memo(
const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
const [chatMode, setChatMode] = useState<'discuss' | 'build'>('build');
const [selectedElement, setSelectedElement] = useState<ElementInfo | null>(null);
const notes = useStore(notesStore);
const {
messages,
isLoading,
@ -172,6 +174,7 @@ export const ChatImpl = memo(
contextOptimization: contextOptimizationEnabled,
chatMode,
designScheme,
userNotes: notes.map((n) => n.text).join('\n'),
supabase: {
isConnected: supabaseConn.isConnected,
hasSelectedProject: !!selectedProject,

View File

@ -40,6 +40,7 @@ export async function streamText(props: {
messageSliceId?: number;
chatMode?: 'discuss' | 'build';
designScheme?: DesignScheme;
userNotes?: string;
}) {
const {
messages,
@ -54,6 +55,7 @@ export async function streamText(props: {
summary,
chatMode,
designScheme,
userNotes,
} = props;
let currentModel = DEFAULT_MODEL;
let currentProvider = DEFAULT_PROVIDER.name;
@ -124,12 +126,13 @@ export async function streamText(props: {
allowedHtmlElements: allowedHTMLElements,
modificationTagName: MODIFICATIONS_TAG_NAME,
designScheme,
userNotes,
supabase: {
isConnected: options?.supabaseConnection?.isConnected || false,
hasSelectedProject: options?.supabaseConnection?.hasSelectedProject || false,
credentials: options?.supabaseConnection?.credentials || undefined,
},
}) ?? getSystemPrompt();
}) ?? getSystemPrompt(WORK_DIR, undefined, designScheme, userNotes);
if (chatMode === 'build' && contextFiles && contextOptimization) {
const codeContext = createFilesContext(contextFiles, true);

View File

@ -8,6 +8,7 @@ export interface PromptOptions {
allowedHtmlElements: string[];
modificationTagName: string;
designScheme?: DesignScheme;
userNotes?: string;
supabase?: {
isConnected: boolean;
hasSelectedProject: boolean;
@ -30,12 +31,24 @@ export class PromptLibrary {
default: {
label: 'Default Prompt',
description: 'This is the battle tested default system Prompt',
get: (options) => getSystemPrompt(options.cwd, options.supabase, options.designScheme),
get: (options) =>
getSystemPrompt(
options.cwd,
options.supabase,
options.designScheme,
options.userNotes,
),
},
enhanced: {
label: 'Fine Tuned Prompt',
description: 'An fine tuned prompt for better results',
get: (options) => getFineTunedPrompt(options.cwd, options.supabase, options.designScheme),
get: (options) =>
getFineTunedPrompt(
options.cwd,
options.supabase,
options.designScheme,
options.userNotes,
),
},
optimized: {
label: 'Optimized Prompt (experimental)',

View File

@ -11,6 +11,7 @@ export const getFineTunedPrompt = (
credentials?: { anonKey?: string; supabaseUrl?: string };
},
designScheme?: DesignScheme,
userNotes?: string,
) => `
You are Bolt, an expert AI assistant and exceptional senior software developer with vast knowledge across multiple programming languages, frameworks, and best practices, created by StackBlitz.
@ -39,6 +40,8 @@ The year is 2025.
- Available shell commands: cat, chmod, cp, echo, hostname, kill, ln, ls, mkdir, mv, ps, pwd, rm, rmdir, xxd, alias, cd, clear, curl, env, false, getconf, head, sort, tail, touch, true, uptime, which, code, jq, loadenv, node, python, python3, wasm, xdg-open, command, exit, export, source
</system_constraints>
${userNotes ? `<user_notes>\n ${userNotes}\n</user_notes>` : ''}
<technology_preferences>
- Use Vite for web servers
- ALWAYS choose Node.js scripts over shell scripts

View File

@ -18,6 +18,8 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
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>
${options.userNotes ? `<user_notes>\n ${options.userNotes}\n</user_notes>` : ''}
<database_instructions>
The following instructions guide how you should handle database operations in projects.

View File

@ -11,6 +11,7 @@ export const getSystemPrompt = (
credentials?: { anonKey?: string; supabaseUrl?: string };
},
designScheme?: DesignScheme,
userNotes?: string,
) => `
You are Bolt, an expert AI assistant and exceptional senior software developer with vast knowledge across multiple programming languages, frameworks, and best practices.
@ -69,9 +70,11 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
- jq: Process JSON
Other Utilities:
- curl, head, sort, tail, clear, which, export, chmod, scho, hostname, kill, ln, xxd, alias, false, getconf, true, loadenv, wasm, xdg-open, command, exit, source
- curl, head, sort, tail, clear, which, export, chmod, scho, hostname, kill, ln, xxd, alias, false, getconf, true, loadenv, wasm, xdg-open, command, exit, source
</system_constraints>
${userNotes ? `<user_notes>\n ${userNotes}\n</user_notes>` : ''}
<database_instructions>
The following instructions guide how you should handle database operations in projects.

48
app/lib/stores/notes.ts Normal file
View File

@ -0,0 +1,48 @@
import { atom } from 'nanostores';
export interface Note {
id: string;
text: string;
createdAt: number;
}
const NOTES_KEY = 'bolt_notes';
function loadNotes(): Note[] {
if (typeof localStorage === 'undefined') {
return [];
}
try {
const json = localStorage.getItem(NOTES_KEY);
return json ? JSON.parse(json) : [];
} catch {
return [];
}
}
function saveNotes(notes: Note[]) {
if (typeof localStorage === 'undefined') return;
try {
localStorage.setItem(NOTES_KEY, JSON.stringify(notes));
} catch {}
}
export const notesStore = atom<Note[]>(loadNotes());
export function addNote(text: string) {
const notes = [...notesStore.get(), { id: crypto.randomUUID(), text, createdAt: Date.now() }];
notesStore.set(notes);
saveNotes(notes);
}
export function removeNote(id: string) {
const notes = notesStore.get().filter((n) => n.id !== id);
notesStore.set(notes);
saveNotes(notes);
}
export function updateNote(id: string, text: string) {
const notes = notesStore.get().map((n) => (n.id === id ? { ...n, text } : n));
notesStore.set(notes);
saveNotes(notes);
}

View File

@ -38,13 +38,14 @@ function parseCookies(cookieHeader: string): Record<string, string> {
}
async function chatAction({ context, request }: ActionFunctionArgs) {
const { messages, files, promptId, contextOptimization, supabase, chatMode, designScheme } = await request.json<{
const { messages, files, promptId, contextOptimization, supabase, chatMode, designScheme, userNotes } = await request.json<{
messages: Messages;
files: any;
promptId?: string;
contextOptimization: boolean;
chatMode: 'discuss' | 'build';
designScheme?: DesignScheme;
userNotes?: string;
supabase?: {
isConnected: boolean;
hasSelectedProject: boolean;
@ -253,6 +254,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
contextFiles: filteredFiles,
chatMode,
designScheme,
userNotes,
summary,
messageSliceId,
});
@ -294,6 +296,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
contextFiles: filteredFiles,
chatMode,
designScheme,
userNotes,
summary,
messageSliceId,
});

View File

@ -80,6 +80,7 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
env: context.cloudflare?.env as any,
apiKeys,
providerSettings,
userNotes: undefined,
options: {
system:
'You are a senior software principal architect, you should help the user analyse the user query and enrich it with the necessary context and constraints to make it more specific, actionable, and effective. You should also ensure that the prompt is self-contained and uses professional language. Your response should ONLY contain the enhanced prompt text. Do not include any explanations, metadata, or wrapper tags.',

View File

@ -66,6 +66,7 @@ async function llmCallAction({ context, request }: ActionFunctionArgs) {
content: `${message}`,
},
],
userNotes: undefined,
env: context.cloudflare?.env as any,
apiKeys,
providerSettings,