fix oauth package name

This commit is contained in:
2025-06-19 11:56:58 +09:00
parent b17ac3d91a
commit 30bdd7b633
105 changed files with 1116 additions and 8739 deletions

234
oauth/src/hooks/useAskAI.js Normal file
View File

@@ -0,0 +1,234 @@
import { useState } from 'react'
import { atproto, collections } from '../api/atproto.js'
import { env } from '../config/env.js'
import { logger } from '../utils/logger.js'
import { getErrorMessage, logError } from '../utils/errorHandler.js'
export function useAskAI(adminData, userProfile, agent) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [chatHistory, setChatHistory] = useState([])
// ask-AIサーバーのURL環境変数から取得、フォールバック付き
const askAIUrl = import.meta.env.VITE_ASK_AI_URL || 'http://localhost:3000/ask'
const askQuestion = async (question) => {
if (!question.trim()) return
setLoading(true)
setError(null)
try {
logger.log('Sending question to ask-AI:', question)
// ask-AIサーバーにリクエスト送信
const response = await fetch(askAIUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
question: question.trim(),
context: {
url: window.location.href,
timestamp: new Date().toISOString()
}
})
})
if (!response.ok) {
throw new Error(`ask-AI server error: ${response.status}`)
}
const aiResponse = await response.json()
logger.log('Received AI response:', aiResponse)
// AI回答をチャット履歴に追加
const chatEntry = {
id: `chat-${Date.now()}`,
question: question.trim(),
answer: aiResponse.answer || 'エラーが発生しました',
timestamp: new Date().toISOString(),
user: userProfile ? {
did: userProfile.did,
handle: userProfile.handle,
displayName: userProfile.displayName,
avatar: userProfile.avatar
} : null
}
setChatHistory(prev => [...prev, chatEntry])
// atprotoにレコードを保存
await saveChatRecord(chatEntry, aiResponse)
// Dispatch event for blog communication
window.dispatchEvent(new CustomEvent('aiResponseReceived', {
detail: {
question: chatEntry.question,
answer: chatEntry.answer,
timestamp: chatEntry.timestamp,
aiProfile: adminData?.profile ? {
did: adminData.did,
handle: adminData.profile.handle,
displayName: adminData.profile.displayName,
avatar: adminData.profile.avatar
} : null
}
}))
return aiResponse
} catch (err) {
logError(err, 'useAskAI.askQuestion')
setError(getErrorMessage(err))
throw err
} finally {
setLoading(false)
}
}
const saveChatRecord = async (chatEntry, aiResponse) => {
if (!agent || !adminData?.did) {
logger.warn('Cannot save chat record: missing agent or admin data')
return
}
try {
const currentUrl = window.location.href
const timestamp = chatEntry.timestamp
const baseRkey = `${new Date(timestamp).toISOString().replace(/[:.]/g, '-').slice(0, -5)}Z`
// Post metadata (共通)
const postMetadata = {
url: currentUrl,
date: timestamp,
slug: new URL(currentUrl).pathname.split('/').pop()?.replace(/\.html$/, '') || '',
tags: [],
title: document.title || 'AI Chat',
language: 'ja'
}
// Question record (ユーザーの質問)
const questionRecord = {
repo: adminData.did,
collection: `${env.collection}.chat`,
rkey: baseRkey,
record: {
$type: `${env.collection}.chat`,
post: postMetadata,
text: chatEntry.question,
type: 'question',
author: chatEntry.user ? {
did: chatEntry.user.did,
handle: chatEntry.user.handle,
displayName: chatEntry.user.displayName,
avatar: chatEntry.user.avatar
} : {
did: 'unknown',
handle: 'user',
displayName: 'User',
avatar: null
},
createdAt: timestamp
}
}
// Answer record (AIの回答)
const answerRecord = {
repo: adminData.did,
collection: `${env.collection}.chat`,
rkey: `${baseRkey}-answer`,
record: {
$type: `${env.collection}.chat`,
post: postMetadata,
text: chatEntry.answer,
type: 'answer',
author: {
did: adminData.did,
handle: adminData.profile?.handle || 'ai',
displayName: adminData.profile?.displayName || 'ai',
avatar: adminData.profile?.avatar || null
},
createdAt: timestamp
}
}
logger.log('Saving question record to atproto:', questionRecord)
await atproto.putRecord(null, questionRecord, agent)
logger.log('Saving answer record to atproto:', answerRecord)
await atproto.putRecord(null, answerRecord, agent)
// キャッシュを無効化
collections.invalidateCache(env.collection)
logger.log('Chat records saved successfully')
} catch (err) {
logError(err, 'useAskAI.saveChatRecord')
// 保存エラーは致命的ではないので、UIエラーにはしない
}
}
const clearChatHistory = () => {
setChatHistory([])
setError(null)
}
const loadChatHistory = async () => {
if (!adminData?.did) return
try {
const records = await collections.getChat(
adminData.apiConfig.pds,
adminData.did,
env.collection
)
// Group records by timestamp and create Q&A pairs
const recordGroups = {}
records.forEach(record => {
const timestamp = record.value.createdAt
const baseKey = timestamp.replace('-answer', '')
if (!recordGroups[baseKey]) {
recordGroups[baseKey] = {}
}
if (record.value.type === 'question') {
recordGroups[baseKey].question = record.value.text
recordGroups[baseKey].user = record.value.author
recordGroups[baseKey].timestamp = timestamp
recordGroups[baseKey].id = record.uri
} else if (record.value.type === 'answer') {
recordGroups[baseKey].answer = record.value.text
recordGroups[baseKey].timestamp = timestamp
}
})
// Convert to history format, only include complete Q&A pairs
const history = Object.values(recordGroups)
.filter(group => group.question && group.answer)
.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp))
.slice(-10) // 最新10件のみ
setChatHistory(history)
logger.log('Chat history loaded:', history.length, 'entries')
} catch (err) {
logError(err, 'useAskAI.loadChatHistory')
// 履歴読み込みエラーは致命的ではない
}
}
return {
askQuestion,
loading,
error,
chatHistory,
clearChatHistory,
loadChatHistory
}
}