orchid-ai 1.2.7 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import type { ModelCapabilities } from '../types/types';
2
+ import type { ServerConfig } from '../types/types';
2
3
  /**
3
4
  * Hook to resolve tier-based default model config to actual model
4
5
  *
@@ -8,13 +9,14 @@ import type { ModelCapabilities } from '../types/types';
8
9
  * match for the provider and tier combination.
9
10
  *
10
11
  * @param defaultModel - The model configuration to resolve
12
+ * @param serverConfig - Optional server config to build the models endpoint URL
11
13
  * @returns The resolved model with provider, model name, and capabilities
12
14
  */
13
15
  export declare function useResolvedDefaultModel(defaultModel?: {
14
16
  provider: string;
15
17
  model?: string;
16
18
  tier?: 'fast' | 'balanced' | 'powerful';
17
- }): {
19
+ }, serverConfig?: ServerConfig): {
18
20
  provider: string;
19
21
  model: string;
20
22
  capabilities: ModelCapabilities;
@@ -10,5 +10,5 @@ export { mergeWithAi } from './utils';
10
10
  export { useAiMerge } from './hooks/useAiMerge';
11
11
  export { ErrorBoundary } from './components/ErrorBoundary';
12
12
  export { Icon as CommandIcon } from './components/Icon';
13
- export type { CommandConfig, TrainingData, AIProvider, CommandTheme, CommandSuggestion, CommandPopupProps, ChatMessage, ChatTheme, AIStatus, Action, ModelInfo, ServerConfig, SchemaDefinition, SchemaProperty, ContextualServiceConfig, ContextualRequest, } from './types/types';
13
+ export type { CommandConfig, TrainingData, AIProvider, CommandTheme, CommandSuggestion, CommandPopupProps, ChatMessage, ChatTheme, AIStatus, Action, ModelInfo, ServerConfig, ClientConfig, SchemaDefinition, SchemaProperty, ContextualServiceConfig, ContextualRequest, } from './types/types';
14
14
  export { defaultTheme as defaultCommandTheme } from './constants';
@@ -24,6 +24,7 @@ export declare class ContextualCommandService {
24
24
  private buildContextualSystemPrompt;
25
25
  /**
26
26
  * Format schema fields into readable descriptions
27
+ * Excludes client-generated fields from the output
27
28
  */
28
29
  private formatSchemaFields;
29
30
  /**
@@ -134,6 +134,24 @@ export interface ServerConfig {
134
134
  additionalContext?: string;
135
135
  stayOnPage?: boolean;
136
136
  }
137
+ export interface ClientConfig {
138
+ models?: Record<string, ModelInfo[]>;
139
+ defaultModel?: {
140
+ provider: string;
141
+ model?: string;
142
+ tier?: 'fast' | 'balanced' | 'powerful';
143
+ };
144
+ showUsageStats?: boolean;
145
+ maxFileSize?: string;
146
+ features?: {
147
+ modelSwitching?: boolean;
148
+ usageTracking?: boolean;
149
+ imageAnalysis?: boolean;
150
+ fileUploads?: boolean;
151
+ enableImageUploads?: boolean;
152
+ };
153
+ schemas?: Record<string, SchemaDefinition>;
154
+ }
137
155
  export interface TrainingDataPaths {
138
156
  components: string[];
139
157
  schemas: string[];
@@ -465,6 +483,8 @@ export interface SchemaProperty {
465
483
  maxLength?: number;
466
484
  minimum?: number;
467
485
  maximum?: number;
486
+ /** If true, this field is not to be generated by AI (e.g. auto-generated fields) */
487
+ skip?: boolean;
468
488
  }
469
489
  /**
470
490
  * Simplified config for ContextualCommandService
@@ -1,4 +1,5 @@
1
1
  import type { ModelCapabilities } from '../types/types';
2
+ import type { ServerConfig } from '../types/types';
2
3
  /**
3
4
  * Hook to resolve tier-based default model config to actual model
4
5
  *
@@ -8,13 +9,14 @@ import type { ModelCapabilities } from '../types/types';
8
9
  * match for the provider and tier combination.
9
10
  *
10
11
  * @param defaultModel - The model configuration to resolve
12
+ * @param serverConfig - Optional server config to build the models endpoint URL
11
13
  * @returns The resolved model with provider, model name, and capabilities
12
14
  */
13
15
  export declare function useResolvedDefaultModel(defaultModel?: {
14
16
  provider: string;
15
17
  model?: string;
16
18
  tier?: 'fast' | 'balanced' | 'powerful';
17
- }): {
19
+ }, serverConfig?: ServerConfig): {
18
20
  provider: string;
19
21
  model: string;
20
22
  capabilities: ModelCapabilities;
package/dist/index.d.ts CHANGED
@@ -10,5 +10,5 @@ export { mergeWithAi } from './utils';
10
10
  export { useAiMerge } from './hooks/useAiMerge';
11
11
  export { ErrorBoundary } from './components/ErrorBoundary';
12
12
  export { Icon as CommandIcon } from './components/Icon';
13
- export type { CommandConfig, TrainingData, AIProvider, CommandTheme, CommandSuggestion, CommandPopupProps, ChatMessage, ChatTheme, AIStatus, Action, ModelInfo, ServerConfig, SchemaDefinition, SchemaProperty, ContextualServiceConfig, ContextualRequest, } from './types/types';
13
+ export type { CommandConfig, TrainingData, AIProvider, CommandTheme, CommandSuggestion, CommandPopupProps, ChatMessage, ChatTheme, AIStatus, Action, ModelInfo, ServerConfig, ClientConfig, SchemaDefinition, SchemaProperty, ContextualServiceConfig, ContextualRequest, } from './types/types';
14
14
  export { defaultTheme as defaultCommandTheme } from './constants';
package/dist/index.esm.js CHANGED
@@ -1100,6 +1100,22 @@ function useDebouncedSuggestions(query, options) {
1100
1100
  };
1101
1101
  }
1102
1102
 
1103
+ // Helper function to build URL from config (reused from useModelSwitcher)
1104
+ function buildUrlFromConfig$1(config) {
1105
+ if (!config)
1106
+ return '';
1107
+ const suffix = config.suffix || '';
1108
+ if (suffix.startsWith('http://') || suffix.startsWith('https://')) {
1109
+ return suffix;
1110
+ }
1111
+ if (typeof window === 'undefined') {
1112
+ return suffix;
1113
+ }
1114
+ const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
1115
+ const hostname = window.location.hostname;
1116
+ const port = window.location.port ? `:${window.location.port}` : '';
1117
+ return `${protocol}//${hostname}${port}${suffix}`;
1118
+ }
1103
1119
  /**
1104
1120
  * Hook to resolve tier-based default model config to actual model
1105
1121
  *
@@ -1109,9 +1125,10 @@ function useDebouncedSuggestions(query, options) {
1109
1125
  * match for the provider and tier combination.
1110
1126
  *
1111
1127
  * @param defaultModel - The model configuration to resolve
1128
+ * @param serverConfig - Optional server config to build the models endpoint URL
1112
1129
  * @returns The resolved model with provider, model name, and capabilities
1113
1130
  */
1114
- function useResolvedDefaultModel(defaultModel) {
1131
+ function useResolvedDefaultModel(defaultModel, serverConfig) {
1115
1132
  const [resolvedModel, setResolvedModel] = useState();
1116
1133
  useEffect(() => {
1117
1134
  if (!defaultModel || defaultModel.model) {
@@ -1128,7 +1145,11 @@ function useResolvedDefaultModel(defaultModel) {
1128
1145
  // Need to resolve tier to actual model
1129
1146
  const resolveModel = async () => {
1130
1147
  try {
1131
- const response = await fetch('/command/models');
1148
+ // Build models endpoint URL from serverConfig if provided, otherwise use default
1149
+ const endpoint = serverConfig
1150
+ ? `${buildUrlFromConfig$1(serverConfig)}/models`
1151
+ : '/command/models';
1152
+ const response = await fetch(endpoint);
1132
1153
  if (!response.ok)
1133
1154
  throw new Error('Failed to fetch models');
1134
1155
  const data = await response.json();
@@ -1155,7 +1176,7 @@ function useResolvedDefaultModel(defaultModel) {
1155
1176
  }
1156
1177
  };
1157
1178
  resolveModel();
1158
- }, [defaultModel]);
1179
+ }, [defaultModel, serverConfig]);
1159
1180
  return resolvedModel;
1160
1181
  }
1161
1182
 
@@ -3302,7 +3323,7 @@ theme, isOpen, }) {
3302
3323
  }, [onModelSelectionChange]);
3303
3324
  return (jsxs("div", { className: `px-3 py-2 border-t flex flex-col gap-2 transition-all duration-500 delay-500 ${isOpen ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'}`, style: { borderColor: theme.colors.border.primary }, children: [attachedFiles.length > 0 && (jsx("div", { className: "flex flex-wrap gap-2 mb-2", children: attachedFiles.map((file, index) => (jsxs("div", { className: "relative group", children: [jsx("div", { className: "w-16 h-16 rounded-lg border border-gray-200 bg-gray-50 flex items-center justify-center overflow-hidden", children: file.type.startsWith('image/') ? (jsx("img", { src: URL.createObjectURL(file), alt: `Attached ${index + 1}`, className: "w-full h-full object-cover" })) : (jsx("div", { className: "flex items-center justify-center", children: FileHandler.getFileIcon(file) })) }), jsx("button", { onClick: () => removeFile(index), className: "absolute -top-2 -right-2 w-5 h-5 bg-red-500 text-white rounded-full flex items-center justify-center text-xs hover:bg-red-600 transition-colors", title: "Remove file", children: "\u00D7" }), jsx("div", { className: "absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-full opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-10 mt-2", children: jsxs("div", { className: "bg-gray-900 text-white text-xs rounded-lg px-2 py-1 whitespace-nowrap shadow-lg", children: [jsx("div", { className: "font-medium truncate max-w-32", title: file.name, children: file.name }), jsxs("div", { className: "text-gray-300", children: [FileHandler.getFileTypeDescription(file), " \u2022", ' ', FileHandler.formatFileSize(file.size)] }), jsx("div", { className: "absolute -top-1 left-1/2 transform -translate-x-1/2 w-2 h-2 bg-gray-900 rotate-45" })] }) })] }, index))) })), incompatibleFiles.length > 0 && (jsxs("div", { className: "relative flex flex-col gap-1 text-sm text-orange-700 bg-orange-50 border border-orange-200 rounded px-3 py-2 mb-2", children: [jsx("button", { onClick: () => setIncompatibleFiles([]), className: "absolute top-1 right-1 w-5 h-5 flex items-center justify-center text-orange-500 hover:text-orange-700 hover:bg-orange-200 rounded-full transition-colors", title: "Dismiss", type: "button", children: "\u00D7" }), jsxs("div", { className: "flex items-center gap-2 pr-6", children: [jsx("span", { children: "\u26A0\uFE0F" }), jsx("span", { className: "font-medium", children: incompatibleFiles.length === 1
3304
3325
  ? 'File not supported'
3305
- : 'Some files not supported' })] }), jsxs("div", { className: "text-xs", children: [jsx("div", { className: "mb-1", children: "These files were skipped:" }), jsxs("ul", { className: "list-disc list-inside ml-2 space-y-0.5", children: [incompatibleFiles.slice(0, 3).map((fileName, index) => (jsx("li", { className: "truncate", children: fileName }, index))), incompatibleFiles.length > 3 && (jsxs("li", { children: ["... and ", incompatibleFiles.length - 3, " more"] }))] }), jsxs("div", { className: "mt-1 text-orange-600", children: ["\uD83D\uDCA1 Only ", FileHandler.getAcceptedTypesDescription(), " are supported", incompatibleFiles.some((name) => name.includes('images not supported by current model')) && (jsxs("div", { className: "mt-1", children: ["\uD83D\uDD04 ", jsx("strong", { children: "Switch to GPT-4O, Claude 3, or Gemini" }), " to enable image uploads"] }))] })] })] })), jsxs("div", { className: `relative flex flex-col gap-1 rounded border px-2 pt-1 pb-1 cursor-text transition-all duration-200 border-2 focus-within:shadow-sm ${isDragOver ? 'border-dashed bg-opacity-20' : 'border-transparent'}`, style: {
3326
+ : 'Some files not supported' })] }), jsxs("div", { className: "text-xs", children: [jsx("div", { className: "mb-1", children: "These files were skipped:" }), jsxs("ul", { className: "list-disc list-inside ml-2 space-y-0.5", children: [incompatibleFiles.slice(0, 3).map((fileName, index) => (jsx("li", { className: "truncate", children: fileName }, index))), incompatibleFiles.length > 3 && (jsxs("li", { children: ["... and ", incompatibleFiles.length - 3, " more"] }))] }), jsxs("div", { className: "mt-1 text-orange-600", children: ["\uD83D\uDCA1 Only ", FileHandler.getAcceptedTypesDescription(), " are supported", incompatibleFiles.some((name) => name.includes('images not supported by current model')) && (jsx("div", { className: "mt-1", children: "\uD83D\uDD04 Switch to a model showing the image icon in the model switcher to enable image uploads" }))] })] })] })), jsxs("div", { className: `relative flex flex-col gap-1 rounded border px-2 pt-1 pb-1 cursor-text transition-all duration-200 border-2 focus-within:shadow-sm ${isDragOver ? 'border-dashed bg-opacity-20' : 'border-transparent'}`, style: {
3306
3327
  backgroundColor: theme.colors.surface.primary,
3307
3328
  borderColor: isDragOver
3308
3329
  ? theme.colors.primary[400]
@@ -3329,7 +3350,7 @@ theme, isOpen, }) {
3329
3350
  e.currentTarget.style.color = theme.colors.text.tertiary;
3330
3351
  }, title: imageSupported
3331
3352
  ? 'Attach images - AI can analyze image content'
3332
- : "Current model doesn't support images. Switch to GPT-4O, Claude 3, or Gemini to use image attachments.", tabIndex: -1, type: "button", onClick: () => imageSupported && imageInputRef.current?.click(), disabled: !imageSupported, children: jsx(Icon, { name: "image", size: "sm" }) })), jsx("button", { className: "p-1 cursor-pointer disabled:opacity-50 disabled:cursor-default transition-colors", style: { color: theme.colors.text.tertiary }, title: "Record audio (coming soon)", tabIndex: -1, type: "button", onClick: handleAudioRecord, disabled: true, children: jsx(Icon, { name: "microphone", size: "sm" }) })] }), jsx("button", { onClick: () => onSend(), disabled: (!query.trim() && attachedFiles.length === 0) || isLoading, className: "rounded-full transition disabled:opacity-50 p-2", style: {
3353
+ : "Current model doesn't support images. Switch to GPT-4O, Claude, or Gemini to use image attachments.", tabIndex: -1, type: "button", onClick: () => imageSupported && imageInputRef.current?.click(), disabled: !imageSupported, children: jsx(Icon, { name: "image", size: "sm" }) })), jsx("button", { className: "p-1 cursor-pointer disabled:opacity-50 disabled:cursor-default transition-colors", style: { color: theme.colors.text.tertiary }, title: "Record audio (coming soon)", tabIndex: -1, type: "button", onClick: handleAudioRecord, disabled: true, children: jsx(Icon, { name: "microphone", size: "sm" }) })] }), jsx("button", { onClick: () => onSend(), disabled: (!query.trim() && attachedFiles.length === 0) || isLoading, className: "rounded-full transition disabled:opacity-50 p-2", style: {
3333
3354
  color: theme.colors.text.primary,
3334
3355
  }, onMouseEnter: (e) => {
3335
3356
  if (!e.currentTarget.disabled) {
@@ -3344,7 +3365,7 @@ theme, isOpen, }) {
3344
3365
  features.imageAnalysis !== false &&
3345
3366
  features.enableImageUploads !== false &&
3346
3367
  !imageSupported &&
3347
- !imageTipDismissed && (jsxs("div", { className: "relative px-2 py-1 bg-amber-50 border border-amber-200 rounded text-amber-700 text-xs flex items-center gap-2", children: [jsx(Icon, { name: "image", size: "xs", className: "opacity-50" }), jsxs("span", { className: "flex-1 pr-4", children: ["\uD83D\uDCA1 ", jsx("strong", { children: "Tip:" }), " Switch to GPT-4O, Claude 3, or Gemini in the model switcher below to enable image attachments"] }), jsx("button", { onClick: () => setImageTipDismissed(true), className: "absolute top-1 right-1 w-4 h-4 flex items-center justify-center text-amber-500 hover:text-amber-700 hover:bg-amber-200 rounded-full transition-colors text-xs font-bold", title: "Dismiss tip", type: "button", children: "\u00D7" })] })), jsxs("div", { className: "flex items-center justify-between text-xs px-0 pt-0", style: { color: theme.colors.text.tertiary }, children: [jsx("div", { className: "flex gap-4", children: features.modelSwitching !== false && (jsx(ModelSwitcher, { models: models, defaultModel: defaultModel, showUsageStats: features.usageTracking !== false && showUsageStats !== false, theme: theme, onModelSelectionChange: modelSelectionHandler })) }), jsx("div", { className: "flex items-center gap-2", children: jsxs("span", { children: [jsx("kbd", { className: "px-1 rounded", style: {
3368
+ !imageTipDismissed && (jsxs("div", { className: "relative px-2 py-1 bg-amber-50 border border-amber-200 rounded text-amber-700 text-xs flex items-center gap-2", children: [jsx(Icon, { name: "image", size: "xs", className: "opacity-50" }), jsxs("span", { className: "flex-1 pr-4", children: ["\uD83D\uDCA1 ", jsx("strong", { children: "Tip:" }), " Switch to a model showing the image icon in the model switcher below to enable image uploads"] }), jsx("button", { onClick: () => setImageTipDismissed(true), className: "absolute top-1 right-1 w-4 h-4 flex items-center justify-center text-amber-500 hover:text-amber-700 hover:bg-amber-200 rounded-full transition-colors text-xs font-bold", title: "Dismiss tip", type: "button", children: "\u00D7" })] })), jsxs("div", { className: "flex items-center justify-between text-xs px-0 pt-0", style: { color: theme.colors.text.tertiary }, children: [jsx("div", { className: "flex gap-4", children: features.modelSwitching !== false && (jsx(ModelSwitcher, { models: models, defaultModel: defaultModel, showUsageStats: features.usageTracking !== false && showUsageStats !== false, theme: theme, onModelSelectionChange: modelSelectionHandler })) }), jsx("div", { className: "flex items-center gap-2", children: jsxs("span", { children: [jsx("kbd", { className: "px-1 rounded", style: {
3348
3369
  backgroundColor: theme.colors.surface.secondary,
3349
3370
  color: theme.colors.text.secondary,
3350
3371
  }, children: "\u23CE" }), ' ', "to send,", ' ', jsx("kbd", { className: "px-1 rounded", style: {
package/dist/index.js CHANGED
@@ -1102,6 +1102,22 @@ function useDebouncedSuggestions(query, options) {
1102
1102
  };
1103
1103
  }
1104
1104
 
1105
+ // Helper function to build URL from config (reused from useModelSwitcher)
1106
+ function buildUrlFromConfig$1(config) {
1107
+ if (!config)
1108
+ return '';
1109
+ const suffix = config.suffix || '';
1110
+ if (suffix.startsWith('http://') || suffix.startsWith('https://')) {
1111
+ return suffix;
1112
+ }
1113
+ if (typeof window === 'undefined') {
1114
+ return suffix;
1115
+ }
1116
+ const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
1117
+ const hostname = window.location.hostname;
1118
+ const port = window.location.port ? `:${window.location.port}` : '';
1119
+ return `${protocol}//${hostname}${port}${suffix}`;
1120
+ }
1105
1121
  /**
1106
1122
  * Hook to resolve tier-based default model config to actual model
1107
1123
  *
@@ -1111,9 +1127,10 @@ function useDebouncedSuggestions(query, options) {
1111
1127
  * match for the provider and tier combination.
1112
1128
  *
1113
1129
  * @param defaultModel - The model configuration to resolve
1130
+ * @param serverConfig - Optional server config to build the models endpoint URL
1114
1131
  * @returns The resolved model with provider, model name, and capabilities
1115
1132
  */
1116
- function useResolvedDefaultModel(defaultModel) {
1133
+ function useResolvedDefaultModel(defaultModel, serverConfig) {
1117
1134
  const [resolvedModel, setResolvedModel] = React.useState();
1118
1135
  React.useEffect(() => {
1119
1136
  if (!defaultModel || defaultModel.model) {
@@ -1130,7 +1147,11 @@ function useResolvedDefaultModel(defaultModel) {
1130
1147
  // Need to resolve tier to actual model
1131
1148
  const resolveModel = async () => {
1132
1149
  try {
1133
- const response = await fetch('/command/models');
1150
+ // Build models endpoint URL from serverConfig if provided, otherwise use default
1151
+ const endpoint = serverConfig
1152
+ ? `${buildUrlFromConfig$1(serverConfig)}/models`
1153
+ : '/command/models';
1154
+ const response = await fetch(endpoint);
1134
1155
  if (!response.ok)
1135
1156
  throw new Error('Failed to fetch models');
1136
1157
  const data = await response.json();
@@ -1157,7 +1178,7 @@ function useResolvedDefaultModel(defaultModel) {
1157
1178
  }
1158
1179
  };
1159
1180
  resolveModel();
1160
- }, [defaultModel]);
1181
+ }, [defaultModel, serverConfig]);
1161
1182
  return resolvedModel;
1162
1183
  }
1163
1184
 
@@ -3304,7 +3325,7 @@ theme, isOpen, }) {
3304
3325
  }, [onModelSelectionChange]);
3305
3326
  return (jsxRuntime.jsxs("div", { className: `px-3 py-2 border-t flex flex-col gap-2 transition-all duration-500 delay-500 ${isOpen ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'}`, style: { borderColor: theme.colors.border.primary }, children: [attachedFiles.length > 0 && (jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 mb-2", children: attachedFiles.map((file, index) => (jsxRuntime.jsxs("div", { className: "relative group", children: [jsxRuntime.jsx("div", { className: "w-16 h-16 rounded-lg border border-gray-200 bg-gray-50 flex items-center justify-center overflow-hidden", children: file.type.startsWith('image/') ? (jsxRuntime.jsx("img", { src: URL.createObjectURL(file), alt: `Attached ${index + 1}`, className: "w-full h-full object-cover" })) : (jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: FileHandler.getFileIcon(file) })) }), jsxRuntime.jsx("button", { onClick: () => removeFile(index), className: "absolute -top-2 -right-2 w-5 h-5 bg-red-500 text-white rounded-full flex items-center justify-center text-xs hover:bg-red-600 transition-colors", title: "Remove file", children: "\u00D7" }), jsxRuntime.jsx("div", { className: "absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-full opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-10 mt-2", children: jsxRuntime.jsxs("div", { className: "bg-gray-900 text-white text-xs rounded-lg px-2 py-1 whitespace-nowrap shadow-lg", children: [jsxRuntime.jsx("div", { className: "font-medium truncate max-w-32", title: file.name, children: file.name }), jsxRuntime.jsxs("div", { className: "text-gray-300", children: [FileHandler.getFileTypeDescription(file), " \u2022", ' ', FileHandler.formatFileSize(file.size)] }), jsxRuntime.jsx("div", { className: "absolute -top-1 left-1/2 transform -translate-x-1/2 w-2 h-2 bg-gray-900 rotate-45" })] }) })] }, index))) })), incompatibleFiles.length > 0 && (jsxRuntime.jsxs("div", { className: "relative flex flex-col gap-1 text-sm text-orange-700 bg-orange-50 border border-orange-200 rounded px-3 py-2 mb-2", children: [jsxRuntime.jsx("button", { onClick: () => setIncompatibleFiles([]), className: "absolute top-1 right-1 w-5 h-5 flex items-center justify-center text-orange-500 hover:text-orange-700 hover:bg-orange-200 rounded-full transition-colors", title: "Dismiss", type: "button", children: "\u00D7" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 pr-6", children: [jsxRuntime.jsx("span", { children: "\u26A0\uFE0F" }), jsxRuntime.jsx("span", { className: "font-medium", children: incompatibleFiles.length === 1
3306
3327
  ? 'File not supported'
3307
- : 'Some files not supported' })] }), jsxRuntime.jsxs("div", { className: "text-xs", children: [jsxRuntime.jsx("div", { className: "mb-1", children: "These files were skipped:" }), jsxRuntime.jsxs("ul", { className: "list-disc list-inside ml-2 space-y-0.5", children: [incompatibleFiles.slice(0, 3).map((fileName, index) => (jsxRuntime.jsx("li", { className: "truncate", children: fileName }, index))), incompatibleFiles.length > 3 && (jsxRuntime.jsxs("li", { children: ["... and ", incompatibleFiles.length - 3, " more"] }))] }), jsxRuntime.jsxs("div", { className: "mt-1 text-orange-600", children: ["\uD83D\uDCA1 Only ", FileHandler.getAcceptedTypesDescription(), " are supported", incompatibleFiles.some((name) => name.includes('images not supported by current model')) && (jsxRuntime.jsxs("div", { className: "mt-1", children: ["\uD83D\uDD04 ", jsxRuntime.jsx("strong", { children: "Switch to GPT-4O, Claude 3, or Gemini" }), " to enable image uploads"] }))] })] })] })), jsxRuntime.jsxs("div", { className: `relative flex flex-col gap-1 rounded border px-2 pt-1 pb-1 cursor-text transition-all duration-200 border-2 focus-within:shadow-sm ${isDragOver ? 'border-dashed bg-opacity-20' : 'border-transparent'}`, style: {
3328
+ : 'Some files not supported' })] }), jsxRuntime.jsxs("div", { className: "text-xs", children: [jsxRuntime.jsx("div", { className: "mb-1", children: "These files were skipped:" }), jsxRuntime.jsxs("ul", { className: "list-disc list-inside ml-2 space-y-0.5", children: [incompatibleFiles.slice(0, 3).map((fileName, index) => (jsxRuntime.jsx("li", { className: "truncate", children: fileName }, index))), incompatibleFiles.length > 3 && (jsxRuntime.jsxs("li", { children: ["... and ", incompatibleFiles.length - 3, " more"] }))] }), jsxRuntime.jsxs("div", { className: "mt-1 text-orange-600", children: ["\uD83D\uDCA1 Only ", FileHandler.getAcceptedTypesDescription(), " are supported", incompatibleFiles.some((name) => name.includes('images not supported by current model')) && (jsxRuntime.jsx("div", { className: "mt-1", children: "\uD83D\uDD04 Switch to a model showing the image icon in the model switcher to enable image uploads" }))] })] })] })), jsxRuntime.jsxs("div", { className: `relative flex flex-col gap-1 rounded border px-2 pt-1 pb-1 cursor-text transition-all duration-200 border-2 focus-within:shadow-sm ${isDragOver ? 'border-dashed bg-opacity-20' : 'border-transparent'}`, style: {
3308
3329
  backgroundColor: theme.colors.surface.primary,
3309
3330
  borderColor: isDragOver
3310
3331
  ? theme.colors.primary[400]
@@ -3331,7 +3352,7 @@ theme, isOpen, }) {
3331
3352
  e.currentTarget.style.color = theme.colors.text.tertiary;
3332
3353
  }, title: imageSupported
3333
3354
  ? 'Attach images - AI can analyze image content'
3334
- : "Current model doesn't support images. Switch to GPT-4O, Claude 3, or Gemini to use image attachments.", tabIndex: -1, type: "button", onClick: () => imageSupported && imageInputRef.current?.click(), disabled: !imageSupported, children: jsxRuntime.jsx(Icon, { name: "image", size: "sm" }) })), jsxRuntime.jsx("button", { className: "p-1 cursor-pointer disabled:opacity-50 disabled:cursor-default transition-colors", style: { color: theme.colors.text.tertiary }, title: "Record audio (coming soon)", tabIndex: -1, type: "button", onClick: handleAudioRecord, disabled: true, children: jsxRuntime.jsx(Icon, { name: "microphone", size: "sm" }) })] }), jsxRuntime.jsx("button", { onClick: () => onSend(), disabled: (!query.trim() && attachedFiles.length === 0) || isLoading, className: "rounded-full transition disabled:opacity-50 p-2", style: {
3355
+ : "Current model doesn't support images. Switch to GPT-4O, Claude, or Gemini to use image attachments.", tabIndex: -1, type: "button", onClick: () => imageSupported && imageInputRef.current?.click(), disabled: !imageSupported, children: jsxRuntime.jsx(Icon, { name: "image", size: "sm" }) })), jsxRuntime.jsx("button", { className: "p-1 cursor-pointer disabled:opacity-50 disabled:cursor-default transition-colors", style: { color: theme.colors.text.tertiary }, title: "Record audio (coming soon)", tabIndex: -1, type: "button", onClick: handleAudioRecord, disabled: true, children: jsxRuntime.jsx(Icon, { name: "microphone", size: "sm" }) })] }), jsxRuntime.jsx("button", { onClick: () => onSend(), disabled: (!query.trim() && attachedFiles.length === 0) || isLoading, className: "rounded-full transition disabled:opacity-50 p-2", style: {
3335
3356
  color: theme.colors.text.primary,
3336
3357
  }, onMouseEnter: (e) => {
3337
3358
  if (!e.currentTarget.disabled) {
@@ -3346,7 +3367,7 @@ theme, isOpen, }) {
3346
3367
  features.imageAnalysis !== false &&
3347
3368
  features.enableImageUploads !== false &&
3348
3369
  !imageSupported &&
3349
- !imageTipDismissed && (jsxRuntime.jsxs("div", { className: "relative px-2 py-1 bg-amber-50 border border-amber-200 rounded text-amber-700 text-xs flex items-center gap-2", children: [jsxRuntime.jsx(Icon, { name: "image", size: "xs", className: "opacity-50" }), jsxRuntime.jsxs("span", { className: "flex-1 pr-4", children: ["\uD83D\uDCA1 ", jsxRuntime.jsx("strong", { children: "Tip:" }), " Switch to GPT-4O, Claude 3, or Gemini in the model switcher below to enable image attachments"] }), jsxRuntime.jsx("button", { onClick: () => setImageTipDismissed(true), className: "absolute top-1 right-1 w-4 h-4 flex items-center justify-center text-amber-500 hover:text-amber-700 hover:bg-amber-200 rounded-full transition-colors text-xs font-bold", title: "Dismiss tip", type: "button", children: "\u00D7" })] })), jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs px-0 pt-0", style: { color: theme.colors.text.tertiary }, children: [jsxRuntime.jsx("div", { className: "flex gap-4", children: features.modelSwitching !== false && (jsxRuntime.jsx(ModelSwitcher, { models: models, defaultModel: defaultModel, showUsageStats: features.usageTracking !== false && showUsageStats !== false, theme: theme, onModelSelectionChange: modelSelectionHandler })) }), jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: jsxRuntime.jsxs("span", { children: [jsxRuntime.jsx("kbd", { className: "px-1 rounded", style: {
3370
+ !imageTipDismissed && (jsxRuntime.jsxs("div", { className: "relative px-2 py-1 bg-amber-50 border border-amber-200 rounded text-amber-700 text-xs flex items-center gap-2", children: [jsxRuntime.jsx(Icon, { name: "image", size: "xs", className: "opacity-50" }), jsxRuntime.jsxs("span", { className: "flex-1 pr-4", children: ["\uD83D\uDCA1 ", jsxRuntime.jsx("strong", { children: "Tip:" }), " Switch to a model showing the image icon in the model switcher below to enable image uploads"] }), jsxRuntime.jsx("button", { onClick: () => setImageTipDismissed(true), className: "absolute top-1 right-1 w-4 h-4 flex items-center justify-center text-amber-500 hover:text-amber-700 hover:bg-amber-200 rounded-full transition-colors text-xs font-bold", title: "Dismiss tip", type: "button", children: "\u00D7" })] })), jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs px-0 pt-0", style: { color: theme.colors.text.tertiary }, children: [jsxRuntime.jsx("div", { className: "flex gap-4", children: features.modelSwitching !== false && (jsxRuntime.jsx(ModelSwitcher, { models: models, defaultModel: defaultModel, showUsageStats: features.usageTracking !== false && showUsageStats !== false, theme: theme, onModelSelectionChange: modelSelectionHandler })) }), jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: jsxRuntime.jsxs("span", { children: [jsxRuntime.jsx("kbd", { className: "px-1 rounded", style: {
3350
3371
  backgroundColor: theme.colors.surface.secondary,
3351
3372
  color: theme.colors.text.secondary,
3352
3373
  }, children: "\u23CE" }), ' ', "to send,", ' ', jsxRuntime.jsx("kbd", { className: "px-1 rounded", style: {
@@ -24,6 +24,7 @@ export declare class ContextualCommandService {
24
24
  private buildContextualSystemPrompt;
25
25
  /**
26
26
  * Format schema fields into readable descriptions
27
+ * Excludes client-generated fields from the output
27
28
  */
28
29
  private formatSchemaFields;
29
30
  /**
@@ -1,4 +1,5 @@
1
1
  import type { ModelCapabilities } from '../types/types';
2
+ import type { ServerConfig } from '../types/types';
2
3
  /**
3
4
  * Hook to resolve tier-based default model config to actual model
4
5
  *
@@ -8,13 +9,14 @@ import type { ModelCapabilities } from '../types/types';
8
9
  * match for the provider and tier combination.
9
10
  *
10
11
  * @param defaultModel - The model configuration to resolve
12
+ * @param serverConfig - Optional server config to build the models endpoint URL
11
13
  * @returns The resolved model with provider, model name, and capabilities
12
14
  */
13
15
  export declare function useResolvedDefaultModel(defaultModel?: {
14
16
  provider: string;
15
17
  model?: string;
16
18
  tier?: 'fast' | 'balanced' | 'powerful';
17
- }): {
19
+ }, serverConfig?: ServerConfig): {
18
20
  provider: string;
19
21
  model: string;
20
22
  capabilities: ModelCapabilities;
@@ -8231,6 +8231,10 @@ function sanitizeFormData(formData, schema) {
8231
8231
  // If schema is provided, use it for validation
8232
8232
  if (schema && schema.properties && schema.properties[key]) {
8233
8233
  const fieldSchema = schema.properties[key];
8234
+ // Skip client-generated fields - they should not be included in formState
8235
+ if (fieldSchema.skip) {
8236
+ return; // Skip this field
8237
+ }
8234
8238
  sanitized[key] = validateAndConvertField(value, fieldSchema);
8235
8239
  }
8236
8240
  else {
@@ -13486,6 +13490,7 @@ ${allRoutesInfo}
13486
13490
  - Match exact field names and types from the chosen schema
13487
13491
  - Respect required vs optional fields
13488
13492
  - If a field has enum values, only use those exact values
13493
+ - **DO NOT generate fields marked with skip: true** - these are auto-generated on the client side and should never appear in your suggestions
13489
13494
 
13490
13495
  **🚨 CRITICAL CURRENCY FORMATTING:**
13491
13496
  - ALL currency amounts (amount, buyRate, sellRate) MUST be NUMBERS in CENTS
@@ -13565,6 +13570,7 @@ ${this.formatSchemaFields(singleSchema)}${routesInfo}
13565
13570
  - For nested objects/arrays, follow the nested schema structure
13566
13571
  - If a field has enum values, only use those exact values
13567
13572
  - When suggesting navigation, use the routes defined in the schema above
13573
+ - **DO NOT generate fields marked with skip: true** - these are auto-generated on the client side and should never appear in your suggestions
13568
13574
 
13569
13575
  **🚨 CRITICAL CURRENCY FORMATTING:**
13570
13576
  - ALL currency amounts (amount, buyRate, sellRate) MUST be NUMBERS in CENTS
@@ -13589,10 +13595,17 @@ ${additionalContext}
13589
13595
  }
13590
13596
  /**
13591
13597
  * Format schema fields into readable descriptions
13598
+ * Excludes client-generated fields from the output
13592
13599
  */
13593
13600
  formatSchemaFields(schema) {
13594
13601
  const fields = [];
13602
+ const clientGeneratedFields = [];
13595
13603
  for (const [fieldName, fieldDef] of Object.entries(schema.properties || {})) {
13604
+ // Skip client-generated fields - they should not be shown to AI
13605
+ if (typeof fieldDef === 'object' && fieldDef.skip) {
13606
+ clientGeneratedFields.push(fieldName);
13607
+ continue;
13608
+ }
13596
13609
  const required = schema.required?.includes(fieldName) ? '**REQUIRED**' : 'optional';
13597
13610
  const type = typeof fieldDef === 'object' ? fieldDef.type || 'unknown' : 'unknown';
13598
13611
  const description = typeof fieldDef === 'object' ? fieldDef.description || '' : '';
@@ -13601,6 +13614,10 @@ ${additionalContext}
13601
13614
  : '';
13602
13615
  fields.push(`- **${fieldName}** (${type}) ${required}${enumValues}${description ? `: ${description}` : ''}`);
13603
13616
  }
13617
+ // Add note about client-generated fields if any exist
13618
+ if (clientGeneratedFields.length > 0) {
13619
+ fields.push(`\n**NOTE:** The following fields are auto-generated on the client side and should NOT be included in your suggestions: ${clientGeneratedFields.join(', ')}`);
13620
+ }
13604
13621
  return fields.join('\n');
13605
13622
  }
13606
13623
  /**
@@ -8251,6 +8251,10 @@ function sanitizeFormData(formData, schema) {
8251
8251
  // If schema is provided, use it for validation
8252
8252
  if (schema && schema.properties && schema.properties[key]) {
8253
8253
  const fieldSchema = schema.properties[key];
8254
+ // Skip client-generated fields - they should not be included in formState
8255
+ if (fieldSchema.skip) {
8256
+ return; // Skip this field
8257
+ }
8254
8258
  sanitized[key] = validateAndConvertField(value, fieldSchema);
8255
8259
  }
8256
8260
  else {
@@ -13506,6 +13510,7 @@ ${allRoutesInfo}
13506
13510
  - Match exact field names and types from the chosen schema
13507
13511
  - Respect required vs optional fields
13508
13512
  - If a field has enum values, only use those exact values
13513
+ - **DO NOT generate fields marked with skip: true** - these are auto-generated on the client side and should never appear in your suggestions
13509
13514
 
13510
13515
  **🚨 CRITICAL CURRENCY FORMATTING:**
13511
13516
  - ALL currency amounts (amount, buyRate, sellRate) MUST be NUMBERS in CENTS
@@ -13585,6 +13590,7 @@ ${this.formatSchemaFields(singleSchema)}${routesInfo}
13585
13590
  - For nested objects/arrays, follow the nested schema structure
13586
13591
  - If a field has enum values, only use those exact values
13587
13592
  - When suggesting navigation, use the routes defined in the schema above
13593
+ - **DO NOT generate fields marked with skip: true** - these are auto-generated on the client side and should never appear in your suggestions
13588
13594
 
13589
13595
  **🚨 CRITICAL CURRENCY FORMATTING:**
13590
13596
  - ALL currency amounts (amount, buyRate, sellRate) MUST be NUMBERS in CENTS
@@ -13609,10 +13615,17 @@ ${additionalContext}
13609
13615
  }
13610
13616
  /**
13611
13617
  * Format schema fields into readable descriptions
13618
+ * Excludes client-generated fields from the output
13612
13619
  */
13613
13620
  formatSchemaFields(schema) {
13614
13621
  const fields = [];
13622
+ const clientGeneratedFields = [];
13615
13623
  for (const [fieldName, fieldDef] of Object.entries(schema.properties || {})) {
13624
+ // Skip client-generated fields - they should not be shown to AI
13625
+ if (typeof fieldDef === 'object' && fieldDef.skip) {
13626
+ clientGeneratedFields.push(fieldName);
13627
+ continue;
13628
+ }
13616
13629
  const required = schema.required?.includes(fieldName) ? '**REQUIRED**' : 'optional';
13617
13630
  const type = typeof fieldDef === 'object' ? fieldDef.type || 'unknown' : 'unknown';
13618
13631
  const description = typeof fieldDef === 'object' ? fieldDef.description || '' : '';
@@ -13621,6 +13634,10 @@ ${additionalContext}
13621
13634
  : '';
13622
13635
  fields.push(`- **${fieldName}** (${type}) ${required}${enumValues}${description ? `: ${description}` : ''}`);
13623
13636
  }
13637
+ // Add note about client-generated fields if any exist
13638
+ if (clientGeneratedFields.length > 0) {
13639
+ fields.push(`\n**NOTE:** The following fields are auto-generated on the client side and should NOT be included in your suggestions: ${clientGeneratedFields.join(', ')}`);
13640
+ }
13624
13641
  return fields.join('\n');
13625
13642
  }
13626
13643
  /**
@@ -24,6 +24,7 @@ export declare class ContextualCommandService {
24
24
  private buildContextualSystemPrompt;
25
25
  /**
26
26
  * Format schema fields into readable descriptions
27
+ * Excludes client-generated fields from the output
27
28
  */
28
29
  private formatSchemaFields;
29
30
  /**
@@ -134,6 +134,24 @@ export interface ServerConfig {
134
134
  additionalContext?: string;
135
135
  stayOnPage?: boolean;
136
136
  }
137
+ export interface ClientConfig {
138
+ models?: Record<string, ModelInfo[]>;
139
+ defaultModel?: {
140
+ provider: string;
141
+ model?: string;
142
+ tier?: 'fast' | 'balanced' | 'powerful';
143
+ };
144
+ showUsageStats?: boolean;
145
+ maxFileSize?: string;
146
+ features?: {
147
+ modelSwitching?: boolean;
148
+ usageTracking?: boolean;
149
+ imageAnalysis?: boolean;
150
+ fileUploads?: boolean;
151
+ enableImageUploads?: boolean;
152
+ };
153
+ schemas?: Record<string, SchemaDefinition>;
154
+ }
137
155
  export interface TrainingDataPaths {
138
156
  components: string[];
139
157
  schemas: string[];
@@ -465,6 +483,8 @@ export interface SchemaProperty {
465
483
  maxLength?: number;
466
484
  minimum?: number;
467
485
  maximum?: number;
486
+ /** If true, this field is not to be generated by AI (e.g. auto-generated fields) */
487
+ skip?: boolean;
468
488
  }
469
489
  /**
470
490
  * Simplified config for ContextualCommandService
@@ -134,6 +134,24 @@ export interface ServerConfig {
134
134
  additionalContext?: string;
135
135
  stayOnPage?: boolean;
136
136
  }
137
+ export interface ClientConfig {
138
+ models?: Record<string, ModelInfo[]>;
139
+ defaultModel?: {
140
+ provider: string;
141
+ model?: string;
142
+ tier?: 'fast' | 'balanced' | 'powerful';
143
+ };
144
+ showUsageStats?: boolean;
145
+ maxFileSize?: string;
146
+ features?: {
147
+ modelSwitching?: boolean;
148
+ usageTracking?: boolean;
149
+ imageAnalysis?: boolean;
150
+ fileUploads?: boolean;
151
+ enableImageUploads?: boolean;
152
+ };
153
+ schemas?: Record<string, SchemaDefinition>;
154
+ }
137
155
  export interface TrainingDataPaths {
138
156
  components: string[];
139
157
  schemas: string[];
@@ -465,6 +483,8 @@ export interface SchemaProperty {
465
483
  maxLength?: number;
466
484
  minimum?: number;
467
485
  maximum?: number;
486
+ /** If true, this field is not to be generated by AI (e.g. auto-generated fields) */
487
+ skip?: boolean;
468
488
  }
469
489
  /**
470
490
  * Simplified config for ContextualCommandService
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchid-ai",
3
- "version": "1.2.7",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "AI-powered command processing and chat interface for React applications",
6
6
  "main": "dist/index.js",