mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
feat: add user notes feature
This commit is contained in:
parent
c47da2996b
commit
a8c7520dd4
@ -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':
|
||||
|
@ -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 },
|
||||
];
|
||||
|
@ -14,6 +14,7 @@ export type TabType =
|
||||
| 'connection'
|
||||
| 'debug'
|
||||
| 'event-logs'
|
||||
| 'notes'
|
||||
| 'update'
|
||||
| 'task-manager'
|
||||
| 'tab-management';
|
||||
|
@ -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
|
||||
|
62
app/components/@settings/tabs/notes/NotesTab.tsx
Normal file
62
app/components/@settings/tabs/notes/NotesTab.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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)',
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
48
app/lib/stores/notes.ts
Normal 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);
|
||||
}
|
@ -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,
|
||||
});
|
||||
|
@ -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.',
|
||||
|
@ -66,6 +66,7 @@ async function llmCallAction({ context, request }: ActionFunctionArgs) {
|
||||
content: `${message}`,
|
||||
},
|
||||
],
|
||||
userNotes: undefined,
|
||||
env: context.cloudflare?.env as any,
|
||||
apiKeys,
|
||||
providerSettings,
|
||||
|
Loading…
Reference in New Issue
Block a user