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