217 lines
7.7 KiB
JavaScript
217 lines
7.7 KiB
JavaScript
import { useState, useEffect } from 'react'
|
|
import { atproto, collections } from '../api/atproto.js'
|
|
import { getApiConfig, isSyuIsHandle, getPdsFromHandle } from '../utils/pds.js'
|
|
import { env } from '../config/env.js'
|
|
import { logger } from '../utils/logger.js'
|
|
|
|
export function useUserData(adminData) {
|
|
const [userComments, setUserComments] = useState([])
|
|
const [chatRecords, setChatRecords] = useState([])
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState(null)
|
|
|
|
useEffect(() => {
|
|
if (!adminData?.did || !adminData?.apiConfig) return
|
|
|
|
const fetchUserData = async () => {
|
|
setLoading(true)
|
|
setError(null)
|
|
|
|
try {
|
|
// 1. Get user list from admin account
|
|
const userListRecords = await collections.getUserList(
|
|
adminData.apiConfig.pds,
|
|
adminData.did,
|
|
env.collection
|
|
)
|
|
|
|
// 2. Get chat records from ai.syui.log.chat and process into pairs
|
|
const chatResult = await collections.getChat(
|
|
adminData.apiConfig.pds,
|
|
adminData.did,
|
|
env.collection
|
|
)
|
|
|
|
const chatRecords = chatResult.records || chatResult
|
|
logger.log('useUserData: raw chatRecords:', chatRecords.length, chatRecords)
|
|
|
|
// Process chat records into question-answer pairs
|
|
const chatPairs = []
|
|
const recordMap = new Map()
|
|
|
|
// First pass: organize records by base rkey
|
|
chatRecords.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) {
|
|
chatPairs.push({
|
|
rkey,
|
|
question: pair.question,
|
|
answer: pair.answer,
|
|
createdAt: pair.question.value.createdAt
|
|
})
|
|
}
|
|
})
|
|
|
|
// Sort by creation time (newest first)
|
|
chatPairs.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
|
|
|
logger.log('useUserData: processed chatPairs:', chatPairs.length, chatPairs)
|
|
setChatRecords(chatPairs)
|
|
|
|
// 3. Get base collection records which contain user comments
|
|
const baseRecords = await collections.getBase(
|
|
adminData.apiConfig.pds,
|
|
adminData.did,
|
|
env.collection
|
|
)
|
|
|
|
// Extract comments from base records
|
|
const allUserComments = []
|
|
|
|
for (const record of baseRecords) {
|
|
if (record.value?.comments && Array.isArray(record.value.comments)) {
|
|
// Each comment already has author info, so we can use it directly
|
|
const commentsWithMeta = record.value.comments.map(comment => ({
|
|
uri: record.uri,
|
|
cid: record.cid,
|
|
value: {
|
|
...comment,
|
|
post: {
|
|
url: record.value.url
|
|
}
|
|
}
|
|
}))
|
|
allUserComments.push(...commentsWithMeta)
|
|
}
|
|
}
|
|
|
|
// Also try to get individual user records from the user list
|
|
// Currently skipping user list processing since users contain placeholder DIDs
|
|
if (userListRecords.length > 0 && userListRecords[0].value?.users) {
|
|
logger.log('User list found, but skipping placeholder users for now')
|
|
|
|
// Filter out placeholder users
|
|
const realUsers = userListRecords[0].value.users.filter(user =>
|
|
user.handle &&
|
|
user.did &&
|
|
!user.did.includes('placeholder') &&
|
|
!user.did.includes('example')
|
|
)
|
|
|
|
if (realUsers.length > 0) {
|
|
logger.log(`Processing ${realUsers.length} real users`)
|
|
|
|
for (const user of realUsers) {
|
|
const userHandle = user.handle
|
|
|
|
try {
|
|
// Get user's DID and PDS using PDS detection logic
|
|
let userDid, userPds, userApiConfig
|
|
|
|
if (user.did && user.pds) {
|
|
// Use DID and PDS from user record
|
|
userDid = user.did
|
|
userPds = user.pds.replace('https://', '')
|
|
userApiConfig = getApiConfig(userPds)
|
|
} else {
|
|
// Always get actual PDS from describeRepo first
|
|
try {
|
|
// Try bsky.social first for most handles
|
|
const bskyPds = 'bsky.social'
|
|
userDid = await atproto.getDid(bskyPds, userHandle)
|
|
|
|
// Get the actual PDS endpoint from DID
|
|
const realPds = await getPdsFromHandle(userHandle)
|
|
userPds = realPds.replace('https://', '')
|
|
userApiConfig = getApiConfig(realPds)
|
|
} catch (error) {
|
|
// Fallback to syu.is if bsky.social fails
|
|
logger.warn(`Failed to get PDS for ${userHandle} from bsky.social, trying syu.is:`, error)
|
|
userPds = env.pds
|
|
userApiConfig = getApiConfig(env.pds)
|
|
userDid = await atproto.getDid(userPds, userHandle)
|
|
}
|
|
}
|
|
|
|
// Get user's own ai.syui.log records
|
|
const userRecords = await collections.getUserComments(
|
|
userApiConfig.pds,
|
|
userDid,
|
|
env.collection
|
|
)
|
|
|
|
// Skip if no records found
|
|
if (!userRecords || userRecords.length === 0) {
|
|
continue
|
|
}
|
|
|
|
// Get user's profile for enrichment
|
|
let profile = null
|
|
try {
|
|
profile = await atproto.getProfile(userApiConfig.bsky, userDid)
|
|
} catch (profileError) {
|
|
logger.warn(`Failed to get profile for ${userHandle}:`, profileError)
|
|
}
|
|
|
|
// Add profile info to each record
|
|
const enrichedRecords = userRecords.map(record => ({
|
|
...record,
|
|
value: {
|
|
...record.value,
|
|
author: {
|
|
did: userDid,
|
|
handle: profile?.data?.handle || userHandle,
|
|
displayName: profile?.data?.displayName || userHandle,
|
|
avatar: profile?.data?.avatar || null
|
|
}
|
|
}
|
|
}))
|
|
|
|
allUserComments.push(...enrichedRecords)
|
|
} catch (userError) {
|
|
logger.warn(`Failed to fetch data for user ${userHandle}:`, userError)
|
|
}
|
|
}
|
|
} else {
|
|
logger.log('No real users found in user list - all appear to be placeholders')
|
|
}
|
|
}
|
|
|
|
setUserComments(allUserComments)
|
|
} catch (err) {
|
|
setError(err.message)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
fetchUserData()
|
|
}, [adminData])
|
|
|
|
const refresh = () => {
|
|
if (adminData?.did && adminData?.apiConfig) {
|
|
// Re-trigger the effect by clearing and re-setting adminData
|
|
const currentAdminData = adminData
|
|
setUserComments([])
|
|
setChatRecords([])
|
|
// The useEffect will automatically run again
|
|
}
|
|
}
|
|
|
|
return { userComments, chatRecords, loading, error, refresh }
|
|
} |