@probelabs/probe-chat 0.6.0-rc102 → 0.6.0-rc103
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.
- package/LOCAL_IMAGE_SUPPORT.md +195 -0
- package/package.json +2 -1
- package/probeChat.js +146 -15
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Local Image Support in Probe Agent
|
|
2
|
+
|
|
3
|
+
The probe agent now supports reading local image files directly from file paths mentioned in user messages and **automatically loads images when mentioned during the agentic loop**.
|
|
4
|
+
|
|
5
|
+
## Features Added
|
|
6
|
+
|
|
7
|
+
### Automatic Local File Detection
|
|
8
|
+
- Detects local image file paths in user messages
|
|
9
|
+
- Supports both relative and absolute paths
|
|
10
|
+
- Recognizes common image extensions: `.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`, `.bmp`, `.svg`
|
|
11
|
+
|
|
12
|
+
### 🚀 NEW: Agentic Loop Image Loading
|
|
13
|
+
- **Automatic detection**: Agent automatically detects when it mentions image files in its internal thinking
|
|
14
|
+
- **Smart loading**: Images are loaded and added to the AI context for subsequent iterations
|
|
15
|
+
- **Persistent context**: Loaded images remain available throughout the conversation
|
|
16
|
+
- **Tool result processing**: Images mentioned in tool outputs are also automatically loaded
|
|
17
|
+
- **Caching**: Prevents reloading the same images multiple times
|
|
18
|
+
|
|
19
|
+
### Security Features
|
|
20
|
+
- Path validation to prevent directory traversal attacks
|
|
21
|
+
- Restricts file access to allowed directories (respects `ALLOWED_FOLDERS` environment variable)
|
|
22
|
+
- Validates file existence and readability before processing
|
|
23
|
+
|
|
24
|
+
### Supported Path Formats
|
|
25
|
+
```
|
|
26
|
+
./image.png # Relative path from current directory
|
|
27
|
+
../assets/screenshot.jpg # Relative path with directory traversal
|
|
28
|
+
/absolute/path/to/image.webp # Absolute path
|
|
29
|
+
image.gif # File in current directory
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Automatic Conversion
|
|
33
|
+
- Local files are automatically converted to base64 data URLs
|
|
34
|
+
- Maintains original MIME type based on file extension
|
|
35
|
+
- Seamlessly integrates with existing URL and base64 image support
|
|
36
|
+
|
|
37
|
+
## Usage Examples
|
|
38
|
+
|
|
39
|
+
### Basic Usage
|
|
40
|
+
```javascript
|
|
41
|
+
import { ProbeChat } from './probeChat.js';
|
|
42
|
+
|
|
43
|
+
const chat = new ProbeChat({ debug: true });
|
|
44
|
+
|
|
45
|
+
// The agent will automatically detect and process the local image
|
|
46
|
+
const response = await chat.chat('Analyze this screenshot: ./screenshot.png');
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Mixed Content
|
|
50
|
+
```javascript
|
|
51
|
+
// Mix local files with URLs
|
|
52
|
+
const message = `
|
|
53
|
+
Compare this local image ./local.png
|
|
54
|
+
with this remote image https://example.com/remote.jpg
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const response = await chat.chat(message);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Direct Function Usage
|
|
61
|
+
```javascript
|
|
62
|
+
import { extractImageUrls } from './probeChat.js';
|
|
63
|
+
|
|
64
|
+
const message = 'Please review this diagram: ./architecture.png';
|
|
65
|
+
const result = await extractImageUrls(message, true);
|
|
66
|
+
|
|
67
|
+
console.log(`Found ${result.urls.length} images`);
|
|
68
|
+
console.log(`Cleaned message: "${result.cleanedMessage}"`);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 🤖 Agentic Loop Integration
|
|
72
|
+
|
|
73
|
+
The most powerful feature is automatic image loading during the agent's internal reasoning process.
|
|
74
|
+
|
|
75
|
+
### How It Works
|
|
76
|
+
|
|
77
|
+
When the probe agent is working through a task, it can now:
|
|
78
|
+
|
|
79
|
+
1. **Mention an image file** in its reasoning: "I need to check ./screenshot.png"
|
|
80
|
+
2. **Automatically load the image** before the next AI iteration
|
|
81
|
+
3. **Use visual context** for enhanced analysis and problem-solving
|
|
82
|
+
|
|
83
|
+
### Agentic Flow Example
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
👤 USER: "Analyze the system architecture"
|
|
87
|
+
|
|
88
|
+
🤖 AGENT: "Let me search for architecture documentation..."
|
|
89
|
+
🔍 Tool: search "architecture design"
|
|
90
|
+
📊 Result: "Found ./docs/system-diagram.png"
|
|
91
|
+
|
|
92
|
+
🤖 AGENT: "I found a system diagram at ./docs/system-diagram.png. Let me analyze it."
|
|
93
|
+
🖼️ AUTO: Image ./docs/system-diagram.png loaded into context
|
|
94
|
+
|
|
95
|
+
🤖 AGENT: "Based on the diagram I can see..."
|
|
96
|
+
💭 AI now has visual access to the diagram and can analyze it
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Trigger Patterns
|
|
100
|
+
|
|
101
|
+
The agent automatically loads images when it mentions:
|
|
102
|
+
|
|
103
|
+
- **Direct paths**: `./screenshot.png`, `/path/to/image.jpg`
|
|
104
|
+
- **Contextual references**: "the file diagram.png shows", "looking at chart.gif"
|
|
105
|
+
- **Tool results**: When tools return paths to image files
|
|
106
|
+
- **Generated content**: "saved visualization as ./output.png"
|
|
107
|
+
|
|
108
|
+
### Benefits
|
|
109
|
+
|
|
110
|
+
- **🧠 Enhanced reasoning**: Agent gains visual understanding of referenced images
|
|
111
|
+
- **🔄 Seamless workflow**: No manual image loading required
|
|
112
|
+
- **⚡ Performance**: Intelligent caching prevents reloading
|
|
113
|
+
- **🔒 Security**: Same security validations as manual loading
|
|
114
|
+
- **📱 Persistence**: Images remain available throughout the conversation
|
|
115
|
+
|
|
116
|
+
## Security Considerations
|
|
117
|
+
|
|
118
|
+
### Path Restrictions
|
|
119
|
+
- Files must be within the allowed directory structure
|
|
120
|
+
- Prevents access to system files (e.g., `/etc/passwd`)
|
|
121
|
+
- Respects the `ALLOWED_FOLDERS` environment variable
|
|
122
|
+
|
|
123
|
+
### File Validation
|
|
124
|
+
- Verifies file existence before attempting to read
|
|
125
|
+
- Validates file extensions against supported image formats
|
|
126
|
+
- Handles file reading errors gracefully
|
|
127
|
+
|
|
128
|
+
### Error Handling
|
|
129
|
+
- Failed file reads are logged but don't interrupt processing
|
|
130
|
+
- Invalid paths are silently ignored
|
|
131
|
+
- Maintains functionality for valid images even if some fail
|
|
132
|
+
|
|
133
|
+
## Implementation Details
|
|
134
|
+
|
|
135
|
+
### Pattern Matching
|
|
136
|
+
The system uses an enhanced regex pattern to detect:
|
|
137
|
+
```javascript
|
|
138
|
+
/(?:data:image\/[a-zA-Z]*;base64,[A-Za-z0-9+/=]+|https?:\/\/(?:(?:private-user-images\.githubusercontent\.com|github\.com\/user-attachments\/assets)\/[^\s"'<>]+|[^\s"'<>]+\.(?:png|jpg|jpeg|webp|gif)(?:\?[^\s"'<>]*)?)|(?:\.?\.?\/)?[^\s"'<>]*\.(?:png|jpg|jpeg|webp|gif))/gi
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Processing Pipeline
|
|
142
|
+
1. **Pattern Detection** - Find all potential image references in text
|
|
143
|
+
2. **Classification** - Distinguish between URLs, base64 data, and local paths
|
|
144
|
+
3. **Validation** - Verify local file paths for security and existence
|
|
145
|
+
4. **Conversion** - Read local files and convert to base64 data URLs
|
|
146
|
+
5. **Integration** - Pass processed images to AI models
|
|
147
|
+
|
|
148
|
+
### File Size Limitations
|
|
149
|
+
- No explicit file size limits implemented
|
|
150
|
+
- Memory usage scales with image size
|
|
151
|
+
- Large images may impact performance
|
|
152
|
+
|
|
153
|
+
## Testing
|
|
154
|
+
|
|
155
|
+
Run the test suite to verify functionality:
|
|
156
|
+
```bash
|
|
157
|
+
cd examples/chat
|
|
158
|
+
node test-local-image-reading.js
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The test covers:
|
|
162
|
+
- Basic local file detection and conversion
|
|
163
|
+
- Mixed URL and local file processing
|
|
164
|
+
- Relative path handling
|
|
165
|
+
- Security validation
|
|
166
|
+
- Error handling for missing files
|
|
167
|
+
|
|
168
|
+
## Backward Compatibility
|
|
169
|
+
|
|
170
|
+
This enhancement is fully backward compatible:
|
|
171
|
+
- Existing URL-based image handling unchanged
|
|
172
|
+
- Base64 data URL support maintained
|
|
173
|
+
- No breaking changes to existing APIs
|
|
174
|
+
|
|
175
|
+
## Environment Configuration
|
|
176
|
+
|
|
177
|
+
Set allowed folders to restrict file access:
|
|
178
|
+
```bash
|
|
179
|
+
export ALLOWED_FOLDERS="/path/to/project,/path/to/assets"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
If no `ALLOWED_FOLDERS` is set, defaults to current working directory.
|
|
183
|
+
|
|
184
|
+
## Error Handling
|
|
185
|
+
|
|
186
|
+
The system gracefully handles various error conditions:
|
|
187
|
+
- **File not found**: Logged and ignored
|
|
188
|
+
- **Permission denied**: Logged and ignored
|
|
189
|
+
- **Invalid format**: Logged and ignored
|
|
190
|
+
- **Path traversal attempts**: Blocked by security validation
|
|
191
|
+
|
|
192
|
+
Enable debug mode to see detailed logging:
|
|
193
|
+
```javascript
|
|
194
|
+
const chat = new ProbeChat({ debug: true });
|
|
195
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probelabs/probe-chat",
|
|
3
|
-
"version": "0.6.0-
|
|
3
|
+
"version": "0.6.0-rc103",
|
|
4
4
|
"description": "CLI and web interface for Probe code search (formerly @probelabs/probe-web and @probelabs/probe-chat)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
"logo.png",
|
|
97
97
|
"README.md",
|
|
98
98
|
"TRACING.md",
|
|
99
|
+
"LOCAL_IMAGE_SUPPORT.md",
|
|
99
100
|
"LICENSE"
|
|
100
101
|
]
|
|
101
102
|
}
|
package/probeChat.js
CHANGED
|
@@ -2,7 +2,8 @@ import 'dotenv/config';
|
|
|
2
2
|
import { ProbeAgent } from '@probelabs/probe/agent';
|
|
3
3
|
import { TokenUsageDisplay } from './tokenUsageDisplay.js';
|
|
4
4
|
import { writeFileSync, existsSync } from 'fs';
|
|
5
|
-
import {
|
|
5
|
+
import { readFile, stat } from 'fs/promises';
|
|
6
|
+
import { join, resolve, isAbsolute } from 'path';
|
|
6
7
|
import { TelemetryConfig } from './telemetry.js';
|
|
7
8
|
import { trace } from '@opentelemetry/api';
|
|
8
9
|
import { appTracer } from './appTracer.js';
|
|
@@ -39,24 +40,128 @@ if (typeof process !== 'undefined' && !process.env.PROBE_CHAT_SKIP_FOLDER_VALIDA
|
|
|
39
40
|
validateFolders();
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
// Maximum image file size (20MB) to prevent OOM attacks
|
|
44
|
+
const MAX_IMAGE_FILE_SIZE = 20 * 1024 * 1024;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Security validation for local file paths
|
|
48
|
+
* @param {string} filePath - The file path to validate
|
|
49
|
+
* @param {string} baseDir - The base directory to restrict access to
|
|
50
|
+
* @returns {boolean} - Whether the path is safe to access
|
|
51
|
+
*/
|
|
52
|
+
function isSecureFilePath(filePath, baseDir = process.cwd()) {
|
|
53
|
+
try {
|
|
54
|
+
// Resolve the absolute path
|
|
55
|
+
const absolutePath = isAbsolute(filePath) ? filePath : resolve(baseDir, filePath);
|
|
56
|
+
const normalizedBase = resolve(baseDir);
|
|
57
|
+
|
|
58
|
+
// Ensure the resolved path is within the allowed directory
|
|
59
|
+
return absolutePath.startsWith(normalizedBase);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
42
65
|
/**
|
|
43
|
-
*
|
|
66
|
+
* Convert local image file to base64 data URL
|
|
67
|
+
* @param {string} filePath - Path to the image file
|
|
68
|
+
* @param {boolean} debug - Whether to log debug information
|
|
69
|
+
* @returns {Promise<string|null>} - Base64 data URL or null if failed
|
|
70
|
+
*/
|
|
71
|
+
async function convertImageFileToBase64(filePath, debug = false) {
|
|
72
|
+
try {
|
|
73
|
+
// Security check: validate the file path against all allowed directories
|
|
74
|
+
const allowedDirs = allowedFolders.length > 0 ? allowedFolders : [process.cwd()];
|
|
75
|
+
const isPathAllowed = allowedDirs.some(dir => isSecureFilePath(filePath, dir));
|
|
76
|
+
|
|
77
|
+
if (!isPathAllowed) {
|
|
78
|
+
if (debug) {
|
|
79
|
+
console.log(`[DEBUG] Security check failed for path: ${filePath}`);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Resolve the path - for relative paths, use the first allowed directory as base
|
|
85
|
+
const baseDir = allowedDirs[0];
|
|
86
|
+
const absolutePath = isAbsolute(filePath) ? filePath : resolve(baseDir, filePath);
|
|
87
|
+
|
|
88
|
+
// Check if file exists and get file stats
|
|
89
|
+
let fileStats;
|
|
90
|
+
try {
|
|
91
|
+
fileStats = await stat(absolutePath);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (debug) {
|
|
94
|
+
console.log(`[DEBUG] File not found: ${absolutePath}`);
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Validate file size to prevent OOM attacks
|
|
100
|
+
if (fileStats.size > MAX_IMAGE_FILE_SIZE) {
|
|
101
|
+
if (debug) {
|
|
102
|
+
console.log(`[DEBUG] Image file too large: ${absolutePath} (${fileStats.size} bytes, max: ${MAX_IMAGE_FILE_SIZE})`);
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Determine MIME type based on file extension
|
|
108
|
+
const extension = absolutePath.toLowerCase().split('.').pop();
|
|
109
|
+
const mimeTypes = {
|
|
110
|
+
'png': 'image/png',
|
|
111
|
+
'jpg': 'image/jpeg',
|
|
112
|
+
'jpeg': 'image/jpeg',
|
|
113
|
+
'webp': 'image/webp',
|
|
114
|
+
'gif': 'image/gif'
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const mimeType = mimeTypes[extension];
|
|
118
|
+
if (!mimeType) {
|
|
119
|
+
if (debug) {
|
|
120
|
+
console.log(`[DEBUG] Unsupported image format: ${extension}`);
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Read file and convert to base64 asynchronously
|
|
126
|
+
const fileBuffer = await readFile(absolutePath);
|
|
127
|
+
const base64Data = fileBuffer.toString('base64');
|
|
128
|
+
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
129
|
+
|
|
130
|
+
if (debug) {
|
|
131
|
+
console.log(`[DEBUG] Successfully converted ${absolutePath} to base64 (${fileBuffer.length} bytes)`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return dataUrl;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (debug) {
|
|
137
|
+
console.log(`[DEBUG] Error converting file to base64: ${error.message}`);
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Export the extractImageUrls function for testing
|
|
144
|
+
export { extractImageUrls };
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Extract image URLs and local file paths from message text
|
|
44
148
|
* @param {string} message - The message text to analyze
|
|
45
149
|
* @param {boolean} debug - Whether to log debug information
|
|
46
|
-
* @returns {
|
|
150
|
+
* @returns {Promise<Object>} Promise resolving to { urls: Array, cleanedMessage: string }
|
|
47
151
|
*/
|
|
48
|
-
function extractImageUrls(message, debug = false) {
|
|
152
|
+
async function extractImageUrls(message, debug = false) {
|
|
49
153
|
// This function should be called within the session context, so it will inherit the trace ID
|
|
50
154
|
const tracer = trace.getTracer('probe-chat', '1.0.0');
|
|
51
|
-
return tracer.startActiveSpan('content.image.extract', (span) => {
|
|
155
|
+
return tracer.startActiveSpan('content.image.extract', async (span) => {
|
|
52
156
|
try {
|
|
53
|
-
// Pattern to match image URLs and
|
|
157
|
+
// Pattern to match image URLs, base64 data, and local file paths:
|
|
54
158
|
// 1. GitHub private-user-images URLs (always images, regardless of extension)
|
|
55
159
|
// 2. GitHub user-attachments/assets URLs (always images, regardless of extension)
|
|
56
160
|
// 3. URLs with common image extensions (PNG, JPG, JPEG, WebP, GIF)
|
|
57
161
|
// 4. Base64 data URLs (data:image/...)
|
|
162
|
+
// 5. Local file paths with image extensions (relative and absolute)
|
|
58
163
|
// Updated to stop at quotes, spaces, or common HTML/XML delimiters
|
|
59
|
-
const imageUrlPattern = /(?:data:image\/[a-zA-Z]*;base64,[A-Za-z0-9+/=]+|https?:\/\/(?:(?:private-user-images\.githubusercontent\.com|github\.com\/user-attachments\/assets)\/[^\s"'<>]+|[^\s"'<>]+\.(?:png|jpg|jpeg|webp|gif)(?:\?[^\s"'<>]*)?))/gi;
|
|
164
|
+
const imageUrlPattern = /(?:data:image\/[a-zA-Z]*;base64,[A-Za-z0-9+/=]+|https?:\/\/(?:(?:private-user-images\.githubusercontent\.com|github\.com\/user-attachments\/assets)\/[^\s"'<>]+|[^\s"'<>]+\.(?:png|jpg|jpeg|webp|gif)(?:\?[^\s"'<>]*)?)|(?:\.?\.?\/)?[^\s"'<>]*\.(?:png|jpg|jpeg|webp|gif))/gi;
|
|
60
165
|
|
|
61
166
|
span.setAttributes({
|
|
62
167
|
'message.length': message.length,
|
|
@@ -69,31 +174,57 @@ function extractImageUrls(message, debug = false) {
|
|
|
69
174
|
}
|
|
70
175
|
|
|
71
176
|
const urls = [];
|
|
177
|
+
const foundPatterns = [];
|
|
72
178
|
let match;
|
|
73
179
|
|
|
74
180
|
while ((match = imageUrlPattern.exec(message)) !== null) {
|
|
75
|
-
|
|
181
|
+
foundPatterns.push(match[0]);
|
|
76
182
|
if (debug) {
|
|
77
|
-
console.log(`[DEBUG] Found image
|
|
183
|
+
console.log(`[DEBUG] Found image pattern: ${match[0]}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Process each found pattern - convert local files to base64, keep URLs as-is
|
|
188
|
+
for (const pattern of foundPatterns) {
|
|
189
|
+
// Check if it's already a URL or base64 data
|
|
190
|
+
if (pattern.startsWith('http') || pattern.startsWith('data:image/')) {
|
|
191
|
+
urls.push(pattern);
|
|
192
|
+
if (debug) {
|
|
193
|
+
console.log(`[DEBUG] Using URL/base64 as-is: ${pattern.substring(0, 50)}...`);
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
// It's a local file path - convert to base64
|
|
197
|
+
const base64Data = await convertImageFileToBase64(pattern, debug);
|
|
198
|
+
if (base64Data) {
|
|
199
|
+
urls.push(base64Data);
|
|
200
|
+
if (debug) {
|
|
201
|
+
console.log(`[DEBUG] Converted local file ${pattern} to base64`);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
if (debug) {
|
|
205
|
+
console.log(`[DEBUG] Failed to convert local file: ${pattern}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
78
208
|
}
|
|
79
209
|
}
|
|
80
210
|
|
|
81
|
-
// Clean the message by removing found
|
|
211
|
+
// Clean the message by removing found patterns
|
|
82
212
|
let cleanedMessage = message;
|
|
83
|
-
|
|
84
|
-
cleanedMessage = cleanedMessage.replace(
|
|
213
|
+
foundPatterns.forEach(pattern => {
|
|
214
|
+
cleanedMessage = cleanedMessage.replace(pattern, '').trim();
|
|
85
215
|
});
|
|
86
216
|
|
|
87
217
|
// Clean up any remaining extra whitespace
|
|
88
218
|
cleanedMessage = cleanedMessage.replace(/\s+/g, ' ').trim();
|
|
89
219
|
|
|
90
220
|
span.setAttributes({
|
|
91
|
-
'
|
|
221
|
+
'patterns.found': foundPatterns.length,
|
|
222
|
+
'images.processed': urls.length,
|
|
92
223
|
'message.cleaned_length': cleanedMessage.length
|
|
93
224
|
});
|
|
94
225
|
|
|
95
226
|
if (debug) {
|
|
96
|
-
console.log(`[DEBUG]
|
|
227
|
+
console.log(`[DEBUG] Found ${foundPatterns.length} patterns, processed ${urls.length} images`);
|
|
97
228
|
console.log(`[DEBUG] Cleaned message length: ${cleanedMessage.length}`);
|
|
98
229
|
}
|
|
99
230
|
|
|
@@ -163,7 +294,7 @@ export class ProbeChat {
|
|
|
163
294
|
let cleanedMessage = message;
|
|
164
295
|
|
|
165
296
|
if (!images.length) {
|
|
166
|
-
const extracted = extractImageUrls(message, this.debug);
|
|
297
|
+
const extracted = await extractImageUrls(message, this.debug);
|
|
167
298
|
images = extracted.urls;
|
|
168
299
|
cleanedMessage = extracted.cleanedMessage;
|
|
169
300
|
|