From 66712bfa38df3617dd21688f12634fcbfbf4da03 Mon Sep 17 00:00:00 2001 From: syui Date: Wed, 25 Jun 2025 20:17:31 +0900 Subject: [PATCH] test blog profile --- my-blog/static/css/style.css | 37 ++++- my-blog/static/js/ask-ai.js | 70 ++++++++- oauth/src/App.css | 92 +++++++++++- oauth/src/App.jsx | 15 ++ oauth/src/api/atproto.js | 13 +- oauth/src/components/ProfileForm.jsx | 160 +++++++++++++++++++++ oauth/src/components/ProfileRecordList.jsx | 133 +++++++++++++++++ oauth/src/components/RecordTabs.jsx | 15 ++ 8 files changed, 529 insertions(+), 6 deletions(-) create mode 100644 oauth/src/components/ProfileForm.jsx create mode 100644 oauth/src/components/ProfileRecordList.jsx diff --git a/my-blog/static/css/style.css b/my-blog/static/css/style.css index d0a80d0..fbb2138 100644 --- a/my-blog/static/css/style.css +++ b/my-blog/static/css/style.css @@ -200,7 +200,7 @@ a.view-markdown:any-link { /* Main Content */ .main-content { grid-area: main; - max-width: 1000px; + max-width: 800px; margin: 0 auto; padding: 0px; width: 100%; @@ -969,6 +969,41 @@ article.article-content { .question-form { padding: 12px !important; } +} + +/* Profile Display Styles */ +.profile-avatar-fallback { + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--theme-color); + color: var(--white); + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: 700; +} + +.admin-badge { + background: var(--theme-color); + color: var(--white); + font-size: 10px; + padding: 2px 6px; + border-radius: 10px; + font-weight: 500; + margin-left: 8px; +} + +.loading-message, .error-message, .no-profiles { + text-align: center; + padding: 20px; + color: var(--dark-gray); +} + +.error-message { + color: #d32f2f; +} .input-container { flex-direction: column !important; diff --git a/my-blog/static/js/ask-ai.js b/my-blog/static/js/ask-ai.js index 824a3b4..2dbebae 100644 --- a/my-blog/static/js/ask-ai.js +++ b/my-blog/static/js/ask-ai.js @@ -37,10 +37,74 @@ function checkAuthenticationStatus() { document.getElementById('aiQuestion').focus(); }, 50); } else { - // User not authenticated - show auth message - document.getElementById('authCheck').style.display = 'block'; + // User not authenticated - show profiles instead of auth message + document.getElementById('authCheck').style.display = 'none'; document.getElementById('chatForm').style.display = 'none'; - document.getElementById('chatHistory').style.display = 'none'; + document.getElementById('chatHistory').style.display = 'block'; + loadAndShowProfiles(); + } +} + +// Load and display profiles from ai.syui.log.profile collection +async function loadAndShowProfiles() { + const chatHistory = document.getElementById('chatHistory'); + chatHistory.innerHTML = '
Loading profiles...
'; + + try { + const ADMIN_HANDLE = 'ai.syui.ai'; + const OAUTH_COLLECTION = 'ai.syui.log'; + const ATPROTO_PDS = 'syu.is'; + + const response = await fetch(`https://${ATPROTO_PDS}/xrpc/com.atproto.repo.listRecords?repo=${ADMIN_HANDLE}&collection=${OAUTH_COLLECTION}.profile&limit=100`); + + if (!response.ok) { + throw new Error('Failed to fetch profiles'); + } + + const data = await response.json(); + const profiles = (data.records || []).sort((a, b) => { + if (a.value.type === 'admin' && b.value.type !== 'admin') return -1; + if (a.value.type !== 'admin' && b.value.type === 'admin') return 1; + return 0; + }); + + // Clear loading message + chatHistory.innerHTML = ''; + + // Display profiles using the same format as chat + profiles.forEach(profile => { + const profileDiv = document.createElement('div'); + profileDiv.className = 'chat-message ai-message comment-style'; + + const avatarElement = profile.value.author.avatar + ? `${profile.value.author.displayName || profile.value.author.handle}` + : `
${(profile.value.author.displayName || profile.value.author.handle || '?').charAt(0).toUpperCase()}
`; + + const adminBadge = profile.value.type === 'admin' + ? 'Admin' + : ''; + + profileDiv.innerHTML = ` +
+
${avatarElement}
+ +
+
${profile.value.text}
+ `; + chatHistory.appendChild(profileDiv); + }); + + if (profiles.length === 0) { + chatHistory.innerHTML = '
No profiles available
'; + } + + } catch (error) { + console.error('Error loading profiles:', error); + chatHistory.innerHTML = '
Failed to load profiles. Please try again later.
'; } } diff --git a/oauth/src/App.css b/oauth/src/App.css index 03439b4..f403a2f 100644 --- a/oauth/src/App.css +++ b/oauth/src/App.css @@ -34,6 +34,96 @@ body { background: var(--background); } +/* Profile Form Styles */ +.profile-form-container { + background: var(--background-secondary); + border: 1px solid var(--border); + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; +} + +.profile-form-container h3 { + margin: 0 0 16px 0; + color: var(--text); +} + +.profile-form .form-row { + display: flex; + gap: 16px; + margin-bottom: 16px; +} + +.profile-form .form-group { + flex: 1; +} + +.profile-form .form-group label { + display: block; + margin-bottom: 6px; + font-weight: 600; + color: var(--text); +} + +.profile-form .form-group input, +.profile-form .form-group select, +.profile-form .form-group textarea { + width: 100%; + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: 6px; + font-size: 14px; + transition: border-color 0.2s; +} + +.profile-form .form-group input:focus, +.profile-form .form-group select:focus, +.profile-form .form-group textarea:focus { + outline: none; + border-color: var(--primary); +} + +.profile-form .form-group textarea { + resize: vertical; + min-height: 80px; +} + +.profile-form .submit-btn { + background: var(--primary); + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.2s; +} + +.profile-form .submit-btn:hover:not(:disabled) { + background: var(--primary-hover); +} + +.profile-form .submit-btn:disabled { + background: var(--text-secondary); + cursor: not-allowed; +} + +/* Profile Record List Styles */ +.profile-record-list .record-item.admin { + border-left: 4px solid var(--primary); +} + +.profile-record-list .admin-badge { + background: var(--primary); + color: white; + font-size: 10px; + padding: 2px 6px; + border-radius: 10px; + font-weight: 500; + margin-left: 8px; +} + /* Header */ .oauth-app-header { background: var(--background); @@ -264,7 +354,7 @@ body { .main-content { grid-area: main; - max-width: 1000px; + max-width: 800px; margin: 0 auto; padding: 0px; width: 100%; diff --git a/oauth/src/App.jsx b/oauth/src/App.jsx index b684ea5..490a4b8 100644 --- a/oauth/src/App.jsx +++ b/oauth/src/App.jsx @@ -7,6 +7,7 @@ import { usePageContext } from './hooks/usePageContext.js' import AuthButton from './components/AuthButton.jsx' import RecordTabs from './components/RecordTabs.jsx' import CommentForm from './components/CommentForm.jsx' +import ProfileForm from './components/ProfileForm.jsx' import AskAI from './components/AskAI.jsx' import TestUI from './components/TestUI.jsx' import OAuthCallback from './components/OAuthCallback.jsx' @@ -428,6 +429,20 @@ Answer:` )} + {user && ( +
+ { + refreshAdminData?.() + refreshUserData?.() + }} + /> +
+ )} + { + const [text, setText] = useState('') + const [type, setType] = useState('user') + const [handle, setHandle] = useState('') + const [rkey, setRkey] = useState('') + const [posting, setPosting] = useState(false) + const [error, setError] = useState('') + + const handleSubmit = async (e) => { + e.preventDefault() + + if (!text.trim() || !handle.trim() || !rkey.trim()) { + setError('すべてのフィールドを入力してください') + return + } + + setPosting(true) + setError('') + + try { + // Get handle information + let authorData + try { + const handleDid = await atproto.getDid(apiConfig.pds, handle) + authorData = await atproto.getProfile(apiConfig.bsky, handleDid) + } catch (err) { + throw new Error('ハンドルが見つかりません') + } + + // Create record + const record = { + repo: user.did, + collection: `${apiConfig.collection}.profile`, + rkey: rkey, + record: { + $type: `${apiConfig.collection}.profile`, + text: text, + type: type, + author: { + did: authorData.did, + handle: authorData.handle, + displayName: authorData.displayName || authorData.handle, + avatar: authorData.avatar || null + }, + createdAt: new Date().toISOString(), + post: { + url: window.location.origin, + date: new Date().toISOString(), + slug: '', + tags: [], + title: 'Profile', + language: 'ja' + } + } + } + + await atproto.putRecord(apiConfig.pds, record, agent) + + // Invalidate cache and refresh + collections.invalidateCache(`${apiConfig.collection}.profile`) + + // Reset form + setText('') + setType('user') + setHandle('') + setRkey('') + + if (onProfilePosted) { + onProfilePosted() + } + + } catch (err) { + console.error('Failed to create profile:', err) + setError(err.message || 'プロフィールの作成に失敗しました') + } finally { + setPosting(false) + } + } + + if (!user) { + return null + } + + return ( +
+

プロフィール投稿

+ + {error && ( +
+ {error} +
+ )} + +
+
+
+ + setHandle(e.target.value)} + placeholder="例: syui.ai" + required + /> +
+
+ + setRkey(e.target.value)} + placeholder="例: syui" + required + /> +
+
+ +
+ + +
+ +
+ +