test cli stream

This commit is contained in:
2025-06-16 22:09:04 +09:00
parent 889ce8baa1
commit 1e83b50e3f
12 changed files with 776 additions and 82 deletions

View File

@ -31,7 +31,33 @@ function App() {
const [aiCommentRecords, setAiCommentRecords] = useState<any[]>([]);
const [aiProfile, setAiProfile] = useState<any>(null);
const [adminDid, setAdminDid] = useState<string | null>(null);
const [aiDid, setAiDid] = useState<string | null>(null);
// ハンドルからDIDを解決する関数
const resolveHandleToDid = async (handle: string): Promise<string | null> => {
try {
const profile = await import('./utils/pds-detection').then(m => m.getProfileForUser(handle));
return profile?.did || null;
} catch {
return null;
}
};
useEffect(() => {
// 管理者とAIのDIDを解決
const resolveAdminAndAiDids = async () => {
const [resolvedAdminDid, resolvedAiDid] = await Promise.all([
resolveHandleToDid(appConfig.adminHandle),
resolveHandleToDid(appConfig.aiHandle)
]);
setAdminDid(resolvedAdminDid || appConfig.adminDid);
setAiDid(resolvedAiDid || appConfig.aiDid);
};
resolveAdminAndAiDids();
// Setup Jetstream WebSocket for real-time comments (optional)
const setupJetstream = () => {
try {
@ -83,13 +109,28 @@ function App() {
return false;
};
// キャッシュがなければ、ATProtoから取得認証状態に関係なく
if (!loadCachedComments()) {
loadAllComments(); // URLフィルタリングを無効にして全コメント表示
}
// DID解決が完了してからコメントとチャット履歴を読み込む
const loadDataAfterDidResolution = () => {
// キャッシュがなければ、ATProtoから取得認証状態に関係なく
if (!loadCachedComments()) {
loadAllComments(); // URLフィルタリングを無効にして全コメント表示
}
// Load AI chat history (認証状態に関係なく、全ユーザーのチャット履歴を表示)
loadAiChatHistory();
};
// Load AI chat history (認証状態に関係なく、全ユーザーのチャット履歴を表示)
loadAiChatHistory();
// Wait for DID resolution before loading data
if (adminDid && aiDid) {
loadDataAfterDidResolution();
} else {
// Wait a bit and try again
setTimeout(() => {
if (adminDid && aiDid) {
loadDataAfterDidResolution();
}
}, 1000);
}
// Load AI profile from handle
const loadAiProfile = async () => {
@ -198,7 +239,7 @@ function App() {
loadAiChatHistory();
// Load user list records if admin
if (userProfile.did === appConfig.adminDid) {
if (userProfile.did === adminDid) {
loadUserListRecords();
}
@ -224,7 +265,7 @@ function App() {
loadAllComments();
// Load user list records if admin
if (verifiedUser.did === appConfig.adminDid) {
if (verifiedUser.did === adminDid) {
loadUserListRecords();
}
}
@ -244,6 +285,15 @@ function App() {
};
}, []);
// DID解決完了時にデータを再読み込み
useEffect(() => {
if (adminDid && aiDid) {
console.log('DIDs resolved, loading comments and chat history...');
loadAllComments();
loadAiChatHistory();
}
}, [adminDid, aiDid]);
const getUserProfile = async (did: string, handle: string): Promise<User> => {
try {
const agent = atprotoOAuthService.getAgent();
@ -281,21 +331,21 @@ function App() {
const loadAiChatHistory = async () => {
try {
// Load all chat records from users in admin's user list
const adminDid = appConfig.adminDid;
const currentAdminDid = adminDid || appConfig.adminDid;
const atprotoApi = appConfig.atprotoApi || 'https://bsky.social';
const collections = getCollectionNames(appConfig.collections.base);
// First, get user list from admin using their proper PDS
let adminPdsEndpoint;
try {
const resolved = await import('./utils/pds-detection').then(m => m.resolvePdsFromRepo(adminDid));
const resolved = await import('./utils/pds-detection').then(m => m.resolvePdsFromRepo(currentAdminDid));
const config = await import('./utils/pds-detection').then(m => m.getNetworkConfigFromPdsEndpoint(resolved.pds));
adminPdsEndpoint = config.pdsApi;
} catch {
adminPdsEndpoint = atprotoApi;
}
const userListResponse = await fetch(`${adminPdsEndpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(adminDid)}&collection=${encodeURIComponent(collections.user)}&limit=100`);
const userListResponse = await fetch(`${adminPdsEndpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(currentAdminDid)}&collection=${encodeURIComponent(collections.user)}&limit=100`);
if (!userListResponse.ok) {
setAiChatHistory([]);
@ -318,7 +368,7 @@ function App() {
});
// Always include admin DID to check admin's own chats
allUserDids.push(adminDid);
allUserDids.push(currentAdminDid);
const userDids = [...new Set(allUserDids)];
@ -386,7 +436,7 @@ function App() {
// Load AI generated content from admin DID
const loadAIGeneratedContent = async () => {
try {
const adminDid = appConfig.adminDid;
const currentAdminDid = adminDid || appConfig.adminDid;
const atprotoApi = appConfig.atprotoApi || 'https://bsky.social';
const collections = getCollectionNames(appConfig.collections.base);
@ -505,32 +555,40 @@ function App() {
const loadUsersFromRecord = async () => {
try {
// 管理者のユーザーリストを取得 using proper PDS detection
const adminDid = appConfig.adminDid;
const currentAdminDid = adminDid || appConfig.adminDid;
console.log('loadUsersFromRecord: Using Admin DID:', currentAdminDid);
// Use per-user PDS detection for admin's records
let adminPdsEndpoint;
try {
const resolved = await import('./utils/pds-detection').then(m => m.resolvePdsFromRepo(adminDid));
const resolved = await import('./utils/pds-detection').then(m => m.resolvePdsFromRepo(currentAdminDid));
const config = await import('./utils/pds-detection').then(m => m.getNetworkConfigFromPdsEndpoint(resolved.pds));
adminPdsEndpoint = config.pdsApi;
} catch {
adminPdsEndpoint = 'https://bsky.social'; // Fallback
}
const response = await fetch(`${adminPdsEndpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(adminDid)}&collection=${encodeURIComponent(getCollectionNames(appConfig.collections.base).user)}&limit=100`);
const userCollectionUrl = `${adminPdsEndpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(currentAdminDid)}&collection=${encodeURIComponent(getCollectionNames(appConfig.collections.base).user)}&limit=100`;
console.log('loadUsersFromRecord: Fetching from URL:', userCollectionUrl);
const response = await fetch(userCollectionUrl);
console.log('loadUsersFromRecord: Response status:', response.status);
if (!response.ok) {
// Failed to fetch user list from admin, using default users
console.log('loadUsersFromRecord: Failed to fetch, using default users');
return getDefaultUsers();
}
const data = await response.json();
const userRecords = data.records || [];
// User records found
console.log('loadUsersFromRecord: Found user records:', userRecords.length);
if (userRecords.length === 0) {
// No user records found, using default users
return getDefaultUsers();
console.log('loadUsersFromRecord: No user records found, using default users');
const defaultUsers = getDefaultUsers();
console.log('loadUsersFromRecord: Default users:', defaultUsers);
return defaultUsers;
}
// レコードからユーザーリストを構築し、プレースホルダーDIDを実際のDIDに解決
@ -562,7 +620,7 @@ function App() {
}
}
// Loaded and resolved users from admin records
console.log('loadUsersFromRecord: Resolved users:', allUsers);
return allUsers;
} catch (err) {
// Failed to load users from records, using defaults
@ -574,19 +632,19 @@ function App() {
const loadUserListRecords = async () => {
try {
// Loading user list records using proper PDS detection
const adminDid = appConfig.adminDid;
const currentAdminDid = adminDid || appConfig.adminDid;
// Use per-user PDS detection for admin's records
let adminPdsEndpoint;
try {
const resolved = await import('./utils/pds-detection').then(m => m.resolvePdsFromRepo(adminDid));
const resolved = await import('./utils/pds-detection').then(m => m.resolvePdsFromRepo(currentAdminDid));
const config = await import('./utils/pds-detection').then(m => m.getNetworkConfigFromPdsEndpoint(resolved.pds));
adminPdsEndpoint = config.pdsApi;
} catch {
adminPdsEndpoint = 'https://bsky.social'; // Fallback
}
const response = await fetch(`${adminPdsEndpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(adminDid)}&collection=${encodeURIComponent(getCollectionNames(appConfig.collections.base).user)}&limit=100`);
const response = await fetch(`${adminPdsEndpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(currentAdminDid)}&collection=${encodeURIComponent(getCollectionNames(appConfig.collections.base).user)}&limit=100`);
if (!response.ok) {
// Failed to fetch user list records
@ -611,21 +669,27 @@ function App() {
};
const getDefaultUsers = () => {
const currentAdminDid = adminDid || appConfig.adminDid;
const defaultUsers = [
// Default admin user
{ did: appConfig.adminDid, handle: 'syui.ai', pds: 'https://bsky.social' },
{ did: currentAdminDid, handle: appConfig.adminHandle, pds: 'https://syu.is' },
];
// 現在ログインしているユーザーも追加(重複チェック)
if (user && user.did && user.handle && !defaultUsers.find(u => u.did === user.did)) {
// Detect PDS based on handle
const userPds = user.handle.endsWith('.syu.is') ? 'https://syu.is' :
user.handle.endsWith('.syui.ai') ? 'https://syu.is' :
'https://bsky.social';
defaultUsers.push({
did: user.did,
handle: user.handle,
pds: user.handle.endsWith('.syu.is') ? 'https://syu.is' : 'https://bsky.social'
pds: userPds
});
}
// Default users list (including current user)
console.log('getDefaultUsers: Returning default users:', defaultUsers);
return defaultUsers;
};
@ -635,6 +699,7 @@ function App() {
// ユーザーリストを動的に取得
const knownUsers = await loadUsersFromRecord();
console.log('loadAllComments: Using users for comment fetching:', knownUsers);
const allComments = [];
@ -888,7 +953,7 @@ function App() {
// 管理者チェック
const isAdmin = (user: User | null): boolean => {
return user?.did === appConfig.adminDid;
return user?.did === adminDid || user?.did === appConfig.adminDid;
};
// ユーザーリスト投稿
@ -1182,6 +1247,25 @@ function App() {
return (
<div className="app">
{/* Debug Info */}
<div style={{
padding: '10px',
backgroundColor: '#f0f0f0',
border: '1px solid #ccc',
marginBottom: '10px',
fontSize: '12px'
}}>
<strong>Debug Info:</strong><br />
Admin Handle: {appConfig.adminHandle}<br />
Admin DID (resolved): {adminDid || 'resolving...'}<br />
Admin DID (config): {appConfig.adminDid}<br />
AI Handle: {appConfig.aiHandle}<br />
AI DID (resolved): {aiDid || 'resolving...'}<br />
AI DID (config): {appConfig.aiDid}<br />
Collection Base: {appConfig.collections.base}<br />
User Collection: {appConfig.collections.base}.user<br />
DIDs Resolved: {adminDid && aiDid ? 'Yes' : 'No'}
</div>
<main className="app-main">
<section className="comment-section">
@ -1442,7 +1526,7 @@ function App() {
aiChatHistory.map((record, index) => {
// For AI responses, use AI DID; for user questions, use the actual author
const isAiResponse = record.value.type === 'answer';
const displayDid = isAiResponse ? appConfig.aiDid : record.value.author?.did;
const displayDid = isAiResponse ? (aiDid || appConfig.aiDid) : record.value.author?.did;
const displayHandle = isAiResponse ? (aiProfile?.handle || 'yui.syui.ai') : record.value.author?.handle;
const displayName = isAiResponse ? 'AI' : (record.value.author?.displayName || record.value.author?.handle);
@ -1558,8 +1642,9 @@ function App() {
className="comment-avatar"
ref={(img) => {
// Fetch AI avatar
if (img && appConfig.aiDid) {
fetch(`${appConfig.bskyPublicApi}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(appConfig.aiDid)}`)
const currentAiDid = aiDid || appConfig.aiDid;
if (img && currentAiDid) {
fetch(`${appConfig.bskyPublicApi}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(currentAiDid)}`)
.then(res => res.json())
.then(data => {
if (data.avatar && img) {

View File

@ -84,10 +84,12 @@ function extractRkeyFromUrl(): string | undefined {
// Get application configuration from environment variables
export function getAppConfig(): AppConfig {
const host = import.meta.env.VITE_APP_HOST || 'https://log.syui.ai';
const adminHandle = import.meta.env.VITE_ADMIN_HANDLE || 'ai.syui.ai';
const aiHandle = import.meta.env.VITE_AI_HANDLE || 'ai.syui.ai';
// DIDsはハンドルから実行時に解決されるフォールバック用のみ保持
const adminDid = import.meta.env.VITE_ADMIN_DID || 'did:plc:uqzpqmrjnptsxezjx4xuh2mn';
const adminHandle = import.meta.env.VITE_ADMIN_HANDLE || 'syui.ai';
const aiDid = import.meta.env.VITE_AI_DID || 'did:plc:4hqjfn7m6n5hno3doamuhgef';
const aiHandle = import.meta.env.VITE_AI_HANDLE || 'yui.syui.ai';
const aiDisplayName = import.meta.env.VITE_AI_DISPLAY_NAME || 'ai';
const aiAvatar = import.meta.env.VITE_AI_AVATAR || '';
const aiDescription = import.meta.env.VITE_AI_DESCRIPTION || '';