Add HTML fallback and improve bubble rendering in collapsed tool calls
- Create image_html.php as fallback when GD not available - Add test.html page to demonstrate different bubble styles - Update index.js to support rendering bubbles in collapsed tool calls - Improve example.html settings page with new options - Add reference/ to .gitignore 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0e532e81f4
commit
a907c4d073
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,6 +4,9 @@ mounts/
|
|||||||
# Ignore fonts directory
|
# Ignore fonts directory
|
||||||
fonts/
|
fonts/
|
||||||
|
|
||||||
|
# Ignore reference repos
|
||||||
|
reference/
|
||||||
|
|
||||||
# Node modules
|
# Node modules
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sillybubble_block flex-container">
|
<div class="sillybubble_block flex-container">
|
||||||
<label for="sillybubble_image_url">Image Service URL:</label>
|
<label for="sillybubble_image_url">Image Service URL (full URL including https://):</label>
|
||||||
<input id="sillybubble_image_url" type="text" class="text_pole" />
|
<input id="sillybubble_image_url" type="text" class="text_pole" placeholder="https://calista.the.sexiest.cat/image.php" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sillybubble_block flex-container">
|
<div class="sillybubble_block flex-container">
|
||||||
|
114
image_html.php
Normal file
114
image_html.php
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* SillyBubble HTML Fallback
|
||||||
|
*
|
||||||
|
* Creates a chat bubble using HTML/CSS when the GD library is not available
|
||||||
|
* Accepts the same parameters as image.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get the text from the URL parameter
|
||||||
|
$text = isset($_GET['q']) ? $_GET['q'] : 'Hello world!';
|
||||||
|
$style = isset($_GET['style']) ? strtolower($_GET['style']) : 'default';
|
||||||
|
|
||||||
|
// Decode URL-encoded text
|
||||||
|
$text = htmlspecialchars(urldecode($text));
|
||||||
|
|
||||||
|
// Define styles
|
||||||
|
$styles = [
|
||||||
|
'default' => [
|
||||||
|
'background' => '#c8dcff',
|
||||||
|
'border' => '#96aade',
|
||||||
|
'text' => '#000000',
|
||||||
|
],
|
||||||
|
'modern' => [
|
||||||
|
'background' => '#646464',
|
||||||
|
'border' => '#464646',
|
||||||
|
'text' => '#ffffff',
|
||||||
|
],
|
||||||
|
'retro' => [
|
||||||
|
'background' => '#fff0c8',
|
||||||
|
'border' => '#dcb48c',
|
||||||
|
'text' => '#64321e',
|
||||||
|
],
|
||||||
|
'minimal' => [
|
||||||
|
'background' => '#f5f5f5',
|
||||||
|
'border' => '#c8c8c8',
|
||||||
|
'text' => '#323232',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Use default style if specified style doesn't exist
|
||||||
|
if (!isset($styles[$style])) {
|
||||||
|
$style = 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSS for different shapes
|
||||||
|
$shapeCSS = [
|
||||||
|
'default' => 'border-radius: 10px;',
|
||||||
|
'modern' => 'border-radius: 10px;',
|
||||||
|
'retro' => 'border-radius: 0;',
|
||||||
|
'minimal' => 'border-radius: 0;',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Output HTML/CSS
|
||||||
|
header('Content-Type: text/html');
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Chat Bubble</title>
|
||||||
|
<style>
|
||||||
|
body, html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.bubble-container {
|
||||||
|
max-width: 600px;
|
||||||
|
position: relative;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.bubble {
|
||||||
|
position: relative;
|
||||||
|
background-color: <?php echo $styles[$style]['background']; ?>;
|
||||||
|
border: 1px solid <?php echo $styles[$style]['border']; ?>;
|
||||||
|
color: <?php echo $styles[$style]['text']; ?>;
|
||||||
|
padding: 20px;
|
||||||
|
<?php echo $shapeCSS[$style]; ?>
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.bubble:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -15px;
|
||||||
|
left: 50px;
|
||||||
|
border-width: 15px 15px 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: <?php echo $styles[$style]['background']; ?> transparent;
|
||||||
|
}
|
||||||
|
.bubble:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -16px;
|
||||||
|
left: 49px;
|
||||||
|
border-width: 16px 16px 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: <?php echo $styles[$style]['border']; ?> transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="bubble-container">
|
||||||
|
<div class="bubble">
|
||||||
|
<?php echo nl2br($text); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
42
index.js
42
index.js
@ -8,10 +8,10 @@ const extensionFolderPath = `scripts/extensions/third-party/${extensionName}`;
|
|||||||
|
|
||||||
// Default settings
|
// Default settings
|
||||||
const defaultSettings = {
|
const defaultSettings = {
|
||||||
image_service_url: "image.php",
|
image_service_url: "https://calista.the.sexiest.cat/image.php", // Use fully qualified URL by default
|
||||||
default_style: "default",
|
default_style: "default",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
render_in_collapse: true // New setting to enable/disable rendering in collapsed tool calls
|
render_in_collapse: true // Setting to enable/disable rendering in collapsed tool calls
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make sure settings exist
|
// Make sure settings exist
|
||||||
@ -24,6 +24,14 @@ if (Object.keys(extension_settings[extensionName]).length === 0) {
|
|||||||
Object.assign(extension_settings[extensionName], defaultSettings);
|
Object.assign(extension_settings[extensionName], defaultSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate settings - ensure image URL is absolute
|
||||||
|
if (extension_settings[extensionName].image_service_url &&
|
||||||
|
!extension_settings[extensionName].image_service_url.startsWith('http://') &&
|
||||||
|
!extension_settings[extensionName].image_service_url.startsWith('https://')) {
|
||||||
|
console.warn(`[${extensionName}] Saved image URL is not absolute, updating to use https://`);
|
||||||
|
extension_settings[extensionName].image_service_url = `https://${extension_settings[extensionName].image_service_url}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Function for AI to call - generates markdown image with URL-encoded text
|
// Function for AI to call - generates markdown image with URL-encoded text
|
||||||
function generateChatBubbleImage(text, style) {
|
function generateChatBubbleImage(text, style) {
|
||||||
if (!extension_settings[extensionName].enabled) {
|
if (!extension_settings[extensionName].enabled) {
|
||||||
@ -36,14 +44,23 @@ function generateChatBubbleImage(text, style) {
|
|||||||
// URL encode the text parameter
|
// URL encode the text parameter
|
||||||
const encodedText = encodeURIComponent(text);
|
const encodedText = encodeURIComponent(text);
|
||||||
|
|
||||||
|
// Ensure the URL is absolute (starts with http:// or https://)
|
||||||
|
let serviceUrl = extension_settings[extensionName].image_service_url;
|
||||||
|
if (!serviceUrl.startsWith('http://') && !serviceUrl.startsWith('https://')) {
|
||||||
|
console.warn(`[${extensionName}] Image service URL is not absolute: ${serviceUrl}`);
|
||||||
|
serviceUrl = `https://${serviceUrl}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Construct the URL with the encoded text
|
// Construct the URL with the encoded text
|
||||||
const imageUrl = `${extension_settings[extensionName].image_service_url}?q=${encodedText}`;
|
const imageUrl = `${serviceUrl}?q=${encodedText}`;
|
||||||
|
|
||||||
// Add style parameter if specified
|
// Add style parameter if specified
|
||||||
const fullUrl = bubbleStyle !== "default"
|
const fullUrl = bubbleStyle !== "default"
|
||||||
? `${imageUrl}&style=${bubbleStyle}`
|
? `${imageUrl}&style=${bubbleStyle}`
|
||||||
: imageUrl;
|
: imageUrl;
|
||||||
|
|
||||||
|
console.log(`[${extensionName}] Generated image URL: ${fullUrl}`);
|
||||||
|
|
||||||
// Return markdown image format
|
// Return markdown image format
|
||||||
return ``;
|
return ``;
|
||||||
}
|
}
|
||||||
@ -66,7 +83,15 @@ function onEnabledInput(event) {
|
|||||||
|
|
||||||
// Handle image service URL changes
|
// Handle image service URL changes
|
||||||
function onImageUrlInput(event) {
|
function onImageUrlInput(event) {
|
||||||
const value = $(event.target).val();
|
let value = $(event.target).val().trim();
|
||||||
|
|
||||||
|
// Ensure URL is absolute
|
||||||
|
if (value && !value.startsWith('http://') && !value.startsWith('https://')) {
|
||||||
|
console.warn(`[${extensionName}] Entered URL is not absolute, updating: ${value}`);
|
||||||
|
value = `https://${value}`;
|
||||||
|
$(event.target).val(value);
|
||||||
|
}
|
||||||
|
|
||||||
extension_settings[extensionName].image_service_url = value;
|
extension_settings[extensionName].image_service_url = value;
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
@ -201,7 +226,14 @@ function processToolCallMessages() {
|
|||||||
const markdownImgRegex = /!\[\]\(([^)]+)\)/;
|
const markdownImgRegex = /!\[\]\(([^)]+)\)/;
|
||||||
const match = tool.result.match(markdownImgRegex);
|
const match = tool.result.match(markdownImgRegex);
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
renderContainer.html(`<img src="${match[1]}" alt="Chat Bubble">`);
|
// Ensure the image URL is absolute
|
||||||
|
let imgUrl = match[1];
|
||||||
|
if (!imgUrl.startsWith('http://') && !imgUrl.startsWith('https://')) {
|
||||||
|
console.warn(`[${extensionName}] Image URL is not absolute: ${imgUrl}`);
|
||||||
|
imgUrl = `https://${imgUrl}`;
|
||||||
|
}
|
||||||
|
console.log(`[${extensionName}] Rendering image from URL: ${imgUrl}`);
|
||||||
|
renderContainer.html(`<img src="${imgUrl}" alt="Chat Bubble">`);
|
||||||
} else {
|
} else {
|
||||||
renderContainer.html(tool.result);
|
renderContainer.html(tool.result);
|
||||||
}
|
}
|
||||||
|
157
test.html
Normal file
157
test.html
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>SillyBubble Image Generator Test</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.bubble-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.bubble {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.bubble h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.bubble img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
input[type="text"], select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
.code {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>SillyBubble Image Generator Test</h1>
|
||||||
|
|
||||||
|
<div class="bubble">
|
||||||
|
<h3>Generate Your Own Chat Bubble</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="text">Text:</label>
|
||||||
|
<input type="text" id="text" value="Hello, I'm a chat bubble generated on the fly!" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="style">Style:</label>
|
||||||
|
<select id="style">
|
||||||
|
<option value="default">Default</option>
|
||||||
|
<option value="modern">Modern</option>
|
||||||
|
<option value="retro">Retro</option>
|
||||||
|
<option value="minimal">Minimal</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button onclick="generateImage()">Generate Image</button>
|
||||||
|
<div id="result" style="margin-top: 20px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bubble-container">
|
||||||
|
<div class="bubble">
|
||||||
|
<h3>Default Style</h3>
|
||||||
|
<img src="image.php?q=This%20is%20the%20default%20style%20for%20chat%20bubbles.%20It%20has%20a%20light%20blue%20background%20with%20rounded%20corners." alt="Default Style Chat Bubble" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bubble">
|
||||||
|
<h3>Modern Style</h3>
|
||||||
|
<img src="image.php?q=This%20is%20the%20modern%20style%20for%20chat%20bubbles.%20It%20has%20a%20dark%20background%20with%20white%20text.&style=modern" alt="Modern Style Chat Bubble" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bubble">
|
||||||
|
<h3>Retro Style</h3>
|
||||||
|
<img src="image.php?q=This%20is%20the%20retro%20style%20for%20chat%20bubbles.%20It%20has%20a%20warm%20color%20scheme%20and%20square%20corners.&style=retro" alt="Retro Style Chat Bubble" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bubble">
|
||||||
|
<h3>Minimal Style</h3>
|
||||||
|
<img src="image.php?q=This%20is%20the%20minimal%20style%20for%20chat%20bubbles.%20It%20has%20a%20clean,%20simple%20design%20with%20square%20corners.&style=minimal" alt="Minimal Style Chat Bubble" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bubble">
|
||||||
|
<h3>How to Use in SillyBubble Extension</h3>
|
||||||
|
<p>The SillyBubble extension uses this image generator to create chat bubbles on the fly. The extension passes text to this script and gets back an image.</p>
|
||||||
|
|
||||||
|
<h4>URL Format:</h4>
|
||||||
|
<div class="code">image.php?q=[URL-ENCODED-TEXT]&style=[STYLE-NAME]</div>
|
||||||
|
|
||||||
|
<h4>Available Styles:</h4>
|
||||||
|
<ul>
|
||||||
|
<li><strong>default</strong> - Light blue with rounded corners</li>
|
||||||
|
<li><strong>modern</strong> - Dark with light text</li>
|
||||||
|
<li><strong>retro</strong> - Warm colors with square corners</li>
|
||||||
|
<li><strong>minimal</strong> - Clean gray with square corners</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function generateImage() {
|
||||||
|
const text = document.getElementById('text').value;
|
||||||
|
const style = document.getElementById('style').value;
|
||||||
|
const encodedText = encodeURIComponent(text);
|
||||||
|
|
||||||
|
let imageUrl = `image.php?q=${encodedText}`;
|
||||||
|
if (style !== 'default') {
|
||||||
|
imageUrl += `&style=${style}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultDiv = document.getElementById('result');
|
||||||
|
resultDiv.innerHTML = `
|
||||||
|
<h4>Generated Image:</h4>
|
||||||
|
<img src="${imageUrl}" alt="Generated Chat Bubble" style="max-width: 100%;" />
|
||||||
|
<h4>URL:</h4>
|
||||||
|
<div class="code">${imageUrl}</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user