fix oauth plc
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -17,3 +17,4 @@ my-blog/templates/oauth-assets.html
 | 
				
			|||||||
cloudflared-config.yml
 | 
					cloudflared-config.yml
 | 
				
			||||||
.config
 | 
					.config
 | 
				
			||||||
oauth-server-example
 | 
					oauth-server-example
 | 
				
			||||||
 | 
					atproto
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,8 +16,10 @@ auto_translate = false
 | 
				
			|||||||
comment_moderation = false
 | 
					comment_moderation = false
 | 
				
			||||||
ask_ai = true
 | 
					ask_ai = true
 | 
				
			||||||
provider = "ollama"
 | 
					provider = "ollama"
 | 
				
			||||||
model = "gemma3:4b"
 | 
					model = "qwen3"
 | 
				
			||||||
host = "https://localhost:11434"
 | 
					model_translation = "llama3.2:1b"
 | 
				
			||||||
 | 
					model_technical = "phi3:mini"
 | 
				
			||||||
 | 
					host = "http://localhost:11434"
 | 
				
			||||||
system_prompt = "あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。"
 | 
					system_prompt = "あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。"
 | 
				
			||||||
handle = "ai.syui.ai"
 | 
					handle = "ai.syui.ai"
 | 
				
			||||||
#num_predict = 200
 | 
					#num_predict = 200
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,5 +16,5 @@ VITE_AI_ENABLED=true
 | 
				
			|||||||
VITE_AI_ASK_AI=true
 | 
					VITE_AI_ASK_AI=true
 | 
				
			||||||
VITE_AI_PROVIDER=ollama
 | 
					VITE_AI_PROVIDER=ollama
 | 
				
			||||||
VITE_AI_MODEL=gemma3:4b
 | 
					VITE_AI_MODEL=gemma3:4b
 | 
				
			||||||
VITE_AI_HOST=https://localhost:11434
 | 
					VITE_AI_HOST=http://localhost:11434
 | 
				
			||||||
VITE_AI_SYSTEM_PROMPT="あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。"
 | 
					VITE_AI_SYSTEM_PROMPT="あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ VITE_ATPROTO_HANDLE_LIST=["syui.syui.ai","ai.syui.ai","ai.ai"]
 | 
				
			|||||||
VITE_AI_ENABLED=true
 | 
					VITE_AI_ENABLED=true
 | 
				
			||||||
VITE_AI_ASK_AI=true
 | 
					VITE_AI_ASK_AI=true
 | 
				
			||||||
VITE_AI_PROVIDER=ollama
 | 
					VITE_AI_PROVIDER=ollama
 | 
				
			||||||
VITE_AI_MODEL=gemma3:4b
 | 
					VITE_AI_MODEL=gemma3:1b
 | 
				
			||||||
VITE_AI_HOST=https://ollama.syui.ai
 | 
					VITE_AI_HOST=https://ollama.syui.ai
 | 
				
			||||||
VITE_AI_SYSTEM_PROMPT="あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。"
 | 
					VITE_AI_SYSTEM_PROMPT="あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -224,13 +224,7 @@ function App() {
 | 
				
			|||||||
        // Ensure handle is not DID
 | 
					        // Ensure handle is not DID
 | 
				
			||||||
        const handle = oauthResult.handle !== oauthResult.did ? oauthResult.handle : oauthResult.handle;
 | 
					        const handle = oauthResult.handle !== oauthResult.did ? oauthResult.handle : oauthResult.handle;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Check if handle is allowed
 | 
					        // Note: appConfig.allowedHandles is used for PDS detection, not access control
 | 
				
			||||||
        if (appConfig.allowedHandles.length > 0 && !appConfig.allowedHandles.includes(handle)) {
 | 
					 | 
				
			||||||
          // Handle not in allowed list
 | 
					 | 
				
			||||||
          setError(`Access denied: ${handle} is not authorized for this application.`);
 | 
					 | 
				
			||||||
          setIsLoading(false);
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Get user profile including avatar
 | 
					        // Get user profile including avatar
 | 
				
			||||||
        const userProfile = await getUserProfile(oauthResult.did, handle);
 | 
					        const userProfile = await getUserProfile(oauthResult.did, handle);
 | 
				
			||||||
@@ -1213,8 +1207,9 @@ function App() {
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    // Extract content based on format
 | 
					    // Extract content based on format
 | 
				
			||||||
    const contentText = isNewFormat ? value.text : (value.content || value.body || '');
 | 
					    const contentText = isNewFormat ? value.text : (value.content || value.body || '');
 | 
				
			||||||
    // For AI comments, always use the loaded AI profile instead of record.value.author
 | 
					    // Use the author from the record if available, otherwise fall back to AI profile
 | 
				
			||||||
    const authorInfo = aiProfile;
 | 
					    const authorInfo = value.author || aiProfile;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    const postInfo = isNewFormat ? value.post : null;
 | 
					    const postInfo = isNewFormat ? value.post : null;
 | 
				
			||||||
    const contentType = value.type || 'unknown';
 | 
					    const contentType = value.type || 'unknown';
 | 
				
			||||||
    const createdAt = value.createdAt || value.generated_at || '';
 | 
					    const createdAt = value.createdAt || value.generated_at || '';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -191,6 +191,7 @@ Answer:`;
 | 
				
			|||||||
          method: 'POST',
 | 
					          method: 'POST',
 | 
				
			||||||
          headers: {
 | 
					          headers: {
 | 
				
			||||||
            'Content-Type': 'application/json',
 | 
					            'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					            'Origin': 'https://syui.ai',
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          body: JSON.stringify({
 | 
					          body: JSON.stringify({
 | 
				
			||||||
            model: aiConfig.model,
 | 
					            model: aiConfig.model,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -113,7 +113,7 @@ export function getAppConfig(): AppConfig {
 | 
				
			|||||||
  const aiEnabled = import.meta.env.VITE_AI_ENABLED === 'true';
 | 
					  const aiEnabled = import.meta.env.VITE_AI_ENABLED === 'true';
 | 
				
			||||||
  const aiAskAi = import.meta.env.VITE_AI_ASK_AI === 'true';
 | 
					  const aiAskAi = import.meta.env.VITE_AI_ASK_AI === 'true';
 | 
				
			||||||
  const aiProvider = import.meta.env.VITE_AI_PROVIDER || 'ollama';
 | 
					  const aiProvider = import.meta.env.VITE_AI_PROVIDER || 'ollama';
 | 
				
			||||||
  const aiModel = import.meta.env.VITE_AI_MODEL || 'gemma2:2b';
 | 
					  const aiModel = import.meta.env.VITE_AI_MODEL || 'gemma3:4b';
 | 
				
			||||||
  const aiHost = import.meta.env.VITE_AI_HOST || 'https://ollama.syui.ai';
 | 
					  const aiHost = import.meta.env.VITE_AI_HOST || 'https://ollama.syui.ai';
 | 
				
			||||||
  const aiSystemPrompt = import.meta.env.VITE_AI_SYSTEM_PROMPT || 'You are a helpful AI assistant trained on this blog\'s content.';
 | 
					  const aiSystemPrompt = import.meta.env.VITE_AI_SYSTEM_PROMPT || 'You are a helpful AI assistant trained on this blog\'s content.';
 | 
				
			||||||
  const atprotoPds = import.meta.env.VITE_ATPROTO_PDS || 'syu.is';
 | 
					  const atprotoPds = import.meta.env.VITE_ATPROTO_PDS || 'syu.is';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ interface AtprotoSession {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class AtprotoOAuthService {
 | 
					class AtprotoOAuthService {
 | 
				
			||||||
  private oauthClient: BrowserOAuthClient | null = null;
 | 
					  private oauthClient: BrowserOAuthClient | null = null;
 | 
				
			||||||
 | 
					  private oauthClientSyuIs: BrowserOAuthClient | null = null;
 | 
				
			||||||
  private agent: Agent | null = null;
 | 
					  private agent: Agent | null = null;
 | 
				
			||||||
  private initializePromise: Promise<void> | null = null;
 | 
					  private initializePromise: Promise<void> | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,22 +32,27 @@ class AtprotoOAuthService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private async _doInitialize(): Promise<void> {
 | 
					  private async _doInitialize(): Promise<void> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      // Generate client ID based on current origin
 | 
					      // Generate client ID based on current origin
 | 
				
			||||||
      const clientId = this.getClientId();
 | 
					      const clientId = this.getClientId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      
 | 
					      // Initialize both OAuth clients
 | 
				
			||||||
      // Support multiple PDS hosts for OAuth
 | 
					 | 
				
			||||||
      this.oauthClient = await BrowserOAuthClient.load({
 | 
					      this.oauthClient = await BrowserOAuthClient.load({
 | 
				
			||||||
        clientId: clientId,
 | 
					        clientId: clientId,
 | 
				
			||||||
        handleResolver: 'https://bsky.social', // Default resolver
 | 
					        handleResolver: 'https://bsky.social',
 | 
				
			||||||
 | 
					        plcDirectoryUrl: 'https://plc.directory',
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.oauthClientSyuIs = await BrowserOAuthClient.load({
 | 
				
			||||||
 | 
					        clientId: clientId,
 | 
				
			||||||
 | 
					        handleResolver: 'https://syu.is',
 | 
				
			||||||
 | 
					        plcDirectoryUrl: 'https://plc.syu.is',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
      
 | 
					      // Try to restore existing session from either client
 | 
				
			||||||
      // Try to restore existing session
 | 
					      let result = await this.oauthClient.init();
 | 
				
			||||||
      const result = await this.oauthClient.init();
 | 
					      if (!result?.session) {
 | 
				
			||||||
 | 
					        result = await this.oauthClientSyuIs.init();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      if (result?.session) {
 | 
					      if (result?.session) {
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Create Agent instance with proper configuration
 | 
					        // Create Agent instance with proper configuration
 | 
				
			||||||
@@ -92,41 +98,13 @@ class AtprotoOAuthService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async processSession(session: any): Promise<{ did: string; handle: string }> {
 | 
					  private async processSession(session: any): Promise<{ did: string; handle: string }> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // Log full session structure
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // Check if agent has properties we can access
 | 
					 | 
				
			||||||
    if (session.agent) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    const did = session.sub || session.did;
 | 
					    const did = session.sub || session.did;
 | 
				
			||||||
    let handle = session.handle || 'unknown';
 | 
					    let handle = session.handle || 'unknown';
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Create Agent directly with session (per official docs)
 | 
					    // Create Agent directly with session (per official docs)
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      this.agent = new Agent(session);
 | 
					      this.agent = new Agent(session);
 | 
				
			||||||
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      // Check if agent has session info after creation
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (this.agent.session) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Fallback to dpopFetch method
 | 
					      // Fallback to dpopFetch method
 | 
				
			||||||
      this.agent = new Agent({
 | 
					      this.agent = new Agent({
 | 
				
			||||||
        service: session.server?.serviceEndpoint || 'https://bsky.social',
 | 
					        service: session.server?.serviceEndpoint || 'https://bsky.social',
 | 
				
			||||||
@@ -204,61 +182,15 @@ class AtprotoOAuthService {
 | 
				
			|||||||
    return `${origin}/client-metadata.json`;
 | 
					    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}`)) {
 | 
					 | 
				
			||||||
        // Using PDS for domain match
 | 
					 | 
				
			||||||
        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) {
 | 
					 | 
				
			||||||
              console.log('[OAuth Debug] Resolved handle via', endpoint, '- using that PDS');
 | 
					 | 
				
			||||||
              return endpoint;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        } catch (e) {
 | 
					 | 
				
			||||||
          continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      console.log('[OAuth Debug] Handle resolution failed, using default');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // Default to bsky.social
 | 
					 | 
				
			||||||
    // Using default bsky.social
 | 
					 | 
				
			||||||
    return 'https://bsky.social';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async initiateOAuthFlow(handle?: string): Promise<void> {
 | 
					  async initiateOAuthFlow(handle?: string): Promise<void> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      if (!this.oauthClient || !this.oauthClientSyuIs) {
 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      if (!this.oauthClient) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await this.initialize();
 | 
					        await this.initialize();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!this.oauthClient) {
 | 
					      if (!this.oauthClient || !this.oauthClientSyuIs) {
 | 
				
			||||||
        throw new Error('Failed to initialize OAuth client');
 | 
					        throw new Error('Failed to initialize OAuth clients');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // If handle is not provided, prompt user
 | 
					      // If handle is not provided, prompt user
 | 
				
			||||||
@@ -269,61 +201,27 @@ class AtprotoOAuthService {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Determine which OAuth client to use
 | 
				
			||||||
      
 | 
					      const allowedHandlesStr = import.meta.env.VITE_ATPROTO_HANDLE_LIST || '[]';
 | 
				
			||||||
      // Detect PDS based on handle
 | 
					      let allowedHandles: string[] = [];
 | 
				
			||||||
      const pdsUrl = await this.detectPDSFromHandle(handle);
 | 
					 | 
				
			||||||
      // Starting OAuth flow
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      // Always re-initialize OAuth client with detected PDS
 | 
					 | 
				
			||||||
      // Re-initializing OAuth client
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      // Clear existing client to force fresh initialization
 | 
					 | 
				
			||||||
      this.oauthClient = null;
 | 
					 | 
				
			||||||
      this.initializePromise = null;
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      this.oauthClient = await BrowserOAuthClient.load({
 | 
					 | 
				
			||||||
        clientId: this.getClientId(),
 | 
					 | 
				
			||||||
        handleResolver: pdsUrl,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      // OAuth client initialized
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      // Start OAuth authorization flow
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        // Starting OAuth authorization
 | 
					        allowedHandles = JSON.parse(allowedHandlesStr);
 | 
				
			||||||
        
 | 
					      } catch {
 | 
				
			||||||
        // Try to authorize with DID instead of handle for syu.is PDS only
 | 
					        allowedHandles = [];
 | 
				
			||||||
        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, {
 | 
					 | 
				
			||||||
          scope: 'atproto transition:generic',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Redirect to authorization server
 | 
					 | 
				
			||||||
        window.location.href = authUrl.toString();
 | 
					 | 
				
			||||||
      } catch (authorizeError) {
 | 
					 | 
				
			||||||
        // Authorization failed
 | 
					 | 
				
			||||||
        throw authorizeError;
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
    } catch (error) {
 | 
					      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',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      // Redirect to authorization server
 | 
				
			||||||
 | 
					      window.location.href = authUrl.toString();
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
      throw new Error(`OAuth認証の開始に失敗しました: ${error}`);
 | 
					      throw new Error(`OAuth認証の開始に失敗しました: ${error}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -379,22 +277,16 @@ class AtprotoOAuthService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  async checkSession(): Promise<{ did: string; handle: string } | null> {
 | 
					  async checkSession(): Promise<{ did: string; handle: string } | null> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      if (!this.oauthClient) {
 | 
					      if (!this.oauthClient) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        await this.initialize();
 | 
					        await this.initialize();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!this.oauthClient) {
 | 
					      if (!this.oauthClient) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
      const result = await this.oauthClient.init();
 | 
					      const result = await this.oauthClient.init();
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      if (result?.session) {
 | 
					      if (result?.session) {
 | 
				
			||||||
        // Use the common session processing method
 | 
					        // Use the common session processing method
 | 
				
			||||||
        return this.processSession(result.session);
 | 
					        return this.processSession(result.session);
 | 
				
			||||||
@@ -458,28 +350,20 @@ class AtprotoOAuthService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  async logout(): Promise<void> {
 | 
					  async logout(): Promise<void> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      // Clear Agent
 | 
					      // Clear Agent
 | 
				
			||||||
      this.agent = null;
 | 
					      this.agent = null;
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      // Clear BrowserOAuthClient session
 | 
					      // Clear BrowserOAuthClient session
 | 
				
			||||||
      if (this.oauthClient) {
 | 
					      if (this.oauthClient) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          // BrowserOAuthClient may have a revoke or signOut method
 | 
					          // BrowserOAuthClient may have a revoke or signOut method
 | 
				
			||||||
          if (typeof (this.oauthClient as any).signOut === 'function') {
 | 
					          if (typeof (this.oauthClient as any).signOut === 'function') {
 | 
				
			||||||
            await (this.oauthClient as any).signOut();
 | 
					            await (this.oauthClient as any).signOut();
 | 
				
			||||||
 | 
					 | 
				
			||||||
          } else if (typeof (this.oauthClient as any).revoke === 'function') {
 | 
					          } else if (typeof (this.oauthClient as any).revoke === 'function') {
 | 
				
			||||||
            await (this.oauthClient as any).revoke();
 | 
					            await (this.oauthClient as any).revoke();
 | 
				
			||||||
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } catch (oauthError) {
 | 
					        } catch (oauthError) {
 | 
				
			||||||
 | 
					          // Ignore logout errors
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Reset the OAuth client to force re-initialization
 | 
					        // Reset the OAuth client to force re-initialization
 | 
				
			||||||
@@ -491,18 +375,16 @@ class AtprotoOAuthService {
 | 
				
			|||||||
      localStorage.removeItem('atproto_session');
 | 
					      localStorage.removeItem('atproto_session');
 | 
				
			||||||
      sessionStorage.clear();
 | 
					      sessionStorage.clear();
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
      // Clear all localStorage items that might be related to OAuth
 | 
					      // Clear all OAuth-related storage
 | 
				
			||||||
      const keysToRemove: string[] = [];
 | 
					 | 
				
			||||||
      for (let i = 0; i < localStorage.length; i++) {
 | 
					      for (let i = 0; i < localStorage.length; i++) {
 | 
				
			||||||
        const key = localStorage.key(i);
 | 
					        const key = localStorage.key(i);
 | 
				
			||||||
        if (key && (key.includes('oauth') || key.includes('atproto') || key.includes('session'))) {
 | 
					        if (key && (key.includes('oauth') || key.includes('atproto') || key.includes('session'))) {
 | 
				
			||||||
          keysToRemove.push(key);
 | 
					          localStorage.removeItem(key);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      keysToRemove.forEach(key => {
 | 
					 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
        localStorage.removeItem(key);
 | 
					      // Clear internal session info
 | 
				
			||||||
      });
 | 
					      (this as any)._sessionInfo = null;
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,10 @@
 | 
				
			|||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cb=ai.syui.log
 | 
					cb=ai.syui.log
 | 
				
			||||||
cl=( $cb.user )
 | 
					cl=( $cb.chat.lang $cb.chat.comment)
 | 
				
			||||||
f=~/.config/syui/ai/log/config.json
 | 
					f=~/.config/syui/ai/log/config.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
default_collection="ai.syui.log.chat.comment"
 | 
					default_collection="ai.syui.log.chat"
 | 
				
			||||||
default_pds="syu.is"
 | 
					default_pds="syu.is"
 | 
				
			||||||
default_did=`cat $f|jq -r .admin.did`
 | 
					default_did=`cat $f|jq -r .admin.did`
 | 
				
			||||||
default_token=`cat $f|jq -r .admin.access_jwt`
 | 
					default_token=`cat $f|jq -r .admin.access_jwt`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1426,21 +1426,8 @@ async fn generate_ai_content(content: &str, prompt_type: &str, ai_config: &AiCon
 | 
				
			|||||||
        .timeout(std::time::Duration::from_secs(120)) // 2 minute timeout
 | 
					        .timeout(std::time::Duration::from_secs(120)) // 2 minute timeout
 | 
				
			||||||
        .build()?;
 | 
					        .build()?;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Try localhost first (for same-server deployment)
 | 
					    // Use configured Ollama host
 | 
				
			||||||
    let localhost_url = "http://localhost:11434/api/generate";
 | 
					    let ollama_url = format!("{}/api/generate", ai_config.ollama_host);
 | 
				
			||||||
    match client.post(localhost_url).json(&request).send().await {
 | 
					 | 
				
			||||||
        Ok(response) if response.status().is_success() => {
 | 
					 | 
				
			||||||
            let ollama_response: OllamaResponse = response.json().await?;
 | 
					 | 
				
			||||||
            println!("{}", "✅ Used localhost Ollama".green());
 | 
					 | 
				
			||||||
            return Ok(ollama_response.response);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        _ => {
 | 
					 | 
				
			||||||
            println!("{}", "⚠️ Localhost Ollama not available, trying remote...".yellow());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // Fallback to remote host
 | 
					 | 
				
			||||||
    let remote_url = format!("{}/api/generate", ai_config.ollama_host);
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Check if this is a local/private network connection (no CORS needed)
 | 
					    // Check if this is a local/private network connection (no CORS needed)
 | 
				
			||||||
    // RFC 1918 private networks + localhost
 | 
					    // RFC 1918 private networks + localhost
 | 
				
			||||||
@@ -1461,13 +1448,13 @@ async fn generate_ai_content(content: &str, prompt_type: &str, ai_config: &AiCon
 | 
				
			|||||||
                       } else { false }
 | 
					                       } else { false }
 | 
				
			||||||
                   });
 | 
					                   });
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    let mut request_builder = client.post(&remote_url).json(&request);
 | 
					    let mut request_builder = client.post(&ollama_url).json(&request);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if !is_local {
 | 
					    if !is_local {
 | 
				
			||||||
        println!("{}", format!("🔗 Making request to: {} with Origin: {}", remote_url, ai_config.blog_host).blue());
 | 
					        println!("{}", format!("🔗 Making request to: {} with Origin: {}", ollama_url, ai_config.blog_host).blue());
 | 
				
			||||||
        request_builder = request_builder.header("Origin", &ai_config.blog_host);
 | 
					        request_builder = request_builder.header("Origin", &ai_config.blog_host);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        println!("{}", format!("🔗 Making request to local network: {}", remote_url).blue());
 | 
					        println!("{}", format!("🔗 Making request to local network: {}", ollama_url).blue());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    let response = request_builder.send().await?;
 | 
					    let response = request_builder.send().await?;
 | 
				
			||||||
@@ -1477,7 +1464,7 @@ async fn generate_ai_content(content: &str, prompt_type: &str, ai_config: &AiCon
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    let ollama_response: OllamaResponse = response.json().await?;
 | 
					    let ollama_response: OllamaResponse = response.json().await?;
 | 
				
			||||||
    println!("{}", "✅ Used remote Ollama".green());
 | 
					    println!("{}", "✅ Ollama request successful".green());
 | 
				
			||||||
    Ok(ollama_response.response)
 | 
					    Ok(ollama_response.response)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user