Merge branch 'main' into copyMyFix
This commit is contained in:
@@ -1,13 +1,30 @@
|
||||
import { memo } from 'react';
|
||||
import { Markdown } from './Markdown';
|
||||
import type { JSONValue } from 'ai';
|
||||
|
||||
interface AssistantMessageProps {
|
||||
content: string;
|
||||
annotations?: JSONValue[];
|
||||
}
|
||||
|
||||
export const AssistantMessage = memo(({ content }: AssistantMessageProps) => {
|
||||
export const AssistantMessage = memo(({ content, annotations }: AssistantMessageProps) => {
|
||||
const filteredAnnotations = (annotations?.filter(
|
||||
(annotation: JSONValue) => annotation && typeof annotation === 'object' && Object.keys(annotation).includes('type'),
|
||||
) || []) as { type: string; value: any }[];
|
||||
|
||||
const usage: {
|
||||
completionTokens: number;
|
||||
promptTokens: number;
|
||||
totalTokens: number;
|
||||
} = filteredAnnotations.find((annotation) => annotation.type === 'usage')?.value;
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden w-full">
|
||||
{usage && (
|
||||
<div className="text-sm text-bolt-elements-textSecondary mb-2">
|
||||
Tokens: {usage.totalTokens} (prompt: {usage.promptTokens}, completion: {usage.completionTokens})
|
||||
</div>
|
||||
)}
|
||||
<Markdown html>{content}</Markdown>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -77,7 +77,8 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
input = '',
|
||||
enhancingPrompt,
|
||||
handleInputChange,
|
||||
promptEnhanced,
|
||||
|
||||
// promptEnhanced,
|
||||
enhancePrompt,
|
||||
sendMessage,
|
||||
handleStop,
|
||||
@@ -490,10 +491,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
<IconButton
|
||||
title="Enhance prompt"
|
||||
disabled={input.length === 0 || enhancingPrompt}
|
||||
className={classNames(
|
||||
'transition-all',
|
||||
enhancingPrompt ? 'opacity-100' : '',
|
||||
)}
|
||||
className={classNames('transition-all', enhancingPrompt ? 'opacity-100' : '')}
|
||||
onClick={() => {
|
||||
enhancePrompt?.();
|
||||
toast.success('Prompt enhanced!');
|
||||
|
||||
@@ -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,14 +115,24 @@ export const ChatImpl = memo(
|
||||
body: {
|
||||
apiKeys,
|
||||
files,
|
||||
promptId,
|
||||
},
|
||||
sendExtraMessageFields: true,
|
||||
onError: (error) => {
|
||||
logger.error('Request failed\n\n', error);
|
||||
toast.error(
|
||||
'There was an error processing your request: ' + (error.message ? error.message : 'No details were returned'),
|
||||
);
|
||||
},
|
||||
onFinish: () => {
|
||||
onFinish: (message, response) => {
|
||||
const usage = response.usage;
|
||||
|
||||
if (usage) {
|
||||
console.log('Token usage:', usage);
|
||||
|
||||
// You can now use the usage data as needed
|
||||
}
|
||||
|
||||
logger.debug('Finished streaming');
|
||||
},
|
||||
initialMessages,
|
||||
|
||||
@@ -65,12 +65,16 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-col-1 w-full">
|
||||
{isUserMessage ? <UserMessage content={content} /> : <AssistantMessage content={content} />}
|
||||
{isUserMessage ? (
|
||||
<UserMessage content={content} />
|
||||
) : (
|
||||
<AssistantMessage content={content} annotations={message.annotations} />
|
||||
)}
|
||||
</div>
|
||||
{!isUserMessage && (
|
||||
<div className="flex gap-2 flex-col lg:flex-row">
|
||||
<WithTooltip tooltip="Revert to this message">
|
||||
{messageId && (
|
||||
{messageId && (
|
||||
<WithTooltip tooltip="Revert to this message">
|
||||
<button
|
||||
onClick={() => handleRewind(messageId)}
|
||||
key="i-ph:arrow-u-up-left"
|
||||
@@ -79,8 +83,8 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
|
||||
'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</WithTooltip>
|
||||
</WithTooltip>
|
||||
)}
|
||||
|
||||
<WithTooltip tooltip="Fork chat from this message">
|
||||
<button
|
||||
|
||||
@@ -12,42 +12,36 @@ interface UserMessageProps {
|
||||
export function UserMessage({ content }: UserMessageProps) {
|
||||
if (Array.isArray(content)) {
|
||||
const textItem = content.find((item) => item.type === 'text');
|
||||
const textContent = sanitizeUserMessage(textItem?.text || '');
|
||||
const textContent = stripMetadata(textItem?.text || '');
|
||||
const images = content.filter((item) => item.type === 'image' && item.image);
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden pt-[4px]">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-1">
|
||||
<Markdown limitedMarkdown>{textContent}</Markdown>
|
||||
</div>
|
||||
{images.length > 0 && (
|
||||
<div className="flex-shrink-0 w-[160px]">
|
||||
{images.map((item, index) => (
|
||||
<div key={index} className="relative">
|
||||
<img
|
||||
src={item.image}
|
||||
alt={`Uploaded image ${index + 1}`}
|
||||
className="w-full h-[160px] rounded-lg object-cover border border-bolt-elements-borderColor"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-4">
|
||||
{textContent && <Markdown html>{textContent}</Markdown>}
|
||||
{images.map((item, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={item.image}
|
||||
alt={`Image ${index + 1}`}
|
||||
className="max-w-full h-auto rounded-lg"
|
||||
style={{ maxHeight: '512px', objectFit: 'contain' }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const textContent = sanitizeUserMessage(content);
|
||||
const textContent = stripMetadata(content);
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden pt-[4px]">
|
||||
<Markdown limitedMarkdown>{textContent}</Markdown>
|
||||
<Markdown html>{textContent}</Markdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function sanitizeUserMessage(content: string) {
|
||||
function stripMetadata(content: string) {
|
||||
return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '');
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ export default function ChatHistoryTab() {
|
||||
};
|
||||
|
||||
const handleDeleteAllChats = async () => {
|
||||
const confirmDelete = window.confirm("Are you sure you want to delete all chats? This action cannot be undone.");
|
||||
const confirmDelete = window.confirm('Are you sure you want to delete all chats? This action cannot be undone.');
|
||||
|
||||
if (!confirmDelete) {
|
||||
return; // Exit if the user cancels
|
||||
}
|
||||
@@ -31,11 +32,13 @@ export default function ChatHistoryTab() {
|
||||
const error = new Error('Database is not available');
|
||||
logStore.logError('Failed to delete chats - DB unavailable', error);
|
||||
toast.error('Database is not available');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
|
||||
const allChats = await getAll(db);
|
||||
await Promise.all(allChats.map((chat) => deleteById(db!, chat.id)));
|
||||
logStore.logSystem('All chats deleted successfully', { count: allChats.length });
|
||||
@@ -55,6 +58,7 @@ export default function ChatHistoryTab() {
|
||||
const error = new Error('Database is not available');
|
||||
logStore.logError('Failed to export chats - DB unavailable', error);
|
||||
toast.error('Database is not available');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,12 @@ interface SystemInfo {
|
||||
timezone: string;
|
||||
memory: string;
|
||||
cores: number;
|
||||
deviceType: string;
|
||||
colorDepth: string;
|
||||
pixelRatio: number;
|
||||
online: boolean;
|
||||
cookiesEnabled: boolean;
|
||||
doNotTrack: boolean;
|
||||
}
|
||||
|
||||
interface IProviderConfig {
|
||||
@@ -34,14 +40,19 @@ interface IProviderConfig {
|
||||
|
||||
interface CommitData {
|
||||
commit: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
const connitJson: CommitData = commit;
|
||||
|
||||
const LOCAL_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike'];
|
||||
const versionHash = commit.commit;
|
||||
const versionHash = connitJson.commit;
|
||||
const versionTag = connitJson.version;
|
||||
const GITHUB_URLS = {
|
||||
original: 'https://api.github.com/repos/stackblitz-labs/bolt.diy/commits/main',
|
||||
fork: 'https://api.github.com/repos/Stijnus/bolt.new-any-llm/commits/main',
|
||||
commitJson: (branch: string) => `https://raw.githubusercontent.com/stackblitz-labs/bolt.diy/${branch}/app/commit.json`,
|
||||
commitJson: (branch: string) =>
|
||||
`https://raw.githubusercontent.com/stackblitz-labs/bolt.diy/${branch}/app/commit.json`,
|
||||
};
|
||||
|
||||
function getSystemInfo(): SystemInfo {
|
||||
@@ -57,14 +68,100 @@ function getSystemInfo(): SystemInfo {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const getBrowserInfo = (): string => {
|
||||
const ua = navigator.userAgent;
|
||||
let browser = 'Unknown';
|
||||
|
||||
if (ua.includes('Firefox/')) {
|
||||
browser = 'Firefox';
|
||||
} else if (ua.includes('Chrome/')) {
|
||||
if (ua.includes('Edg/')) {
|
||||
browser = 'Edge';
|
||||
} else if (ua.includes('OPR/')) {
|
||||
browser = 'Opera';
|
||||
} else {
|
||||
browser = 'Chrome';
|
||||
}
|
||||
} else if (ua.includes('Safari/')) {
|
||||
if (!ua.includes('Chrome')) {
|
||||
browser = 'Safari';
|
||||
}
|
||||
}
|
||||
|
||||
// Extract version number
|
||||
const match = ua.match(new RegExp(`${browser}\\/([\\d.]+)`));
|
||||
const version = match ? ` ${match[1]}` : '';
|
||||
|
||||
return `${browser}${version}`;
|
||||
};
|
||||
|
||||
const getOperatingSystem = (): string => {
|
||||
const ua = navigator.userAgent;
|
||||
const platform = navigator.platform;
|
||||
|
||||
if (ua.includes('Win')) {
|
||||
return 'Windows';
|
||||
}
|
||||
|
||||
if (ua.includes('Mac')) {
|
||||
if (ua.includes('iPhone') || ua.includes('iPad')) {
|
||||
return 'iOS';
|
||||
}
|
||||
|
||||
return 'macOS';
|
||||
}
|
||||
|
||||
if (ua.includes('Linux')) {
|
||||
return 'Linux';
|
||||
}
|
||||
|
||||
if (ua.includes('Android')) {
|
||||
return 'Android';
|
||||
}
|
||||
|
||||
return platform || 'Unknown';
|
||||
};
|
||||
|
||||
const getDeviceType = (): string => {
|
||||
const ua = navigator.userAgent;
|
||||
|
||||
if (ua.includes('Mobile')) {
|
||||
return 'Mobile';
|
||||
}
|
||||
|
||||
if (ua.includes('Tablet')) {
|
||||
return 'Tablet';
|
||||
}
|
||||
|
||||
return 'Desktop';
|
||||
};
|
||||
|
||||
// Get more detailed memory info if available
|
||||
const getMemoryInfo = (): string => {
|
||||
if ('memory' in performance) {
|
||||
const memory = (performance as any).memory;
|
||||
return `${formatBytes(memory.jsHeapSizeLimit)} (Used: ${formatBytes(memory.usedJSHeapSize)})`;
|
||||
}
|
||||
|
||||
return 'Not available';
|
||||
};
|
||||
|
||||
return {
|
||||
os: navigator.platform,
|
||||
browser: navigator.userAgent.split(' ').slice(-1)[0],
|
||||
os: getOperatingSystem(),
|
||||
browser: getBrowserInfo(),
|
||||
screen: `${window.screen.width}x${window.screen.height}`,
|
||||
language: navigator.language,
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
memory: formatBytes(performance?.memory?.jsHeapSizeLimit || 0),
|
||||
memory: getMemoryInfo(),
|
||||
cores: navigator.hardwareConcurrency || 0,
|
||||
deviceType: getDeviceType(),
|
||||
|
||||
// Add new fields
|
||||
colorDepth: `${window.screen.colorDepth}-bit`,
|
||||
pixelRatio: window.devicePixelRatio,
|
||||
online: navigator.onLine,
|
||||
cookiesEnabled: navigator.cookieEnabled,
|
||||
doNotTrack: navigator.doNotTrack === '1',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -206,7 +303,7 @@ const checkProviderStatus = async (url: string | null, providerName: string): Pr
|
||||
};
|
||||
|
||||
export default function DebugTab() {
|
||||
const { providers, useLatestBranch } = useSettings();
|
||||
const { providers, isLatestBranch } = useSettings();
|
||||
const [activeProviders, setActiveProviders] = useState<ProviderStatus[]>([]);
|
||||
const [updateMessage, setUpdateMessage] = useState<string>('');
|
||||
const [systemInfo] = useState<SystemInfo>(getSystemInfo());
|
||||
@@ -227,19 +324,20 @@ export default function DebugTab() {
|
||||
provider.name.toLowerCase() === 'ollama'
|
||||
? 'OLLAMA_API_BASE_URL'
|
||||
: provider.name.toLowerCase() === 'lmstudio'
|
||||
? 'LMSTUDIO_API_BASE_URL'
|
||||
: `REACT_APP_${provider.name.toUpperCase()}_URL`;
|
||||
? 'LMSTUDIO_API_BASE_URL'
|
||||
: `REACT_APP_${provider.name.toUpperCase()}_URL`;
|
||||
|
||||
// Access environment variables through import.meta.env
|
||||
const url = import.meta.env[envVarName] || provider.settings.baseUrl || null; // Ensure baseUrl is used
|
||||
console.log(`[Debug] Using URL for ${provider.name}:`, url, `(from ${envVarName})`);
|
||||
|
||||
const status = await checkProviderStatus(url, provider.name);
|
||||
|
||||
return {
|
||||
...status,
|
||||
enabled: provider.settings.enabled ?? false,
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
setActiveProviders(statuses);
|
||||
@@ -265,23 +363,24 @@ export default function DebugTab() {
|
||||
setIsCheckingUpdate(true);
|
||||
setUpdateMessage('Checking for updates...');
|
||||
|
||||
const branchToCheck = useLatestBranch ? 'main' : 'stable';
|
||||
const branchToCheck = isLatestBranch ? 'main' : 'stable';
|
||||
console.log(`[Debug] Checking for updates against ${branchToCheck} branch`);
|
||||
|
||||
const localCommitResponse = await fetch(GITHUB_URLS.commitJson(branchToCheck));
|
||||
|
||||
if (!localCommitResponse.ok) {
|
||||
throw new Error('Failed to fetch local commit info');
|
||||
}
|
||||
|
||||
const localCommitData = await localCommitResponse.json() as CommitData;
|
||||
const localCommitData = (await localCommitResponse.json()) as CommitData;
|
||||
const remoteCommitHash = localCommitData.commit;
|
||||
const currentCommitHash = versionHash;
|
||||
|
||||
if (remoteCommitHash !== currentCommitHash) {
|
||||
setUpdateMessage(
|
||||
`Update available from ${branchToCheck} branch!\n` +
|
||||
`Current: ${currentCommitHash.slice(0, 7)}\n` +
|
||||
`Latest: ${remoteCommitHash.slice(0, 7)}`
|
||||
`Current: ${currentCommitHash.slice(0, 7)}\n` +
|
||||
`Latest: ${remoteCommitHash.slice(0, 7)}`,
|
||||
);
|
||||
} else {
|
||||
setUpdateMessage(`You are on the latest version from the ${branchToCheck} branch`);
|
||||
@@ -292,7 +391,7 @@ export default function DebugTab() {
|
||||
} finally {
|
||||
setIsCheckingUpdate(false);
|
||||
}
|
||||
}, [isCheckingUpdate, useLatestBranch]);
|
||||
}, [isCheckingUpdate, isLatestBranch]);
|
||||
|
||||
const handleCopyToClipboard = useCallback(() => {
|
||||
const debugInfo = {
|
||||
@@ -309,7 +408,7 @@ export default function DebugTab() {
|
||||
})),
|
||||
Version: {
|
||||
hash: versionHash.slice(0, 7),
|
||||
branch: useLatestBranch ? 'main' : 'stable'
|
||||
branch: isLatestBranch ? 'main' : 'stable',
|
||||
},
|
||||
Timestamp: new Date().toISOString(),
|
||||
};
|
||||
@@ -317,7 +416,7 @@ export default function DebugTab() {
|
||||
navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2)).then(() => {
|
||||
toast.success('Debug information copied to clipboard!');
|
||||
});
|
||||
}, [activeProviders, systemInfo, useLatestBranch]);
|
||||
}, [activeProviders, systemInfo, isLatestBranch]);
|
||||
|
||||
return (
|
||||
<div className="p-4 space-y-6">
|
||||
@@ -377,10 +476,31 @@ export default function DebugTab() {
|
||||
<p className="text-xs text-bolt-elements-textSecondary">Operating System</p>
|
||||
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.os}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-bolt-elements-textSecondary">Device Type</p>
|
||||
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.deviceType}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-bolt-elements-textSecondary">Browser</p>
|
||||
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.browser}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-bolt-elements-textSecondary">Display</p>
|
||||
<p className="text-sm font-medium text-bolt-elements-textPrimary">
|
||||
{systemInfo.screen} ({systemInfo.colorDepth}) @{systemInfo.pixelRatio}x
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-bolt-elements-textSecondary">Connection</p>
|
||||
<p className="text-sm font-medium flex items-center gap-2">
|
||||
<span
|
||||
className={`inline-block w-2 h-2 rounded-full ${systemInfo.online ? 'bg-green-500' : 'bg-red-500'}`}
|
||||
/>
|
||||
<span className={`${systemInfo.online ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{systemInfo.online ? 'Online' : 'Offline'}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-bolt-elements-textSecondary">Screen Resolution</p>
|
||||
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.screen}</p>
|
||||
@@ -403,7 +523,7 @@ export default function DebugTab() {
|
||||
<p className="text-sm font-medium text-bolt-elements-textPrimary font-mono">
|
||||
{versionHash.slice(0, 7)}
|
||||
<span className="ml-2 text-xs text-bolt-elements-textSecondary">
|
||||
({new Date().toLocaleDateString()})
|
||||
(v{versionTag || '0.0.1'}) - {isLatestBranch ? 'nightly' : 'stable'}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +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, useLatestBranch, enableLatestBranch } = useSettings();
|
||||
const {
|
||||
debug,
|
||||
enableDebugMode,
|
||||
isLocalModel,
|
||||
enableLocalModels,
|
||||
enableEventLogs,
|
||||
isLatestBranch,
|
||||
enableLatestBranch,
|
||||
promptId,
|
||||
setPromptId,
|
||||
} = useSettings();
|
||||
|
||||
const handleToggle = (enabled: boolean) => {
|
||||
enableDebugMode(enabled);
|
||||
@@ -22,9 +33,11 @@ export default function FeaturesTab() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<span className="text-bolt-elements-textPrimary">Use Main Branch</span>
|
||||
<p className="text-sm text-bolt-elements-textSecondary">Check for updates against the main branch instead of stable</p>
|
||||
<p className="text-sm text-bolt-elements-textSecondary">
|
||||
Check for updates against the main branch instead of stable
|
||||
</p>
|
||||
</div>
|
||||
<Switch className="ml-auto" checked={useLatestBranch} onCheckedChange={enableLatestBranch} />
|
||||
<Switch className="ml-auto" checked={isLatestBranch} onCheckedChange={enableLatestBranch} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,10 +47,28 @@ 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">
|
||||
<span className="text-bolt-elements-textPrimary">Experimental Providers</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>
|
||||
);
|
||||
|
||||
@@ -56,7 +56,8 @@ export default function ProvidersTab() {
|
||||
<div className="flex items-center gap-2">
|
||||
<img
|
||||
src={`/icons/${provider.name}.svg`} // Attempt to load the specific icon
|
||||
onError={(e) => { // Fallback to default icon on error
|
||||
onError={(e) => {
|
||||
// Fallback to default icon on error
|
||||
e.currentTarget.src = DefaultIcon;
|
||||
}}
|
||||
alt={`${provider.name} icon`}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo } from 'react';
|
||||
import { memo, forwardRef, type ForwardedRef } from 'react';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
|
||||
type IconSize = 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
|
||||
@@ -25,41 +25,48 @@ type IconButtonWithChildrenProps = {
|
||||
|
||||
type IconButtonProps = IconButtonWithoutChildrenProps | IconButtonWithChildrenProps;
|
||||
|
||||
// Componente IconButton com suporte a refs
|
||||
export const IconButton = memo(
|
||||
({
|
||||
icon,
|
||||
size = 'xl',
|
||||
className,
|
||||
iconClassName,
|
||||
disabledClassName,
|
||||
disabled = false,
|
||||
title,
|
||||
onClick,
|
||||
children,
|
||||
}: IconButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
'flex items-center text-bolt-elements-item-contentDefault bg-transparent enabled:hover:text-bolt-elements-item-contentActive rounded-md p-1 enabled:hover:bg-bolt-elements-item-backgroundActive disabled:cursor-not-allowed',
|
||||
{
|
||||
[classNames('opacity-30', disabledClassName)]: disabled,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
title={title}
|
||||
disabled={disabled}
|
||||
onClick={(event) => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
forwardRef(
|
||||
(
|
||||
{
|
||||
icon,
|
||||
size = 'xl',
|
||||
className,
|
||||
iconClassName,
|
||||
disabledClassName,
|
||||
disabled = false,
|
||||
title,
|
||||
onClick,
|
||||
children,
|
||||
}: IconButtonProps,
|
||||
ref: ForwardedRef<HTMLButtonElement>,
|
||||
) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
'flex items-center text-bolt-elements-item-contentDefault bg-transparent enabled:hover:text-bolt-elements-item-contentActive rounded-md p-1 enabled:hover:bg-bolt-elements-item-backgroundActive disabled:cursor-not-allowed',
|
||||
{
|
||||
[classNames('opacity-30', disabledClassName)]: disabled,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
title={title}
|
||||
disabled={disabled}
|
||||
onClick={(event) => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
onClick?.(event);
|
||||
}}
|
||||
>
|
||||
{children ? children : <div className={classNames(icon, getIconSize(size), iconClassName)}></div>}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
onClick?.(event);
|
||||
}}
|
||||
>
|
||||
{children ? children : <div className={classNames(icon, getIconSize(size), iconClassName)}></div>}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
function getIconSize(size: IconSize) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
import { forwardRef, type ForwardedRef, type ReactElement } from 'react';
|
||||
|
||||
interface TooltipProps {
|
||||
tooltip: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
children: ReactElement;
|
||||
sideOffset?: number;
|
||||
className?: string;
|
||||
arrowClassName?: string;
|
||||
@@ -12,62 +13,67 @@ interface TooltipProps {
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
const WithTooltip = ({
|
||||
tooltip,
|
||||
children,
|
||||
sideOffset = 5,
|
||||
className = '',
|
||||
arrowClassName = '',
|
||||
tooltipStyle = {},
|
||||
position = 'top',
|
||||
maxWidth = 250,
|
||||
delay = 0,
|
||||
}: TooltipProps) => {
|
||||
return (
|
||||
<Tooltip.Root delayDuration={delay}>
|
||||
<Tooltip.Trigger asChild>{children}</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
side={position}
|
||||
className={`
|
||||
z-[2000]
|
||||
px-2.5
|
||||
py-1.5
|
||||
max-h-[300px]
|
||||
select-none
|
||||
rounded-md
|
||||
bg-bolt-elements-background-depth-3
|
||||
text-bolt-elements-textPrimary
|
||||
text-sm
|
||||
leading-tight
|
||||
shadow-lg
|
||||
animate-in
|
||||
fade-in-0
|
||||
zoom-in-95
|
||||
data-[state=closed]:animate-out
|
||||
data-[state=closed]:fade-out-0
|
||||
data-[state=closed]:zoom-out-95
|
||||
${className}
|
||||
`}
|
||||
sideOffset={sideOffset}
|
||||
style={{
|
||||
maxWidth,
|
||||
...tooltipStyle,
|
||||
}}
|
||||
>
|
||||
<div className="break-words">{tooltip}</div>
|
||||
<Tooltip.Arrow
|
||||
const WithTooltip = forwardRef(
|
||||
(
|
||||
{
|
||||
tooltip,
|
||||
children,
|
||||
sideOffset = 5,
|
||||
className = '',
|
||||
arrowClassName = '',
|
||||
tooltipStyle = {},
|
||||
position = 'top',
|
||||
maxWidth = 250,
|
||||
delay = 0,
|
||||
}: TooltipProps,
|
||||
_ref: ForwardedRef<HTMLElement>,
|
||||
) => {
|
||||
return (
|
||||
<Tooltip.Root delayDuration={delay}>
|
||||
<Tooltip.Trigger asChild>{children}</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
side={position}
|
||||
className={`
|
||||
fill-bolt-elements-background-depth-3
|
||||
${arrowClassName}
|
||||
z-[2000]
|
||||
px-2.5
|
||||
py-1.5
|
||||
max-h-[300px]
|
||||
select-none
|
||||
rounded-md
|
||||
bg-bolt-elements-background-depth-3
|
||||
text-bolt-elements-textPrimary
|
||||
text-sm
|
||||
leading-tight
|
||||
shadow-lg
|
||||
animate-in
|
||||
fade-in-0
|
||||
zoom-in-95
|
||||
data-[state=closed]:animate-out
|
||||
data-[state=closed]:fade-out-0
|
||||
data-[state=closed]:zoom-out-95
|
||||
${className}
|
||||
`}
|
||||
width={12}
|
||||
height={6}
|
||||
/>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
};
|
||||
sideOffset={sideOffset}
|
||||
style={{
|
||||
maxWidth,
|
||||
...tooltipStyle,
|
||||
}}
|
||||
>
|
||||
<div className="break-words">{tooltip}</div>
|
||||
<Tooltip.Arrow
|
||||
className={`
|
||||
fill-bolt-elements-background-depth-3
|
||||
${arrowClassName}
|
||||
`}
|
||||
width={12}
|
||||
height={6}
|
||||
/>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default WithTooltip;
|
||||
|
||||
Reference in New Issue
Block a user