oauth markdown
This commit is contained in:
		| @@ -844,7 +844,6 @@ article.article-content { | ||||
|     font-size: 24px; | ||||
|     font-weight: 600; | ||||
|     margin-bottom: 32px; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| /* OAuth Comment System - Hide on homepage by default, show on post pages */ | ||||
|   | ||||
| @@ -8,10 +8,13 @@ | ||||
|     "preview": "vite preview" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@atproto/api": "^0.15.12", | ||||
|     "@atproto/oauth-client-browser": "^0.3.19", | ||||
|     "react": "^18.2.0", | ||||
|     "react-dom": "^18.2.0", | ||||
|     "@atproto/api": "^0.15.12", | ||||
|     "@atproto/oauth-client-browser": "^0.3.19" | ||||
|     "react-markdown": "^9.0.1", | ||||
|     "rehype-highlight": "^7.0.2", | ||||
|     "remark-gfm": "^4.0.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/react": "^18.2.0", | ||||
|   | ||||
| @@ -1337,10 +1337,144 @@ body { | ||||
| .message-content { | ||||
|   color: var(--text); | ||||
|   line-height: 1.5; | ||||
|   white-space: pre-wrap; | ||||
|   word-wrap: break-word; | ||||
| } | ||||
|  | ||||
| /* Markdown styles */ | ||||
| .message-content h1, | ||||
| .message-content h2, | ||||
| .message-content h3, | ||||
| .message-content h4, | ||||
| .message-content h5, | ||||
| .message-content h6 { | ||||
|   margin: 16px 0 8px 0; | ||||
|   font-weight: 600; | ||||
| } | ||||
|  | ||||
| .message-content h1 { font-size: 1.5em; } | ||||
| .message-content h2 { font-size: 1.3em; } | ||||
| .message-content h3 { font-size: 1.1em; } | ||||
|  | ||||
| .message-content p { | ||||
|   margin: 8px 0; | ||||
| } | ||||
|  | ||||
| .message-content pre { | ||||
|   background: var(--background-secondary); | ||||
|   border: 1px solid var(--border); | ||||
|   border-radius: 6px; | ||||
|   padding: 12px; | ||||
|   margin: 12px 0; | ||||
|   overflow-x: auto; | ||||
| } | ||||
|  | ||||
| .message-content code { | ||||
|   background: var(--background-secondary); | ||||
|   padding: 2px 4px; | ||||
|   border-radius: 3px; | ||||
|   font-family: 'Consolas', 'Monaco', 'Andale Mono', 'Ubuntu Mono', monospace; | ||||
|   font-size: 0.9em; | ||||
| } | ||||
|  | ||||
| .message-content pre code { | ||||
|   background: transparent; | ||||
|   padding: 0; | ||||
|   border-radius: 0; | ||||
|   font-size: 0.9em; | ||||
| } | ||||
|  | ||||
| .message-content ul, | ||||
| .message-content ol { | ||||
|   margin: 8px 0; | ||||
|   padding-left: 24px; | ||||
| } | ||||
|  | ||||
| .message-content li { | ||||
|   margin: 4px 0; | ||||
| } | ||||
|  | ||||
| .message-content blockquote { | ||||
|   border-left: 4px solid var(--border); | ||||
|   padding-left: 16px; | ||||
|   margin: 12px 0; | ||||
|   color: var(--text-secondary); | ||||
| } | ||||
|  | ||||
| .message-content table { | ||||
|   border-collapse: collapse; | ||||
|   width: 100%; | ||||
|   margin: 12px 0; | ||||
| } | ||||
|  | ||||
| .message-content th, | ||||
| .message-content td { | ||||
|   border: 1px solid var(--border); | ||||
|   padding: 8px 12px; | ||||
|   text-align: left; | ||||
| } | ||||
|  | ||||
| .message-content th { | ||||
|   background: var(--background-secondary); | ||||
|   font-weight: 600; | ||||
| } | ||||
|  | ||||
| .message-content a { | ||||
|   color: var(--primary); | ||||
|   text-decoration: none; | ||||
| } | ||||
|  | ||||
| .message-content a:hover { | ||||
|   text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .message-content hr { | ||||
|   border: none; | ||||
|   border-top: 1px solid var(--border); | ||||
|   margin: 16px 0; | ||||
| } | ||||
|  | ||||
| .record-actions { | ||||
|   flex-shrink: 0; | ||||
| } | ||||
|  | ||||
| .bluesky-footer { | ||||
|   text-align: center; | ||||
|   padding: 20px; | ||||
|   color: var(--primary); | ||||
|   font-size: 24px; | ||||
| } | ||||
|  | ||||
| .bluesky-footer i { | ||||
|   transition: color 0.2s ease; | ||||
| } | ||||
|  | ||||
| .bluesky-footer i:hover { | ||||
|   color: var(--primary-hover); | ||||
| } | ||||
|  | ||||
| /* Custom code block styling */ | ||||
| .message-content pre { | ||||
|   background: #2d3748 !important; | ||||
|   border: 1px solid #4a5568 !important; | ||||
|   border-radius: 6px; | ||||
|   padding: 12px; | ||||
|   margin: 12px 0; | ||||
|   overflow-x: auto; | ||||
| } | ||||
|  | ||||
| .message-content pre code { | ||||
|   background: transparent !important; | ||||
|   color: #e2e8f0 !important; | ||||
|   font-family: 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace; | ||||
|   font-size: 14px; | ||||
|   line-height: 1.5; | ||||
| } | ||||
|  | ||||
| .message-content code { | ||||
|   background: #2d3748 !important; | ||||
|   color: #e2e8f0 !important; | ||||
|   padding: 2px 4px; | ||||
|   border-radius: 3px; | ||||
|   font-family: 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace; | ||||
|   font-size: 14px; | ||||
| } | ||||
|   | ||||
| @@ -14,7 +14,7 @@ import OAuthCallback from './components/OAuthCallback.jsx' | ||||
|  | ||||
| export default function App() { | ||||
|   const { user, agent, loading: authLoading, login, logout } = useAuth() | ||||
|   const { adminData, langRecords, commentRecords, chatRecords: adminChatRecords, loading: dataLoading, error, refresh: refreshAdminData } = useAdminData() | ||||
|   const { adminData, langRecords, commentRecords, chatRecords: adminChatRecords, chatHasMore, loading: dataLoading, error, refresh: refreshAdminData, loadMoreChat } = useAdminData() | ||||
|   const { userComments, chatRecords, loading: userLoading, refresh: refreshUserData } = useUserData(adminData) | ||||
|   const [userChatRecords, setUserChatRecords] = useState([]) | ||||
|   const [userChatLoading, setUserChatLoading] = useState(false) | ||||
| @@ -430,6 +430,8 @@ Answer:` | ||||
|             commentRecords={commentRecords} | ||||
|             userComments={userComments} | ||||
|             chatRecords={adminChatRecords} | ||||
|             chatHasMore={chatHasMore} | ||||
|             onLoadMoreChat={loadMoreChat} | ||||
|             userChatRecords={userChatRecords} | ||||
|             userChatLoading={userChatLoading} | ||||
|             baseRecords={adminData.records} | ||||
| @@ -461,9 +463,6 @@ Answer:` | ||||
|             </div> | ||||
|           )} | ||||
|            | ||||
|           <div className="bluesky-footer"> | ||||
|             <i className="fab fa-bluesky"></i> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   | ||||
| @@ -83,9 +83,16 @@ export const atproto = { | ||||
|     return await request(`${apiEndpoint}/xrpc/${ENDPOINTS.getProfile}?actor=${actor}`) | ||||
|   }, | ||||
|  | ||||
|   async getRecords(pds, repo, collection, limit = 10) { | ||||
|     const res = await request(`${pds}/xrpc/${ENDPOINTS.listRecords}?repo=${repo}&collection=${collection}&limit=${limit}`) | ||||
|     return res.records || [] | ||||
|   async getRecords(pds, repo, collection, limit = 10, cursor = null) { | ||||
|     let url = `${pds}/xrpc/${ENDPOINTS.listRecords}?repo=${repo}&collection=${collection}&limit=${limit}` | ||||
|     if (cursor) { | ||||
|       url += `&cursor=${cursor}` | ||||
|     } | ||||
|     const res = await request(url) | ||||
|     return { | ||||
|       records: res.records || [], | ||||
|       cursor: res.cursor || null | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   async searchPlc(plc, did) { | ||||
| @@ -121,8 +128,10 @@ export const collections = { | ||||
|     if (cached) return cached | ||||
|      | ||||
|     const data = await atproto.getRecords(pds, repo, collection, limit) | ||||
|     dataCache.set(cacheKey, data) | ||||
|     return data | ||||
|     // Extract records array for backward compatibility | ||||
|     const records = data.records || data | ||||
|     dataCache.set(cacheKey, records) | ||||
|     return records | ||||
|   }, | ||||
|  | ||||
|   async getLang(pds, repo, collection, limit = 10) { | ||||
| @@ -131,8 +140,10 @@ export const collections = { | ||||
|     if (cached) return cached | ||||
|      | ||||
|     const data = await atproto.getRecords(pds, repo, `${collection}.chat.lang`, limit) | ||||
|     dataCache.set(cacheKey, data) | ||||
|     return data | ||||
|     // Extract records array for backward compatibility | ||||
|     const records = data.records || data | ||||
|     dataCache.set(cacheKey, records) | ||||
|     return records | ||||
|   }, | ||||
|  | ||||
|   async getComment(pds, repo, collection, limit = 10) { | ||||
| @@ -141,17 +152,29 @@ export const collections = { | ||||
|     if (cached) return cached | ||||
|      | ||||
|     const data = await atproto.getRecords(pds, repo, `${collection}.chat.comment`, limit) | ||||
|     dataCache.set(cacheKey, data) | ||||
|     return data | ||||
|     // Extract records array for backward compatibility | ||||
|     const records = data.records || data | ||||
|     dataCache.set(cacheKey, records) | ||||
|     return records | ||||
|   }, | ||||
|  | ||||
|   async getChat(pds, repo, collection, limit = 10) { | ||||
|   async getChat(pds, repo, collection, limit = 10, cursor = null) { | ||||
|     // Don't use cache for pagination requests | ||||
|     if (cursor) { | ||||
|       const result = await atproto.getRecords(pds, repo, `${collection}.chat`, limit, cursor) | ||||
|       return result | ||||
|     } | ||||
|      | ||||
|     const cacheKey = dataCache.generateKey('chat', pds, repo, collection, limit) | ||||
|     const cached = dataCache.get(cacheKey) | ||||
|     if (cached) return cached | ||||
|     if (cached) { | ||||
|       // Ensure cached data has the correct structure | ||||
|       return Array.isArray(cached) ? { records: cached, cursor: null } : cached | ||||
|     } | ||||
|      | ||||
|     const data = await atproto.getRecords(pds, repo, `${collection}.chat`, limit) | ||||
|     dataCache.set(cacheKey, data) | ||||
|     // Cache only the records array for backward compatibility | ||||
|     dataCache.set(cacheKey, data.records || data) | ||||
|     return data | ||||
|   }, | ||||
|  | ||||
| @@ -161,8 +184,10 @@ export const collections = { | ||||
|     if (cached) return cached | ||||
|      | ||||
|     const data = await atproto.getRecords(pds, repo, `${collection}.user`, limit) | ||||
|     dataCache.set(cacheKey, data) | ||||
|     return data | ||||
|     // Extract records array for backward compatibility | ||||
|     const records = data.records || data | ||||
|     dataCache.set(cacheKey, records) | ||||
|     return records | ||||
|   }, | ||||
|  | ||||
|   async getUserComments(pds, repo, collection, limit = 10) { | ||||
| @@ -171,8 +196,10 @@ export const collections = { | ||||
|     if (cached) return cached | ||||
|      | ||||
|     const data = await atproto.getRecords(pds, repo, collection, limit) | ||||
|     dataCache.set(cacheKey, data) | ||||
|     return data | ||||
|     // Extract records array for backward compatibility | ||||
|     const records = data.records || data | ||||
|     dataCache.set(cacheKey, records) | ||||
|     return records | ||||
|   }, | ||||
|  | ||||
|   async getProfiles(pds, repo, collection, limit = 100) { | ||||
| @@ -181,8 +208,10 @@ export const collections = { | ||||
|     if (cached) return cached | ||||
|      | ||||
|     const data = await atproto.getRecords(pds, repo, `${collection}.profile`, limit) | ||||
|     dataCache.set(cacheKey, data) | ||||
|     return data | ||||
|     // Extract records array for backward compatibility | ||||
|     const records = data.records || data | ||||
|     dataCache.set(cacheKey, records) | ||||
|     return records | ||||
|   }, | ||||
|  | ||||
|   // 投稿後にキャッシュを無効化 | ||||
|   | ||||
| @@ -1,4 +1,8 @@ | ||||
| import React, { useState } from 'react' | ||||
| import ReactMarkdown from 'react-markdown' | ||||
| import remarkGfm from 'remark-gfm' | ||||
| import rehypeHighlight from 'rehype-highlight' | ||||
| import 'highlight.js/styles/github-dark.css' | ||||
|  | ||||
| // Helper function to get correct web URL based on avatar URL | ||||
| function getCorrectWebUrl(avatarUrl) { | ||||
| @@ -18,7 +22,7 @@ function getCorrectWebUrl(avatarUrl) { | ||||
|   return 'https://bsky.app' | ||||
| } | ||||
|  | ||||
| export default function ChatRecordList({ chatPairs, apiConfig, user = null, agent = null, onRecordDeleted = null }) { | ||||
| export default function ChatRecordList({ chatPairs, chatHasMore, onLoadMoreChat, apiConfig, user = null, agent = null, onRecordDeleted = null }) { | ||||
|   const [expandedRecords, setExpandedRecords] = useState(new Set()) | ||||
|  | ||||
|   const toggleJsonView = (key) => { | ||||
| @@ -139,7 +143,14 @@ export default function ChatRecordList({ chatPairs, apiConfig, user = null, agen | ||||
|                   </pre> | ||||
|                 </div> | ||||
|               )} | ||||
|               <div className="message-content">{chatPair.question.value.text}</div> | ||||
|               <div className="message-content"> | ||||
|                 <ReactMarkdown  | ||||
|                   remarkPlugins={[remarkGfm]} | ||||
|                   rehypePlugins={[rehypeHighlight]} | ||||
|                 > | ||||
|                   {chatPair.question.value.text} | ||||
|                 </ReactMarkdown> | ||||
|               </div> | ||||
|             </div> | ||||
|           )} | ||||
|  | ||||
| @@ -190,25 +201,31 @@ export default function ChatRecordList({ chatPairs, apiConfig, user = null, agen | ||||
|                   </pre> | ||||
|                 </div> | ||||
|               )} | ||||
|               <div className="message-content">{chatPair.answer.value.text}</div> | ||||
|               <div className="message-content"> | ||||
|                 <ReactMarkdown  | ||||
|                   remarkPlugins={[remarkGfm]} | ||||
|                   rehypePlugins={[rehypeHighlight]} | ||||
|                 > | ||||
|                   {chatPair.answer.value.text} | ||||
|                 </ReactMarkdown> | ||||
|               </div> | ||||
|             </div> | ||||
|           )} | ||||
|  | ||||
|           {/* Post metadata */} | ||||
|           {chatPair.question?.value.post?.url && ( | ||||
|             <div className="record-meta"> | ||||
|               <a  | ||||
|                 href={chatPair.question.value.post.url}  | ||||
|                 target="_blank"  | ||||
|                 rel="noopener noreferrer" | ||||
|                 className="record-url" | ||||
|               > | ||||
|                 {chatPair.question.value.post.url} | ||||
|               </a> | ||||
|             </div> | ||||
|           )} | ||||
|         </div> | ||||
|       ))} | ||||
|        | ||||
|       {/* Load More Button */} | ||||
|       {chatHasMore && onLoadMoreChat && ( | ||||
|         <div className="bluesky-footer"> | ||||
|           <i  | ||||
|             className="fab fa-bluesky" | ||||
|             onClick={onLoadMoreChat} | ||||
|             style={{cursor: 'pointer'}} | ||||
|             title="続きを読み込む" | ||||
|           ></i> | ||||
|         </div> | ||||
|       )} | ||||
|     </section> | ||||
|   ) | ||||
| } | ||||
| @@ -5,19 +5,22 @@ import ProfileRecordList from './ProfileRecordList.jsx' | ||||
| import LoadingSkeleton from './LoadingSkeleton.jsx' | ||||
| import { logger } from '../utils/logger.js' | ||||
|  | ||||
| export default function RecordTabs({ langRecords, commentRecords, userComments, chatRecords, userChatRecords, userChatLoading, baseRecords, apiConfig, pageContext, user = null, agent = null, onRecordDeleted = null }) { | ||||
| export default function RecordTabs({ langRecords, commentRecords, userComments, chatRecords, chatHasMore, onLoadMoreChat, userChatRecords, userChatLoading, baseRecords, apiConfig, pageContext, user = null, agent = null, onRecordDeleted = null }) { | ||||
|   const [activeTab, setActiveTab] = useState('profiles') | ||||
|    | ||||
|   logger.log('RecordTabs: activeTab is', activeTab) | ||||
|  | ||||
|   // Filter records based on page context | ||||
|   const filterRecords = (records, isProfile = false) => { | ||||
|     // Ensure records is an array | ||||
|     const recordsArray = Array.isArray(records) ? records : [] | ||||
|      | ||||
|     if (pageContext.isTopPage) { | ||||
|       // Top page: show latest 3 records | ||||
|       return records.slice(0, 3) | ||||
|       return recordsArray.slice(0, 3) | ||||
|     } else { | ||||
|       // Individual page: show records matching the URL | ||||
|       return records.filter(record => { | ||||
|       return recordsArray.filter(record => { | ||||
|         // Profile records should always be shown | ||||
|         if (isProfile || record.value?.type === 'profile') { | ||||
|           return true | ||||
| @@ -38,20 +41,25 @@ export default function RecordTabs({ langRecords, commentRecords, userComments, | ||||
|  | ||||
|   // Special filter for chat records (which are already processed into pairs) | ||||
|   const filterChatRecords = (chatPairs) => { | ||||
|     // Ensure chatPairs is an array | ||||
|     const chatArray = Array.isArray(chatPairs) ? chatPairs : [] | ||||
|      | ||||
|     console.log('filterChatRecords called:', {  | ||||
|       isTopPage: pageContext.isTopPage,  | ||||
|       rkey: pageContext.rkey,  | ||||
|       chatPairsLength: chatPairs.length  | ||||
|       chatPairsLength: chatArray.length, | ||||
|       chatPairsType: typeof chatPairs, | ||||
|       isArray: Array.isArray(chatPairs) | ||||
|     }) | ||||
|      | ||||
|     if (pageContext.isTopPage) { | ||||
|       // Top page: show latest 3 pairs | ||||
|       const result = chatPairs.slice(0, 3) | ||||
|       const result = chatArray.slice(0, 3) | ||||
|       console.log('Top page: returning', result.length, 'pairs') | ||||
|       return result | ||||
|     } else { | ||||
|       // Individual page: show pairs matching the URL (compare path only, ignore domain) | ||||
|       const filtered = chatPairs.filter(chatPair => { | ||||
|       const filtered = chatArray.filter(chatPair => { | ||||
|         const recordUrl = chatPair.question?.value?.post?.url | ||||
|         if (!recordUrl) { | ||||
|           console.log('No recordUrl for chatPair:', chatPair) | ||||
| @@ -82,14 +90,14 @@ export default function RecordTabs({ langRecords, commentRecords, userComments, | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const filteredLangRecords = filterRecords(langRecords) | ||||
|   const filteredCommentRecords = filterRecords(commentRecords) | ||||
|   const filteredUserComments = filterRecords(userComments || []) | ||||
|   const filteredChatRecords = filterChatRecords(chatRecords || []) | ||||
|   const filteredBaseRecords = filterRecords(baseRecords || []) | ||||
|   const filteredLangRecords = filterRecords(Array.isArray(langRecords) ? langRecords : []) | ||||
|   const filteredCommentRecords = filterRecords(Array.isArray(commentRecords) ? commentRecords : []) | ||||
|   const filteredUserComments = filterRecords(Array.isArray(userComments) ? userComments : []) | ||||
|   const filteredChatRecords = filterChatRecords(Array.isArray(chatRecords) ? chatRecords : []) | ||||
|   const filteredBaseRecords = filterRecords(Array.isArray(baseRecords) ? baseRecords : []) | ||||
|    | ||||
|   // Filter profile records from baseRecords | ||||
|   const profileRecords = (baseRecords || []).filter(record => record.value?.type === 'profile') | ||||
|   const profileRecords = (Array.isArray(baseRecords) ? baseRecords : []).filter(record => record.value?.type === 'profile') | ||||
|   const sortedProfileRecords = profileRecords.sort((a, b) => { | ||||
|     if (a.value.profileType === 'admin' && b.value.profileType !== 'admin') return -1 | ||||
|     if (a.value.profileType !== 'admin' && b.value.profileType === 'admin') return 1 | ||||
| @@ -171,7 +179,9 @@ export default function RecordTabs({ langRecords, commentRecords, userComments, | ||||
|             <LoadingSkeleton count={2} showTitle={true} /> | ||||
|           ) : ( | ||||
|             <ChatRecordList  | ||||
|               chatPairs={filteredChatRecords.length > 0 ? filteredChatRecords : userChatRecords}  | ||||
|               chatPairs={filteredChatRecords.length > 0 ? filteredChatRecords : (Array.isArray(userChatRecords) ? userChatRecords : [])}  | ||||
|               chatHasMore={filteredChatRecords.length > 0 ? chatHasMore : false} | ||||
|               onLoadMoreChat={filteredChatRecords.length > 0 ? onLoadMoreChat : null} | ||||
|               apiConfig={apiConfig}  | ||||
|               user={user} | ||||
|               agent={agent} | ||||
|   | ||||
| @@ -14,6 +14,8 @@ export function useAdminData() { | ||||
|   const [langRecords, setLangRecords] = useState([]) | ||||
|   const [commentRecords, setCommentRecords] = useState([]) | ||||
|   const [chatRecords, setChatRecords] = useState([]) | ||||
|   const [chatCursor, setChatCursor] = useState(null) | ||||
|   const [chatHasMore, setChatHasMore] = useState(true) | ||||
|   const [loading, setLoading] = useState(true) | ||||
|   const [error, setError] = useState(null) | ||||
|  | ||||
| @@ -31,19 +33,30 @@ export function useAdminData() { | ||||
|       const profile = await atproto.getProfile(apiConfig.bsky, did) | ||||
|        | ||||
|       // Load all data in parallel | ||||
|       const [records, lang, comment, chat] = await Promise.all([ | ||||
|       const [records, lang, comment, chatResult] = await Promise.all([ | ||||
|         collections.getBase(apiConfig.pds, did, env.collection), | ||||
|         collections.getLang(apiConfig.pds, did, env.collection), | ||||
|         collections.getComment(apiConfig.pds, did, env.collection), | ||||
|         collections.getChat(apiConfig.pds, did, env.collection) | ||||
|         collections.getChat(apiConfig.pds, did, env.collection, 10) | ||||
|       ]) | ||||
|        | ||||
|       const chat = chatResult.records || chatResult | ||||
|       const cursor = chatResult.cursor || null | ||||
|       setChatCursor(cursor) | ||||
|       setChatHasMore(!!cursor) | ||||
|  | ||||
|       console.log('useAdminData: chatResult structure:', chatResult) | ||||
|       console.log('useAdminData: chat variable type:', typeof chat, 'isArray:', Array.isArray(chat)) | ||||
|  | ||||
|       // Process chat records into question-answer pairs | ||||
|       const chatPairs = [] | ||||
|       const recordMap = new Map() | ||||
|        | ||||
|       // Ensure chat is an array | ||||
|       const chatArray = Array.isArray(chat) ? chat : [] | ||||
|        | ||||
|       // First pass: organize records by base rkey | ||||
|       chat.forEach(record => { | ||||
|       chatArray.forEach(record => { | ||||
|         const rkey = record.uri.split('/').pop() | ||||
|         const baseRkey = rkey.replace('-answer', '') | ||||
|          | ||||
| @@ -88,13 +101,74 @@ export function useAdminData() { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const loadMoreChat = async () => { | ||||
|     if (!chatCursor || !chatHasMore) return | ||||
|      | ||||
|     try { | ||||
|       const apiConfig = getApiConfig(`https://${env.pds}`) | ||||
|       const did = await atproto.getDid(env.pds, env.admin) | ||||
|       const chatResult = await collections.getChat(apiConfig.pds, did, env.collection, 10, chatCursor) | ||||
|        | ||||
|       const newChatRecords = chatResult.records || chatResult | ||||
|       const newCursor = chatResult.cursor || null | ||||
|        | ||||
|       // Process new chat records into question-answer pairs | ||||
|       const newChatPairs = [] | ||||
|       const recordMap = new Map() | ||||
|        | ||||
|       // Ensure newChatRecords is an array | ||||
|       const newChatArray = Array.isArray(newChatRecords) ? newChatRecords : [] | ||||
|        | ||||
|       // First pass: organize records by base rkey | ||||
|       newChatArray.forEach(record => { | ||||
|         const rkey = record.uri.split('/').pop() | ||||
|         const baseRkey = rkey.replace('-answer', '') | ||||
|          | ||||
|         if (!recordMap.has(baseRkey)) { | ||||
|           recordMap.set(baseRkey, { question: null, answer: null }) | ||||
|         } | ||||
|          | ||||
|         if (record.value.type === 'question') { | ||||
|           recordMap.get(baseRkey).question = record | ||||
|         } else if (record.value.type === 'answer') { | ||||
|           recordMap.get(baseRkey).answer = record | ||||
|         } | ||||
|       }) | ||||
|        | ||||
|       // Second pass: create chat pairs | ||||
|       recordMap.forEach((pair, rkey) => { | ||||
|         if (pair.question) { | ||||
|           newChatPairs.push({ | ||||
|             rkey, | ||||
|             question: pair.question, | ||||
|             answer: pair.answer, | ||||
|             createdAt: pair.question.value.createdAt | ||||
|           }) | ||||
|         } | ||||
|       }) | ||||
|        | ||||
|       // Sort new pairs by creation time (newest first) | ||||
|       newChatPairs.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) | ||||
|        | ||||
|       // Append to existing chat records | ||||
|       setChatRecords(prev => [...prev, ...newChatPairs]) | ||||
|       setChatCursor(newCursor) | ||||
|       setChatHasMore(!!newCursor) | ||||
|        | ||||
|     } catch (err) { | ||||
|       // Silently fail - no error logging | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|     adminData, | ||||
|     langRecords, | ||||
|     commentRecords, | ||||
|     chatRecords, | ||||
|     chatHasMore, | ||||
|     loading, | ||||
|     error, | ||||
|     refresh: loadAdminData | ||||
|     refresh: loadAdminData, | ||||
|     loadMoreChat | ||||
|   } | ||||
| } | ||||
| @@ -6,7 +6,7 @@ cb=ai.syui.log | ||||
| cl=($cb.chat) | ||||
| f=~/.config/syui/ai/log/config.json | ||||
|  | ||||
| default_collection="ai.syui.log.chat" | ||||
| #default_collection="ai.syui.log.chat" | ||||
| default_pds=syu.is | ||||
| default_did=`cat $f|jq -r .admin.did` | ||||
| default_token=`cat $f|jq -r .admin.access_jwt` | ||||
|   | ||||
		Reference in New Issue
	
	Block a user