diff --git a/my-blog/static/css/style.css b/my-blog/static/css/style.css index 57ebe96..972f0b6 100644 --- a/my-blog/static/css/style.css +++ b/my-blog/static/css/style.css @@ -664,7 +664,8 @@ article.article-content { .chat-message.comment-style { background: #ffffff; border: 1px solid #d1d9e0; - border-radius: 8px; + border-left: 4px solid var(--theme-color); + border-radius: 8px; padding: 16px; margin-bottom: 12px; } diff --git a/my-blog/static/oauth/index.html b/my-blog/static/oauth/index.html index ee853ad..7380ee8 100644 --- a/my-blog/static/oauth/index.html +++ b/my-blog/static/oauth/index.html @@ -1,3 +1,3 @@ - + diff --git a/oauth/src/App.css b/oauth/src/App.css index e056258..4c96588 100644 --- a/oauth/src/App.css +++ b/oauth/src/App.css @@ -43,9 +43,9 @@ body { } .oauth-header-content { - display: flex; - justify-content: center; - align-items: center; + /* display: flex; */ + /* justify-content: center; */ + /* align-items: center; */ max-width: 800px; margin: 0 auto; padding: 20px 0; @@ -205,7 +205,7 @@ body { padding: 0; gap: 0; width: 100%; - max-width: 400px; + /* max-width: 400px; */ } .auth-section.search-bar-layout .handle-input { @@ -683,79 +683,250 @@ body { } /* Responsive */ -@media (max-width: 768px) { +@media (max-width: 1000px) { + /* Global mobile constraints */ + * { + max-width: 100% !important; + box-sizing: border-box !important; + } + + body { + overflow-x: hidden !important; + width: 100% !important; + margin: 0 !important; + padding: 0 !important; + } + + .app { + width: 100% !important; + max-width: 100% !important; + overflow-x: hidden !important; + padding: 0 !important; + margin: 0 !important; + } + .main-content { - max-width: 100%; + padding: 0px !important; + margin: 0px !important; + max-width: 100% !important; + width: 100% !important; + overflow-x: hidden !important; + } + + /* OAuth app mobile fixes - prevent overflow and content issues */ + .oauth-app-header { + padding: 0px !important; + margin: 0px !important; + border: none !important; + width: 100% !important; + max-width: 100% !important; + } + + .oauth-header-content { + max-width: 100% !important; + width: 100% !important; + padding: 10px 15px !important; + margin: 0px !important; + overflow-x: hidden !important; + } + + .oauth-header-actions { + width: auto !important; + max-width: 100% !important; + overflow: hidden !important; } .content-area { - border-left: none; - border-right: none; + padding: 0px !important; + margin: 0px !important; + width: 100% !important; + max-width: 100% !important; + overflow-x: hidden !important; } .card { - margin: 0; - border-radius: 0; - border-left: none; - border-right: none; + margin: 0px !important; + border-radius: 0px !important; + border-left: none !important; + border-right: none !important; + max-width: 100% !important; } - .app-header { - padding: 8px 16px; + .card-content { + padding: 15px !important; } - .header-actions { - gap: 4px; - } - - .btn { - padding: 6px 12px; - font-size: 14px; - } - - .tab-btn { - padding: 12px 16px; - font-size: 14px; + .comment-form { + padding: 15px !important; } .record-item { - padding: 12px 16px; + padding: 15px !important; + margin: 0px !important; + border-left: none !important; + border-right: none !important; + } + + .record-content { + word-wrap: break-word !important; + overflow-wrap: break-word !important; + max-width: 100% !important; + } + + .record-meta { + word-break: break-all !important; + overflow-wrap: break-word !important; + flex-wrap: wrap !important; + } + + .record-url { + word-break: break-all !important; + max-width: 100% !important; + } + + .form-group { + margin-bottom: 15px !important; + } + + .form-input, .form-textarea { + width: 100% !important; + max-width: 100% !important; + box-sizing: border-box !important; + padding: 12px !important; + } + + .auth-section { + padding: 0px !important; + max-width: 100% !important; + overflow: hidden !important; + } + + .auth-section.search-bar-layout { + width: 100% !important; + max-width: 100% !important; + } + + .auth-section.search-bar-layout .handle-input { + max-width: calc(100% - 80px) !important; + width: calc(100% - 80px) !important; + } + + .auth-button { + white-space: nowrap !important; + min-width: 70px !important; + } + + .tab-header { + overflow-x: auto !important; + -webkit-overflow-scrolling: touch !important; + width: 100% !important; + display: flex !important; + scrollbar-width: none !important; /* Firefox */ + -ms-overflow-style: none !important; /* IE/Edge */ + } + + .tab-header::-webkit-scrollbar { + display: none !important; /* Chrome/Safari */ + } + + .tab-btn { + white-space: nowrap !important; + min-width: auto !important; + padding: 12px 16px !important; + flex-shrink: 0 !important; + font-size: 13px !important; + } + + .json-content { + font-size: 10px !important; + padding: 8px !important; + overflow-x: auto !important; + -webkit-overflow-scrolling: touch !important; + } + + .ask-ai-container { + margin: 0px !important; + border-radius: 0px !important; + border-left: none !important; + border-right: none !important; } .chat-container { - height: 300px; + height: 250px !important; + padding: 12px !important; } - /* OAuth User Profile Mobile */ + .question-form { + padding: 12px !important; + } + + .input-container { + flex-direction: column !important; + gap: 12px !important; + } + + .question-input { + width: 100% !important; + box-sizing: border-box !important; + } + + .send-btn { + width: 100% !important; + height: 44px !important; + } + .oauth-user-profile { gap: 8px; + width: 100% !important; + max-width: 100% !important; + overflow: hidden !important; } - + + .profile-info { + flex: 1 !important; + min-width: 0 !important; + max-width: calc(100% - 50px) !important; + overflow: hidden !important; + } + .profile-avatar-section .profile-avatar, .profile-avatar-fallback { width: 36px; height: 36px; font-size: 14px; + flex-shrink: 0 !important; } - + .profile-display-name { font-size: 14px; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + max-width: 100% !important; } - + .profile-handle { font-size: 12px; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + max-width: 100% !important; } - + .profile-did { font-size: 9px; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + max-width: 100% !important; } - + .oauth-header-content { flex-direction: column; gap: 12px; - align-items: flex-start; + /* align-items: flex-start; */ } - + .oauth-header-actions { width: 100%; justify-content: center; diff --git a/oauth/src/App.jsx b/oauth/src/App.jsx index 11fac13..e597cbf 100644 --- a/oauth/src/App.jsx +++ b/oauth/src/App.jsx @@ -24,13 +24,130 @@ export default function App() { // Event listeners for blog communication useEffect(() => { - const handleAIQuestion = (event) => { + // Clear OAuth completion flag once app is loaded + if (sessionStorage.getItem('oauth_just_completed') === 'true') { + setTimeout(() => { + sessionStorage.removeItem('oauth_just_completed') + }, 1000) + } + + const handleAIQuestion = async (event) => { const { question } = event.detail if (question && adminData && user && agent) { - // Automatically open Ask AI panel and submit question - setShowAskAI(true) - // We'll need to pass this to the AskAI component - // For now, let's just open the panel + try { + console.log('Processing AI question:', question) + + // AI設定 + const aiConfig = { + host: import.meta.env.VITE_AI_HOST || 'https://ollama.syui.ai', + model: import.meta.env.VITE_AI_MODEL || 'gemma3:1b', + systemPrompt: import.meta.env.VITE_AI_SYSTEM_PROMPT || 'あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。' + } + + const prompt = `${aiConfig.systemPrompt} + +Question: ${question} + +Answer:` + + // Ollamaに直接リクエスト + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 30000) + + const response = await fetch(`${aiConfig.host}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Origin': 'https://syui.ai', + }, + body: JSON.stringify({ + model: aiConfig.model, + prompt: prompt, + stream: false, + options: { + temperature: 0.9, + top_p: 0.9, + num_predict: 200, + repeat_penalty: 1.1, + } + }), + signal: controller.signal + }) + + clearTimeout(timeoutId) + + if (!response.ok) { + throw new Error(`Ollama API error: ${response.status}`) + } + + const data = await response.json() + const answer = data.response || 'エラーが発生しました' + + console.log('AI response received:', answer) + + // Save conversation to ATProto + try { + const timestamp = new Date().toISOString() + const conversationRecord = { + repo: adminData.did, + collection: 'ai.syui.log.chat', + record: { + type: 'ai.syui.log.chat', + question: question, + answer: answer, + user: user ? { + did: user.did, + handle: user.handle, + displayName: user.displayName || user.handle + } : null, + ai: { + did: adminData.did, + handle: adminData.profile?.handle, + displayName: adminData.profile?.displayName + }, + timestamp: timestamp, + createdAt: timestamp + } + } + + await agent.com.atproto.repo.putRecord(conversationRecord) + console.log('Conversation saved to ATProto') + } catch (saveError) { + console.error('Failed to save conversation:', saveError) + } + + // Send response to blog + window.dispatchEvent(new CustomEvent('aiResponseReceived', { + detail: { + question: question, + answer: answer, + timestamp: new Date().toISOString(), + aiProfile: adminData?.profile ? { + did: adminData.did, + handle: adminData.profile.handle, + displayName: adminData.profile.displayName, + avatar: adminData.profile.avatar + } : null + } + })) + + } catch (error) { + console.error('Failed to process AI question:', error) + // Send error response to blog + window.dispatchEvent(new CustomEvent('aiResponseReceived', { + detail: { + question: question, + answer: 'エラーが発生しました。もう一度お試しください。', + timestamp: new Date().toISOString(), + aiProfile: adminData?.profile ? { + did: adminData.did, + handle: adminData.profile.handle, + displayName: adminData.profile.displayName, + avatar: adminData.profile.avatar + } : null + } + })) + } } } @@ -67,7 +184,11 @@ export default function App() { const isLoading = authLoading || dataLoading || userLoading - if (isLoading) { + // Don't show loading if we just completed OAuth callback + const isOAuthReturn = window.location.pathname === '/oauth/callback' || + sessionStorage.getItem('oauth_just_completed') === 'true' + + if (isLoading && !isOAuthReturn) { return (