11 Commits

Author SHA1 Message Date
292bf39c4e fix 2025-06-17 14:15:34 +09:00
be8fd38c59 fix 2025-06-17 14:13:11 +09:00
abc42330ed fix 2025-06-17 14:11:27 +09:00
2143d5b142 fix oauth 2025-06-17 14:00:36 +09:00
549f728b7a fix oauth 2025-06-17 13:58:00 +09:00
849490f5b2 fix oauth 2025-06-17 13:45:40 +09:00
c1d3678d96 fix oauth 2025-06-17 13:43:34 +09:00
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
5 changed files with 102 additions and 39 deletions

View File

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

View File

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

View File

@@ -221,13 +221,25 @@ function App() {
const oauthResult = await atprotoOAuthService.checkSession();
if (oauthResult) {
console.log('OAuth result found:', oauthResult);
// Ensure handle is not DID
const handle = oauthResult.handle !== oauthResult.did ? oauthResult.handle : oauthResult.handle;
console.log('Processed handle:', handle);
// Note: appConfig.allowedHandles is used for PDS detection, not access control
// Check if handle is allowed
if (appConfig.allowedHandles.length > 0 && !appConfig.allowedHandles.includes(handle)) {
console.log('Handle not allowed:', handle, 'Allowed:', appConfig.allowedHandles);
// Handle not in allowed list
setError(`Access denied: ${handle} is not authorized for this application.`);
setIsLoading(false);
return;
}
console.log('Getting user profile for:', oauthResult.did, handle);
// Get user profile including avatar
const userProfile = await getUserProfile(oauthResult.did, handle);
console.log('User profile received:', userProfile);
setUser(userProfile);
// Load all comments for display (this will be the default view)

View File

@@ -12,7 +12,6 @@ interface AtprotoSession {
class AtprotoOAuthService {
private oauthClient: BrowserOAuthClient | null = null;
private oauthClientSyuIs: BrowserOAuthClient | null = null;
private agent: Agent | null = null;
private initializePromise: Promise<void> | null = null;
@@ -32,27 +31,23 @@ class AtprotoOAuthService {
private async _doInitialize(): Promise<void> {
try {
// Generate client ID based on current origin
const clientId = this.getClientId();
// Initialize both OAuth clients
// Support multiple PDS hosts for OAuth
this.oauthClient = await BrowserOAuthClient.load({
clientId: clientId,
handleResolver: 'https://bsky.social',
plcDirectoryUrl: 'https://plc.directory',
});
this.oauthClientSyuIs = await BrowserOAuthClient.load({
clientId: clientId,
handleResolver: 'https://syu.is',
plcDirectoryUrl: 'https://plc.syu.is',
handleResolver: 'https://bsky.social', // Default resolver
plcDirectoryUrl: 'https://plc.directory', // Default PLC directory
});
// Try to restore existing session from either client
let result = await this.oauthClient.init();
if (!result?.session) {
result = await this.oauthClientSyuIs.init();
}
// Try to restore existing session
const result = await this.oauthClient.init();
if (result?.session) {
// Create Agent instance with proper configuration
@@ -182,15 +177,55 @@ class AtprotoOAuthService {
return `${origin}/client-metadata.json`;
}
private async detectPDSFromHandle(handle: string): Promise<string> {
// Handle detection for OAuth PDS routing
// Check if handle ends with known PDS domains first
const pdsMapping = {
'syu.is': 'https://syu.is',
'bsky.social': 'https://bsky.social',
};
for (const [domain, pdsUrl] of Object.entries(pdsMapping)) {
if (handle.endsWith(`.${domain}`)) {
return pdsUrl;
}
}
// For handles that don't match domain patterns, resolve via API
try {
// Try to resolve handle to get the actual PDS
const endpoints = ['https://syu.is', 'https://bsky.social'];
for (const endpoint of endpoints) {
try {
const response = await fetch(`${endpoint}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`);
if (response.ok) {
const data = await response.json();
if (data.did) {
return endpoint;
}
}
} catch (e) {
continue;
}
}
} catch (e) {
// Handle resolution failed, using default
}
// Default to bsky.social
return 'https://bsky.social';
}
async initiateOAuthFlow(handle?: string): Promise<void> {
try {
if (!this.oauthClient || !this.oauthClientSyuIs) {
if (!this.oauthClient) {
await this.initialize();
}
if (!this.oauthClient || !this.oauthClientSyuIs) {
throw new Error('Failed to initialize OAuth clients');
if (!this.oauthClient) {
throw new Error('Failed to initialize OAuth client');
}
// If handle is not provided, prompt user
@@ -201,27 +236,43 @@ class AtprotoOAuthService {
}
}
// Determine which OAuth client to use
const allowedHandlesStr = import.meta.env.VITE_ATPROTO_HANDLE_LIST || '[]';
let allowedHandles: string[] = [];
try {
allowedHandles = JSON.parse(allowedHandlesStr);
} catch {
allowedHandles = [];
// Detect PDS based on handle
const pdsUrl = await this.detectPDSFromHandle(handle);
// Re-initialize OAuth client with correct PDS if needed
if (pdsUrl !== 'https://bsky.social') {
// Determine PLC directory for syu.is only
const plcDirectoryUrl = handle.endsWith('.syu.is') || handle.endsWith('.syui.ai') ? 'https://plc.syu.is' : 'https://plc.directory';
this.oauthClient = await BrowserOAuthClient.load({
clientId: this.getClientId(),
handleResolver: pdsUrl,
plcDirectoryUrl: plcDirectoryUrl,
});
}
const usesSyuIs = handle.endsWith('.syu.is') || allowedHandles.includes(handle);
const oauthClient = usesSyuIs ? this.oauthClientSyuIs : this.oauthClient;
// Start OAuth authorization flow
const authUrl = await oauthClient.authorize(handle, {
scope: 'atproto transition:generic',
});
// OAuth client initialized
// Redirect to authorization server
window.location.href = authUrl.toString();
// Start OAuth authorization flow
try {
// Starting OAuth authorization
// Use handle directly since PLC directory is now correctly configured
const authUrl = await this.oauthClient.authorize(handle, {
scope: 'atproto transition:generic',
});
// Redirect to authorization server
window.location.href = authUrl.toString();
} catch (authorizeError) {
// Authorization failed
throw authorizeError;
}
} catch (error) {
throw new Error(`OAuth認証の開始に失敗しました: ${error}`);
}
}

View File

@@ -3,10 +3,10 @@
set -e
cb=ai.syui.log
cl=( $cb.chat.lang $cb.chat $cb.chat.comment $cb $cb.user )
cl=( $cb.user )
f=~/.config/syui/ai/log/config.json
default_collection="ai.syui.log.chat"
default_collection="ai.syui.log.chat.comment"
default_pds="syu.is"
default_did=`cat $f|jq -r .admin.did`
default_token=`cat $f|jq -r .admin.access_jwt`