4 Commits

Author SHA1 Message Date
f4b4c0a7fa fix ask-AI 2025-06-14 18:51:22 +09:00
eb8280ec14 fix: restore original Ask-AI implementation
Rewrote ask-ai.js to match the original working implementation
from commit 95cee69, using simple functions instead of class-based
approach for better OAuth app integration.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-14 18:43:13 +09:00
ddac65982b fix: rename AskAI to BlogAI to avoid OAuth conflict
OAuth comment system JavaScript was overriding window.AskAI
causing 'AskAI.toggle is not a function' error. Renamed to
BlogAI to prevent naming conflicts.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-14 18:34:42 +09:00
6462ea6f56 fix: preserve static/js directory during OAuth build
Change OAuth asset copying to only remove OAuth-specific files
instead of overwriting entire static directory, preserving
ask-ai.js and other static assets.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-14 18:27:38 +09:00
2 changed files with 252 additions and 331 deletions

View File

@@ -1,360 +1,281 @@
/** /**
* Ask AI functionality - Pure JavaScript, no jQuery dependency * Ask AI functionality - Based on original working implementation
*/ */
class AskAI {
constructor() { // Global variables for AI functionality
this.isReady = false; let aiProfileData = null;
this.aiProfile = null;
this.init(); // Original functions from working implementation
function toggleAskAI() {
const panel = document.getElementById('askAiPanel');
const isVisible = panel.style.display !== 'none';
panel.style.display = isVisible ? 'none' : 'block';
if (!isVisible) {
checkAuthenticationStatus();
} }
}
init() { function checkAuthenticationStatus() {
this.setupEventListeners(); const userSections = document.querySelectorAll('.user-section');
this.checkAuthOnLoad(); const isAuthenticated = userSections.length > 0;
}
if (isAuthenticated) {
setupEventListeners() { // User is authenticated - show Ask AI UI
// Listen for AI ready signal document.getElementById('authCheck').style.display = 'none';
window.addEventListener('aiChatReady', () => { document.getElementById('chatForm').style.display = 'block';
this.isReady = true; document.getElementById('chatHistory').style.display = 'block';
console.log('AI Chat is ready');
});
// Listen for AI profile updates
window.addEventListener('aiProfileLoaded', (event) => {
this.aiProfile = event.detail;
console.log('AI profile loaded:', this.aiProfile);
this.updateButton();
});
// Listen for AI responses
window.addEventListener('aiResponseReceived', (event) => {
this.handleAIResponse(event.detail);
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.hide();
}
if (e.key === 'Enter' && e.target.id === 'aiQuestion' && !e.shiftKey) {
e.preventDefault();
this.ask();
}
});
// Monitor authentication changes
this.observeAuth();
}
toggle() {
const panel = document.getElementById('askAiPanel');
const isVisible = panel.style.display !== 'none';
if (isVisible) { // Show initial greeting if chat history is empty
this.hide();
} else {
this.show();
}
}
show() {
const panel = document.getElementById('askAiPanel');
panel.style.display = 'block';
this.checkAuth();
}
hide() {
const panel = document.getElementById('askAiPanel');
panel.style.display = 'none';
}
checkAuth() {
const userSections = document.querySelectorAll('.user-section');
const isAuthenticated = userSections.length > 0;
const authCheck = document.getElementById('authCheck');
const chatForm = document.getElementById('chatForm');
const chatHistory = document.getElementById('chatHistory'); const chatHistory = document.getElementById('chatHistory');
if (chatHistory.children.length === 0) {
if (isAuthenticated) { showInitialGreeting();
authCheck.style.display = 'none';
chatForm.style.display = 'block';
chatHistory.style.display = 'block';
if (chatHistory.children.length === 0) {
this.showGreeting();
}
setTimeout(() => {
document.getElementById('aiQuestion').focus();
}, 50);
} else {
authCheck.style.display = 'block';
chatForm.style.display = 'none';
chatHistory.style.display = 'none';
} }
}
// Focus on input
checkAuthOnLoad() {
setTimeout(() => { setTimeout(() => {
this.checkAuth(); document.getElementById('aiQuestion').focus();
}, 500); }, 50);
} else {
// User not authenticated - show auth message
document.getElementById('authCheck').style.display = 'block';
document.getElementById('chatForm').style.display = 'none';
document.getElementById('chatHistory').style.display = 'none';
} }
}
observeAuth() { function askQuestion() {
const observer = new MutationObserver(() => { const question = document.getElementById('aiQuestion').value;
const userSections = document.querySelectorAll('.user-section'); if (!question.trim()) return;
if (userSections.length > 0) {
this.checkAuth(); const askButton = document.getElementById('askButton');
observer.disconnect(); askButton.disabled = true;
} askButton.textContent = 'Posting...';
});
try {
// Add user message to chat
addUserMessage(question);
observer.observe(document.body, { // Clear input
childList: true, document.getElementById('aiQuestion').value = '';
subtree: true
}); // Show loading
showLoadingMessage();
// Post question via OAuth app
window.dispatchEvent(new CustomEvent('postAIQuestion', {
detail: { question: question }
}));
} catch (error) {
console.error('Failed to ask question:', error);
showErrorMessage('Sorry, I encountered an error. Please try again.');
} finally {
askButton.disabled = false;
askButton.textContent = 'Ask';
} }
}
updateButton() { function addUserMessage(question) {
const button = document.getElementById('askAiButton'); const chatHistory = document.getElementById('chatHistory');
if (this.aiProfile && this.aiProfile.displayName) { const userSection = document.querySelector('.user-section');
const textNode = button.childNodes[2];
if (textNode) { let userAvatar = '👤';
textNode.textContent = this.aiProfile.displayName; let userDisplay = 'You';
} let userHandle = 'user';
if (userSection) {
const avatarImg = userSection.querySelector('.user-avatar');
const displayName = userSection.querySelector('.user-display-name');
const handle = userSection.querySelector('.user-handle');
if (avatarImg && avatarImg.src) {
userAvatar = `<img src="${avatarImg.src}" alt="${displayName?.textContent || 'User'}" class="profile-avatar">`;
}
if (displayName?.textContent) {
userDisplay = displayName.textContent;
}
if (handle?.textContent) {
userHandle = handle.textContent.replace('@', '');
} }
} }
showGreeting() { const questionDiv = document.createElement('div');
if (!this.aiProfile) return; questionDiv.className = 'chat-message user-message comment-style';
questionDiv.innerHTML = `
const chatHistory = document.getElementById('chatHistory'); <div class="message-header">
const greetingDiv = document.createElement('div'); <div class="avatar">${userAvatar}</div>
greetingDiv.className = 'chat-message ai-message comment-style initial-greeting'; <div class="user-info">
<div class="display-name">${userDisplay}</div>
const avatarElement = this.aiProfile.avatar <div class="handle">@${userHandle}</div>
? `<img src="${this.aiProfile.avatar}" alt="${this.aiProfile.displayName}" class="profile-avatar">` <div class="timestamp">${new Date().toLocaleString()}</div>
: '🤖';
greetingDiv.innerHTML = `
<div class="message-header">
<div class="avatar">${avatarElement}</div>
<div class="user-info">
<div class="display-name">${this.aiProfile.displayName}</div>
<div class="handle">@${this.aiProfile.handle}</div>
<div class="timestamp">${new Date().toLocaleString()}</div>
</div>
</div> </div>
<div class="message-content"> </div>
Hello! I'm an AI assistant trained on this blog's content. I can answer questions about the articles, provide insights, and help you understand the topics discussed here. What would you like to know? <div class="message-content">${question}</div>
`;
chatHistory.appendChild(questionDiv);
}
function showLoadingMessage() {
const chatHistory = document.getElementById('chatHistory');
const loadingDiv = document.createElement('div');
loadingDiv.className = 'ai-loading-simple';
loadingDiv.innerHTML = `
<i class="fas fa-robot"></i>
<span>考えています</span>
<i class="fas fa-spinner fa-spin"></i>
`;
chatHistory.appendChild(loadingDiv);
}
function showErrorMessage(message) {
const chatHistory = document.getElementById('chatHistory');
removeLoadingMessage();
const errorDiv = document.createElement('div');
errorDiv.className = 'chat-message error-message comment-style';
errorDiv.innerHTML = `
<div class="message-header">
<div class="avatar">⚠️</div>
<div class="user-info">
<div class="display-name">System</div>
<div class="handle">@system</div>
<div class="timestamp">${new Date().toLocaleString()}</div>
</div> </div>
`; </div>
chatHistory.appendChild(greetingDiv); <div class="message-content">${message}</div>
} `;
chatHistory.appendChild(errorDiv);
}
async ask() { function removeLoadingMessage() {
const question = document.getElementById('aiQuestion').value; const loadingMsg = document.querySelector('.ai-loading-simple');
const chatHistory = document.getElementById('chatHistory'); if (loadingMsg) {
const askButton = document.getElementById('askButton'); loadingMsg.remove();
if (!question.trim()) return;
// Wait for AI to be ready
if (!this.isReady) {
await this.waitForReady();
}
// Disable button
askButton.disabled = true;
askButton.textContent = 'Posting...';
try {
// Add user message
this.addUserMessage(question);
// Clear input
document.getElementById('aiQuestion').value = '';
// Show loading
this.showLoading();
// Post question
const event = new CustomEvent('postAIQuestion', {
detail: { question: question }
});
window.dispatchEvent(event);
} catch (error) {
this.showError('Sorry, I encountered an error. Please try again.');
} finally {
askButton.disabled = false;
askButton.textContent = 'Ask';
}
} }
}
waitForReady() { function showInitialGreeting() {
return new Promise(resolve => { if (!aiProfileData) return;
const checkReady = setInterval(() => {
if (this.isReady) {
clearInterval(checkReady);
resolve();
}
}, 100);
});
}
addUserMessage(question) { const chatHistory = document.getElementById('chatHistory');
const chatHistory = document.getElementById('chatHistory'); const greetingDiv = document.createElement('div');
const userSection = document.querySelector('.user-section'); greetingDiv.className = 'chat-message ai-message comment-style initial-greeting';
let userAvatar = '👤'; const avatarElement = aiProfileData.avatar
let userDisplay = 'You'; ? `<img src="${aiProfileData.avatar}" alt="${aiProfileData.displayName}" class="profile-avatar">`
let userHandle = 'user'; : '🤖';
if (userSection) { greetingDiv.innerHTML = `
const avatarImg = userSection.querySelector('.user-avatar'); <div class="message-header">
const displayName = userSection.querySelector('.user-display-name'); <div class="avatar">${avatarElement}</div>
const handle = userSection.querySelector('.user-handle'); <div class="user-info">
<div class="display-name">${aiProfileData.displayName}</div>
if (avatarImg && avatarImg.src) { <div class="handle">@${aiProfileData.handle}</div>
userAvatar = `<img src="${avatarImg.src}" alt="${displayName?.textContent || 'User'}" class="profile-avatar">`; <div class="timestamp">${new Date().toLocaleString()}</div>
}
if (displayName?.textContent) {
userDisplay = displayName.textContent;
}
if (handle?.textContent) {
userHandle = handle.textContent.replace('@', '');
}
}
const questionDiv = document.createElement('div');
questionDiv.className = 'chat-message user-message comment-style';
questionDiv.innerHTML = `
<div class="message-header">
<div class="avatar">${userAvatar}</div>
<div class="user-info">
<div class="display-name">${userDisplay}</div>
<div class="handle">@${userHandle}</div>
<div class="timestamp">${new Date().toLocaleString()}</div>
</div>
</div> </div>
<div class="message-content">${question}</div> </div>
`; <div class="message-content">
chatHistory.appendChild(questionDiv); Hello! I'm an AI assistant trained on this blog's content. I can answer questions about the articles, provide insights, and help you understand the topics discussed here. What would you like to know?
} </div>
`;
chatHistory.appendChild(greetingDiv);
}
showLoading() { function updateAskAIButton() {
const chatHistory = document.getElementById('chatHistory'); const button = document.getElementById('askAiButton');
const loadingDiv = document.createElement('div'); if (!button) return;
loadingDiv.className = 'ai-loading-simple';
loadingDiv.innerHTML = ` // Only update text, never modify the icon
<i class="fas fa-robot"></i> if (aiProfileData && aiProfileData.displayName) {
<span>考えています</span> const textNode = button.childNodes[2] || button.lastChild;
<i class="fas fa-spinner fa-spin"></i> if (textNode && textNode.nodeType === Node.TEXT_NODE) {
`; textNode.textContent = aiProfileData.displayName;
chatHistory.appendChild(loadingDiv);
}
showError(message) {
const chatHistory = document.getElementById('chatHistory');
this.removeLoading();
const errorDiv = document.createElement('div');
errorDiv.className = 'chat-message error-message comment-style';
errorDiv.innerHTML = `
<div class="message-header">
<div class="avatar">⚠️</div>
<div class="user-info">
<div class="display-name">System</div>
<div class="handle">@system</div>
<div class="timestamp">${new Date().toLocaleString()}</div>
</div>
</div>
<div class="message-content">${message}</div>
`;
chatHistory.appendChild(errorDiv);
}
removeLoading() {
const loadingMsg = document.querySelector('.ai-loading-simple');
if (loadingMsg) {
loadingMsg.remove();
}
}
handleAIResponse(responseData) {
const chatHistory = document.getElementById('chatHistory');
this.removeLoading();
const aiProfile = responseData.aiProfile;
if (!aiProfile || !aiProfile.handle || !aiProfile.displayName) {
console.error('AI profile data is missing');
return;
}
const timestamp = new Date(responseData.timestamp || Date.now());
const avatarElement = aiProfile.avatar
? `<img src="${aiProfile.avatar}" alt="${aiProfile.displayName}" class="profile-avatar">`
: '🤖';
const answerDiv = document.createElement('div');
answerDiv.className = 'chat-message ai-message comment-style';
answerDiv.innerHTML = `
<div class="message-header">
<div class="avatar">${avatarElement}</div>
<div class="user-info">
<div class="display-name">${aiProfile.displayName}</div>
<div class="handle">@${aiProfile.handle}</div>
<div class="timestamp">${timestamp.toLocaleString()}</div>
</div>
</div>
<div class="message-content">${responseData.answer}</div>
`;
chatHistory.appendChild(answerDiv);
// Limit chat history
this.limitChatHistory();
}
limitChatHistory() {
const chatHistory = document.getElementById('chatHistory');
if (chatHistory.children.length > 10) {
chatHistory.removeChild(chatHistory.children[0]);
if (chatHistory.children.length > 0) {
chatHistory.removeChild(chatHistory.children[0]);
}
} }
} }
} }
// Initialize Ask AI when DOM is loaded function handleAIResponse(responseData) {
document.addEventListener('DOMContentLoaded', () => { const chatHistory = document.getElementById('chatHistory');
try { removeLoadingMessage();
window.askAIInstance = new AskAI();
console.log('Ask AI initialized successfully'); const aiProfile = responseData.aiProfile;
} catch (error) { if (!aiProfile || !aiProfile.handle || !aiProfile.displayName) {
console.error('Failed to initialize Ask AI:', error); console.error('AI profile data is missing');
return;
} }
const timestamp = new Date(responseData.timestamp || Date.now());
const avatarElement = aiProfile.avatar
? `<img src="${aiProfile.avatar}" alt="${aiProfile.displayName}" class="profile-avatar">`
: '🤖';
const answerDiv = document.createElement('div');
answerDiv.className = 'chat-message ai-message comment-style';
answerDiv.innerHTML = `
<div class="message-header">
<div class="avatar">${avatarElement}</div>
<div class="user-info">
<div class="display-name">${aiProfile.displayName}</div>
<div class="handle">@${aiProfile.handle}</div>
<div class="timestamp">${timestamp.toLocaleString()}</div>
</div>
</div>
<div class="message-content">${responseData.answer}</div>
`;
chatHistory.appendChild(answerDiv);
// Limit chat history
limitChatHistory();
}
function limitChatHistory() {
const chatHistory = document.getElementById('chatHistory');
if (chatHistory.children.length > 10) {
chatHistory.removeChild(chatHistory.children[0]);
if (chatHistory.children.length > 0) {
chatHistory.removeChild(chatHistory.children[0]);
}
}
}
// Event listeners setup
function setupAskAIEventListeners() {
// Listen for AI profile updates from OAuth app
window.addEventListener('aiProfileLoaded', function(event) {
aiProfileData = event.detail;
console.log('AI profile loaded:', aiProfileData);
updateAskAIButton();
});
// Listen for AI responses
window.addEventListener('aiResponseReceived', function(event) {
handleAIResponse(event.detail);
});
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
const panel = document.getElementById('askAiPanel');
if (panel) {
panel.style.display = 'none';
}
}
// Enter key to send message
if (e.key === 'Enter' && e.target.id === 'aiQuestion' && !e.shiftKey) {
e.preventDefault();
askQuestion();
}
});
}
// Initialize Ask AI when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
setupAskAIEventListeners();
console.log('Ask AI initialized successfully');
}); });
// Global function for onclick // Global functions for onclick handlers
window.AskAI = { window.toggleAskAI = toggleAskAI;
toggle: function() { window.askQuestion = askQuestion;
console.log('AskAI.toggle called');
if (window.askAIInstance) {
window.askAIInstance.toggle();
} else {
console.error('Ask AI instance not available');
}
},
ask: function() {
console.log('AskAI.ask called');
if (window.askAIInstance) {
window.askAIInstance.ask();
} else {
console.error('Ask AI instance not available');
}
}
};

View File

@@ -49,7 +49,7 @@
</a> </a>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<button class="ask-ai-btn" onclick="AskAI.toggle()" id="askAiButton"> <button class="ask-ai-btn" onclick="toggleAskAI()" id="askAiButton">
<span class="ai-icon icon-ai"></span> <span class="ai-icon icon-ai"></span>
ai ai
</button> </button>