fix
This commit is contained in:
parent
dcec8db031
commit
d6b9889135
@ -227,9 +227,6 @@ Example memory:
|
||||
• 中期記憶(MTM):繰り返し登場する話題、圧縮された文脈保持
|
||||
• 長期記憶(LTM):信頼・関係・背景知識、恒久的な人格情報
|
||||
|
||||
|
||||
|
||||
|
||||
### 2.3 選択的記憶保持戦略(Selective Retention Strategy)
|
||||
• 重要度評価(Importance Score)
|
||||
• 希少性・再利用頻度による重み付け
|
||||
|
@ -1,549 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>改良版 ChatGPT会話コンバーター</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
font-size: 2.5em;
|
||||
font-weight: 300;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
border: 3px dashed #3498db;
|
||||
border-radius: 15px;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
background: linear-gradient(45deg, #f8f9ff, #e8f4f8);
|
||||
margin-bottom: 30px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: #2980b9;
|
||||
background: linear-gradient(45deg, #f0f8ff, #e0f0f8);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.upload-area.dragover {
|
||||
border-color: #27ae60;
|
||||
background: linear-gradient(45deg, #f0fff0, #e0f8e0);
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 4em;
|
||||
color: #3498db;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #3498db, #2980b9);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 30px;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s ease;
|
||||
margin: 10px;
|
||||
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(52, 152, 219, 0.4);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
background: #bdc3c7;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
text-align: center;
|
||||
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #ecf0f1;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #3498db, #27ae60);
|
||||
transition: width 0.3s ease;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.log {
|
||||
background: #2c3e50;
|
||||
color: #ecf0f1;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
margin-top: 20px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.processing {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔧 改良版 ChatGPT会話コンバーター</h1>
|
||||
<p>画像・検索・特殊メッセージに対応した堅牢な変換ツール</p>
|
||||
</div>
|
||||
|
||||
<div class="upload-area" onclick="document.getElementById('file-input').click()">
|
||||
<span class="upload-icon">📁</span>
|
||||
<h3>ChatGPT会話ファイルをドロップまたはクリックして選択</h3>
|
||||
<p>conversations.json ファイルをアップロード</p>
|
||||
<input type="file" id="file-input" accept=".json" />
|
||||
</div>
|
||||
|
||||
<div class="stats" id="stats" style="display: none;">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="total-conversations">0</div>
|
||||
<div class="stat-label">総会話数</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="processed-conversations">0</div>
|
||||
<div class="stat-label">処理済み会話</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="success-conversations">0</div>
|
||||
<div class="stat-label">変換成功</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="failed-conversations">0</div>
|
||||
<div class="stat-label">変換失敗</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-bar" id="progress-container" style="display: none;">
|
||||
<div class="progress-fill" id="progress-fill"></div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center;">
|
||||
<button class="btn" id="convert-btn" disabled>🔄 変換開始</button>
|
||||
<button class="btn" id="download-btn" disabled style="background: linear-gradient(135deg, #27ae60, #2ecc71);">📥 結果をダウンロード</button>
|
||||
<button class="btn" id="clear-btn" style="background: linear-gradient(135deg, #e74c3c, #c0392b);">🗑️ クリア</button>
|
||||
</div>
|
||||
|
||||
<div class="log" id="log"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let originalData = null;
|
||||
let convertedResults = [];
|
||||
|
||||
// DOM要素
|
||||
const fileInput = document.getElementById('file-input');
|
||||
const uploadArea = document.querySelector('.upload-area');
|
||||
const convertBtn = document.getElementById('convert-btn');
|
||||
const downloadBtn = document.getElementById('download-btn');
|
||||
const clearBtn = document.getElementById('clear-btn');
|
||||
const logElement = document.getElementById('log');
|
||||
const statsElement = document.getElementById('stats');
|
||||
const progressContainer = document.getElementById('progress-container');
|
||||
const progressFill = document.getElementById('progress-fill');
|
||||
|
||||
// 統計要素
|
||||
const totalConversationsEl = document.getElementById('total-conversations');
|
||||
const processedConversationsEl = document.getElementById('processed-conversations');
|
||||
const successConversationsEl = document.getElementById('success-conversations');
|
||||
const failedConversationsEl = document.getElementById('failed-conversations');
|
||||
|
||||
// ドラッグ&ドロップ
|
||||
uploadArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.add('dragover');
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('dragleave', () => {
|
||||
uploadArea.classList.remove('dragover');
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.remove('dragover');
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
handleFile(files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
// ファイル選択
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
if (e.target.files.length > 0) {
|
||||
handleFile(e.target.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
// ファイル処理
|
||||
function handleFile(file) {
|
||||
if (!file.name.endsWith('.json')) {
|
||||
log('❌ JSONファイルを選択してください', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
log(`📁 ファイルを読み込み中: ${file.name}`, 'info');
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
originalData = JSON.parse(e.target.result);
|
||||
log(`✅ ファイル読み込み完了 (${(file.size / 1024 / 1024).toFixed(2)}MB)`, 'success');
|
||||
|
||||
// 統計表示
|
||||
const totalCount = Array.isArray(originalData) ? originalData.length : 1;
|
||||
totalConversationsEl.textContent = totalCount;
|
||||
statsElement.style.display = 'grid';
|
||||
|
||||
convertBtn.disabled = false;
|
||||
log('🔄 変換準備完了。「変換開始」ボタンをクリックしてください', 'info');
|
||||
} catch (error) {
|
||||
log(`❌ JSONファイルの解析に失敗: ${error.message}`, 'error');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
// ログ出力
|
||||
function log(message, type = 'info') {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const className = type;
|
||||
logElement.innerHTML += `<span class="${className}">[${timestamp}] ${message}</span>\n`;
|
||||
logElement.scrollTop = logElement.scrollHeight;
|
||||
}
|
||||
|
||||
// メッセージの内容を安全に取得
|
||||
function extractMessageContent(message) {
|
||||
if (!message || !message.content) return '';
|
||||
|
||||
const content = message.content;
|
||||
|
||||
// テキストコンテンツの場合
|
||||
if (content.content_type === 'text' && content.parts) {
|
||||
return content.parts
|
||||
.filter(part => part && typeof part === 'string' && part.trim())
|
||||
.join('\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
// マルチモーダル(画像付き)コンテンツの場合
|
||||
if (content.content_type === 'multimodal_text' && content.parts) {
|
||||
const textParts = [];
|
||||
for (const part of content.parts) {
|
||||
if (typeof part === 'string' && part.trim()) {
|
||||
textParts.push(part);
|
||||
} else if (part && typeof part === 'object') {
|
||||
// 画像や他のメディアの場合
|
||||
if (part.image_url) {
|
||||
textParts.push('[画像が添付されています]');
|
||||
} else if (part.type === 'text' && part.text) {
|
||||
textParts.push(part.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
return textParts.join('\n').trim();
|
||||
}
|
||||
|
||||
// ユーザープロファイル情報の場合
|
||||
if (content.content_type === 'user_editable_context') {
|
||||
return '[システム設定情報]';
|
||||
}
|
||||
|
||||
// その他の特殊コンテンツ
|
||||
if (content.content_type && content.content_type !== 'text') {
|
||||
return `[${content.content_type}]`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// 会話の線形化(親子関係を辿って順序付け)
|
||||
function linearizeConversation(mapping) {
|
||||
const messages = [];
|
||||
const visited = new Set();
|
||||
|
||||
// ルートノードを見つける
|
||||
const rootNode = Object.values(mapping).find(node => node.parent === null);
|
||||
if (!rootNode) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
// 深度優先探索で会話を辿る
|
||||
function traverse(nodeId) {
|
||||
if (visited.has(nodeId) || !mapping[nodeId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
visited.add(nodeId);
|
||||
const node = mapping[nodeId];
|
||||
|
||||
// メッセージが存在し、有効なコンテンツがある場合のみ追加
|
||||
if (node.message) {
|
||||
const message = node.message;
|
||||
const content = extractMessageContent(message);
|
||||
|
||||
// 以下の条件で有効なメッセージとして扱う
|
||||
const isValid = content &&
|
||||
content.length > 0 &&
|
||||
content !== '[システム設定情報]' &&
|
||||
(!message.metadata?.is_visually_hidden_from_conversation ||
|
||||
(message.author?.role === 'user' || message.author?.role === 'assistant'));
|
||||
|
||||
if (isValid) {
|
||||
messages.push({
|
||||
role: message.author?.role || 'unknown',
|
||||
content: content,
|
||||
timestamp: message.create_time || message.update_time || Date.now() / 1000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 子ノードを処理(通常は1つだが、分岐がある場合もある)
|
||||
if (node.children && node.children.length > 0) {
|
||||
// 最初の子ノードのみを辿る(最も新しい応答を優先)
|
||||
traverse(node.children[0]);
|
||||
}
|
||||
}
|
||||
|
||||
traverse(rootNode.id);
|
||||
return messages;
|
||||
}
|
||||
|
||||
// 単一会話の変換
|
||||
function convertSingleConversation(conversation, index) {
|
||||
try {
|
||||
if (!conversation.mapping) {
|
||||
throw new Error('mapping が見つかりません');
|
||||
}
|
||||
|
||||
// 会話を線形化
|
||||
const messages = linearizeConversation(conversation.mapping);
|
||||
|
||||
if (messages.length === 0) {
|
||||
throw new Error('有効なメッセージが見つかりません');
|
||||
}
|
||||
|
||||
// 結果の構築
|
||||
const result = {
|
||||
title: conversation.title || `会話 ${index + 1}`,
|
||||
create_time: conversation.create_time || Date.now() / 1000,
|
||||
update_time: conversation.update_time || Date.now() / 1000,
|
||||
conversation_id: conversation.conversation_id || conversation.id || `conv_${index}`,
|
||||
messages: messages,
|
||||
metadata: {
|
||||
original_message_count: Object.keys(conversation.mapping).length,
|
||||
processed_message_count: messages.length,
|
||||
is_archived: conversation.is_archived || false,
|
||||
model_slug: conversation.default_model_slug || 'unknown'
|
||||
}
|
||||
};
|
||||
|
||||
return { success: true, result, error: null };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: error.message,
|
||||
conversation_title: conversation.title || `会話 ${index + 1}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 変換処理
|
||||
convertBtn.addEventListener('click', async () => {
|
||||
if (!originalData) return;
|
||||
|
||||
convertBtn.disabled = true;
|
||||
downloadBtn.disabled = true;
|
||||
convertedResults = [];
|
||||
|
||||
const conversations = Array.isArray(originalData) ? originalData : [originalData];
|
||||
const total = conversations.length;
|
||||
let processed = 0;
|
||||
let success = 0;
|
||||
let failed = 0;
|
||||
|
||||
log(`🔄 ${total}個の会話の変換を開始します...`, 'info');
|
||||
progressContainer.style.display = 'block';
|
||||
|
||||
for (let i = 0; i < conversations.length; i++) {
|
||||
const conversation = conversations[i];
|
||||
|
||||
log(`[${i + 1}/${total}] "${conversation.title || `会話${i + 1}`}" を処理中...`, 'info');
|
||||
|
||||
const result = convertSingleConversation(conversation, i);
|
||||
|
||||
if (result.success) {
|
||||
convertedResults.push(result.result);
|
||||
success++;
|
||||
log(`✅ [${i + 1}/${total}] 変換成功: ${result.result.messages.length}メッセージ`, 'success');
|
||||
} else {
|
||||
failed++;
|
||||
log(`❌ [${i + 1}/${total}] 変換失敗: ${result.error}`, 'error');
|
||||
}
|
||||
|
||||
processed++;
|
||||
|
||||
// 統計更新
|
||||
processedConversationsEl.textContent = processed;
|
||||
successConversationsEl.textContent = success;
|
||||
failedConversationsEl.textContent = failed;
|
||||
|
||||
// プログレスバー更新
|
||||
const progress = (processed / total) * 100;
|
||||
progressFill.style.width = `${progress}%`;
|
||||
|
||||
// UIを更新するため少し待機
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
}
|
||||
|
||||
log(`🎉 変換完了! 成功: ${success}個, 失敗: ${failed}個`, success > 0 ? 'success' : 'warning');
|
||||
|
||||
if (success > 0) {
|
||||
downloadBtn.disabled = false;
|
||||
}
|
||||
convertBtn.disabled = false;
|
||||
});
|
||||
|
||||
// ダウンロード
|
||||
downloadBtn.addEventListener('click', () => {
|
||||
if (convertedResults.length === 0) return;
|
||||
|
||||
const blob = new Blob([JSON.stringify(convertedResults, null, 2)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `chatgpt_conversations_converted_${new Date().toISOString().split('T')[0]}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
log('📥 変換結果をダウンロードしました', 'success');
|
||||
});
|
||||
|
||||
// クリア
|
||||
clearBtn.addEventListener('click', () => {
|
||||
originalData = null;
|
||||
convertedResults = [];
|
||||
logElement.innerHTML = '';
|
||||
statsElement.style.display = 'none';
|
||||
progressContainer.style.display = 'none';
|
||||
progressFill.style.width = '0%';
|
||||
|
||||
// ボタン状態リセット
|
||||
convertBtn.disabled = true;
|
||||
downloadBtn.disabled = true;
|
||||
|
||||
// ファイル入力リセット
|
||||
fileInput.value = '';
|
||||
|
||||
log('🗑️ すべてクリアしました', 'info');
|
||||
});
|
||||
|
||||
// 初期メッセージ
|
||||
log('👋 ChatGPT会話コンバーターへようこそ!', 'info');
|
||||
log('📁 conversations.json ファイルをアップロードしてください', 'info');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,5 +1,8 @@
|
||||
# rerequirements.txt
|
||||
fastapi>=0.104.0
|
||||
uvicorn[standard]>=0.24.0
|
||||
pydantic>=2.5.0
|
||||
requests>=2.31.0
|
||||
python-multipart>=0.0.6
|
||||
aiohttp
|
||||
asyncio
|
||||
|
Loading…
x
Reference in New Issue
Block a user