6 Commits

Author SHA1 Message Date
fcab7c7f83 fix oauth 2025-06-17 13:19:31 +09:00
51e4a492bc fix: display correct author for user chat messages
Previously, all chat messages in the 'chat' tab were showing with AI account info
regardless of the actual author. This fix ensures the author information from
the record is used, only falling back to AI profile when no author is present.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-17 13:11:04 +09:00
9ed35a6a1e fix oauth 2025-06-17 13:05:01 +09:00
74e1014e77 fix oauth plc 2025-06-17 12:48:55 +09:00
820e47f634 update binary 2025-06-17 11:01:42 +09:00
4dac4a83e0 fix atproto web link 2025-06-17 11:00:09 +09:00
6 changed files with 72 additions and 42 deletions

1
.gitignore vendored
View File

@@ -17,3 +17,4 @@ my-blog/templates/oauth-assets.html
cloudflared-config.yml
.config
oauth-server-example
atproto

Binary file not shown.

View File

@@ -17,7 +17,7 @@ comment_moderation = false
ask_ai = true
provider = "ollama"
model = "gemma3:4b"
host = "https://localhost:11434"
host = "http://localhost:11434"
system_prompt = "あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。"
handle = "ai.syui.ai"
#num_predict = 200

View File

@@ -16,5 +16,5 @@ VITE_AI_ENABLED=true
VITE_AI_ASK_AI=true
VITE_AI_PROVIDER=ollama
VITE_AI_MODEL=gemma3:4b
VITE_AI_HOST=https://localhost:11434
VITE_AI_HOST=http://localhost:11434
VITE_AI_SYSTEM_PROMPT="あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。"

View File

@@ -550,6 +550,13 @@ function App() {
const profileResponse = await fetch(`${apiEndpoint}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(handle)}`);
if (profileResponse.ok) {
const profileData = await profileResponse.json();
// Determine correct web URL based on avatar source
let webUrl = config.webUrl; // Default from handle-based detection
if (profileData.avatar && profileData.avatar.includes('cdn.bsky.app')) {
webUrl = 'https://bsky.app'; // Override to Bluesky if avatar is from Bluesky
}
return {
...record,
value: {
@@ -559,7 +566,7 @@ function App() {
avatar: profileData.avatar,
displayName: profileData.displayName || handle,
_pdsEndpoint: `https://${pds}`, // Store PDS info for later use
_webUrl: config.webUrl, // Store web URL for profile links
_webUrl: webUrl, // Store corrected web URL for profile links
}
}
};
@@ -810,6 +817,14 @@ function App() {
const profile = await import('./utils/pds-detection').then(m => m.getProfileForUser(record.value.author.handle));
if (profile) {
// Determine network config based on profile data
let webUrl = 'https://bsky.app'; // Default to Bluesky
if (profile.avatar && profile.avatar.includes('cdn.bsky.app')) {
webUrl = 'https://bsky.app';
} else if (profile.avatar && profile.avatar.includes('bsky.syu.is')) {
webUrl = 'https://web.syu.is';
}
return {
...record,
value: {
@@ -818,6 +833,7 @@ function App() {
...record.value.author,
avatar: profile.avatar,
displayName: profile.displayName || record.value.author.handle,
_webUrl: webUrl, // Store network config for profile URL generation
}
}
};
@@ -1146,7 +1162,18 @@ function App() {
return `${config.webUrl}/profile/${author.did}`;
}
// Get PDS from handle for other users
// For other users, detect network based on avatar URL or stored network info
if (author.avatar && author.avatar.includes('cdn.bsky.app')) {
// User has Bluesky avatar, use Bluesky web interface
return `https://bsky.app/profile/${author.did}`;
}
// Check if we have stored network config from profile fetching
if (author._webUrl) {
return `${author._webUrl}/profile/${author.did}`;
}
// Fallback: Get PDS from handle for other users
const pds = detectPdsFromHandle(author.handle);
const config = getNetworkConfig(pds);
@@ -1186,8 +1213,8 @@ function App() {
// Extract content based on format
const contentText = isNewFormat ? value.text : (value.content || value.body || '');
// For AI comments, always use the loaded AI profile instead of record.value.author
const authorInfo = aiProfile;
// Use the author from the record if available, otherwise fall back to AI profile
const authorInfo = value.author || aiProfile;
const postInfo = isNewFormat ? value.post : null;
const contentType = value.type || 'unknown';
const createdAt = value.createdAt || value.generated_at || '';

View File

@@ -38,10 +38,13 @@ class AtprotoOAuthService {
// Support multiple PDS hosts for OAuth
console.log('[OAuth Debug] Initializing OAuth client with default settings');
this.oauthClient = await BrowserOAuthClient.load({
clientId: clientId,
handleResolver: 'https://bsky.social', // Default resolver
plcDirectoryUrl: 'https://plc.directory', // Default PLC directory
});
console.log('[OAuth Debug] OAuth client initialized with defaults');
@@ -115,23 +118,24 @@ class AtprotoOAuthService {
// Create Agent directly with session (per official docs)
try {
this.agent = new Agent(session);
console.log('[OAuth Debug] Agent created successfully with session');
// Check if agent has session info after creation
if (this.agent.session) {
console.log('[OAuth Debug] Agent has session:', this.agent.session);
}
} catch (err) {
console.log('[OAuth Debug] Failed to create agent with session:', err);
// Fallback to dpopFetch method
this.agent = new Agent({
service: session.server?.serviceEndpoint || 'https://bsky.social',
fetch: session.dpopFetch
});
try {
this.agent = new Agent({
service: session.server?.serviceEndpoint || 'https://bsky.social',
fetch: session.dpopFetch
});
console.log('[OAuth Debug] Agent created with dpopFetch fallback');
} catch (fallbackErr) {
console.error('[OAuth Debug] Failed to create agent with fallback:', fallbackErr);
}
}
// Store basic session info
@@ -273,21 +277,30 @@ class AtprotoOAuthService {
// Detect PDS based on handle
const pdsUrl = await this.detectPDSFromHandle(handle);
// Starting OAuth flow
console.log('[OAuth Debug] Detected PDS for handle', handle, ':', pdsUrl);
// Always re-initialize OAuth client with detected PDS
// Re-initializing OAuth client
console.log('[OAuth Debug] Re-initializing OAuth client');
// Clear existing client to force fresh initialization
this.oauthClient = null;
this.initializePromise = null;
// Determine PLC directory based on input handle, not environment PDS
let plcDirectoryUrl = 'https://plc.directory'; // Default to Bluesky PLC
if (handle.endsWith('.syu.is') || handle.endsWith('.syui.ai')) {
plcDirectoryUrl = 'https://plc.syu.is';
}
console.log('[OAuth Debug] Using PLC directory:', plcDirectoryUrl);
this.oauthClient = await BrowserOAuthClient.load({
clientId: this.getClientId(),
handleResolver: pdsUrl,
plcDirectoryUrl: plcDirectoryUrl,
});
console.log('[OAuth Debug] OAuth client re-initialized successfully');
// OAuth client initialized
// Start OAuth authorization flow
@@ -296,22 +309,8 @@ class AtprotoOAuthService {
try {
// Starting OAuth authorization
// Try to authorize with DID instead of handle for syu.is PDS only
let authTarget = handle;
if (pdsUrl === 'https://syu.is') {
try {
const resolveResponse = await fetch(`${pdsUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`);
if (resolveResponse.ok) {
const resolveData = await resolveResponse.json();
authTarget = resolveData.did;
// Using DID for syu.is OAuth workaround
}
} catch (e) {
// Could not resolve to DID, using handle
}
}
const authUrl = await this.oauthClient.authorize(authTarget, {
// Use handle directly since PLC directory is now correctly configured
const authUrl = await this.oauthClient.authorize(handle, {
scope: 'atproto transition:generic',
});
@@ -379,27 +378,30 @@ class AtprotoOAuthService {
async checkSession(): Promise<{ did: string; handle: string } | null> {
try {
console.log('[OAuth Debug] Checking session...');
if (!this.oauthClient) {
console.log('[OAuth Debug] No OAuth client, initializing...');
await this.initialize();
}
if (!this.oauthClient) {
console.log('[OAuth Debug] Failed to initialize OAuth client');
return null;
}
const result = await this.oauthClient.init();
console.log('[OAuth Debug] OAuth init result:', !!result?.session);
if (result?.session) {
console.log('[OAuth Debug] Session found, processing...');
// Use the common session processing method
return this.processSession(result.session);
const sessionData = await this.processSession(result.session);
console.log('[OAuth Debug] Processed session data:', sessionData);
return sessionData;
}
console.log('[OAuth Debug] No session found');
return null;
} catch (error) {