feat: Multiple attachments + paste support (App + Diagnostic)
App: - Multiple pending attachments (horizontal scroll preview) - Individual remove (X) or clear all - Send button shows when any attachment pending - All files sent before text message Diagnostic: - Clip icon for file selection (multiple) - Paste images/files from clipboard (Ctrl+V) - Pending preview with thumbnails - Files sent via RVS before text message Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Image,
|
Image,
|
||||||
|
ScrollView,
|
||||||
Modal,
|
Modal,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
@@ -94,7 +95,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
const [fullscreenImage, setFullscreenImage] = useState<string | null>(null);
|
const [fullscreenImage, setFullscreenImage] = useState<string | null>(null);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [searchVisible, setSearchVisible] = useState(false);
|
const [searchVisible, setSearchVisible] = useState(false);
|
||||||
const [pendingAttachment, setPendingAttachment] = useState<{file: any, isPhoto: boolean} | null>(null);
|
const [pendingAttachments, setPendingAttachments] = useState<{file: any, isPhoto: boolean}[]>([]);
|
||||||
|
|
||||||
const flatListRef = useRef<FlatList>(null);
|
const flatListRef = useRef<FlatList>(null);
|
||||||
const messageIdCounter = useRef(0);
|
const messageIdCounter = useRef(0);
|
||||||
@@ -402,9 +403,9 @@ const ChatScreen: React.FC = () => {
|
|||||||
const sendTextMessage = useCallback(async () => {
|
const sendTextMessage = useCallback(async () => {
|
||||||
const text = inputText.trim();
|
const text = inputText.trim();
|
||||||
|
|
||||||
// Wenn pending Anhang vorhanden → Anhang + Text zusammen senden
|
// Wenn pending Anhaenge vorhanden → Anhaenge + Text zusammen senden
|
||||||
if (pendingAttachment) {
|
if (pendingAttachments.length > 0) {
|
||||||
sendPendingAttachment(text);
|
sendPendingAttachments(text);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,7 +428,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
text,
|
text,
|
||||||
...(location && { location }),
|
...(location && { location }),
|
||||||
});
|
});
|
||||||
}, [inputText, getCurrentLocation, pendingAttachment, sendPendingAttachment]);
|
}, [inputText, getCurrentLocation, pendingAttachments, sendPendingAttachments]);
|
||||||
|
|
||||||
// Sprachaufnahme abgeschlossen
|
// Sprachaufnahme abgeschlossen
|
||||||
const handleVoiceRecording = useCallback(async (result: RecordingResult) => {
|
const handleVoiceRecording = useCallback(async (result: RecordingResult) => {
|
||||||
@@ -449,69 +450,81 @@ const ChatScreen: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}, [getCurrentLocation]);
|
}, [getCurrentLocation]);
|
||||||
|
|
||||||
// Datei auswaehlen → als pending speichern (nicht sofort senden)
|
// Datei auswaehlen → zur Pending-Liste hinzufuegen
|
||||||
const handleFileSelected = useCallback(async (file: FileData) => {
|
const handleFileSelected = useCallback(async (file: FileData) => {
|
||||||
setShowFileUpload(false);
|
setShowFileUpload(false);
|
||||||
setPendingAttachment({ file, isPhoto: false });
|
setPendingAttachments(prev => [...prev, { file, isPhoto: false }]);
|
||||||
setInputText(''); // Focus auf Textfeld
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Foto auswaehlen → als pending speichern (nicht sofort senden)
|
// Foto auswaehlen → zur Pending-Liste hinzufuegen
|
||||||
const handlePhotoSelected = useCallback(async (photo: PhotoData) => {
|
const handlePhotoSelected = useCallback(async (photo: PhotoData) => {
|
||||||
setShowCameraUpload(false);
|
setShowCameraUpload(false);
|
||||||
setPendingAttachment({ file: photo, isPhoto: true });
|
setPendingAttachments(prev => [...prev, { file: photo, isPhoto: true }]);
|
||||||
setInputText(''); // Focus auf Textfeld
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Pending Anhang + Text/Sprache senden
|
// Alle Pending Anhaenge + Text senden
|
||||||
const sendPendingAttachment = useCallback(async (messageText: string) => {
|
const sendPendingAttachments = useCallback(async (messageText: string) => {
|
||||||
if (!pendingAttachment) return;
|
if (pendingAttachments.length === 0) return;
|
||||||
const { file, isPhoto } = pendingAttachment;
|
|
||||||
const location = await getCurrentLocation();
|
const location = await getCurrentLocation();
|
||||||
const msgId = nextId();
|
const msgId = nextId();
|
||||||
|
|
||||||
// Chat-Nachricht erstellen
|
// Alle Attachments fuer die Chat-Nachricht sammeln
|
||||||
const isImage = isPhoto || (file.type && file.type.startsWith('image/'));
|
const attachments: Attachment[] = [];
|
||||||
const name = isPhoto ? file.fileName : file.name;
|
for (const { file, isPhoto } of pendingAttachments) {
|
||||||
const base64 = file.base64 || '';
|
const isImage = isPhoto || (file.type && file.type.startsWith('image/'));
|
||||||
const mimeType = file.type || '';
|
const name = isPhoto ? file.fileName : file.name;
|
||||||
const imageUri = isImage && base64 ? `data:${mimeType};base64,${base64}` : file.uri;
|
const base64 = file.base64 || '';
|
||||||
|
const mimeType = file.type || '';
|
||||||
|
const imageUri = isImage && base64 ? `data:${mimeType};base64,${base64}` : file.uri;
|
||||||
|
|
||||||
const userMsg: ChatMessage = {
|
attachments.push({
|
||||||
id: msgId,
|
|
||||||
sender: 'user',
|
|
||||||
text: messageText || 'Anhang',
|
|
||||||
timestamp: Date.now(),
|
|
||||||
attachments: [{
|
|
||||||
type: isImage ? 'image' : 'file',
|
type: isImage ? 'image' : 'file',
|
||||||
name,
|
name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
uri: imageUri,
|
uri: imageUri,
|
||||||
mimeType,
|
mimeType,
|
||||||
}],
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat-Nachricht mit allen Anhaengen
|
||||||
|
const userMsg: ChatMessage = {
|
||||||
|
id: msgId,
|
||||||
|
sender: 'user',
|
||||||
|
text: messageText || `${pendingAttachments.length} Anhang/Anhaenge`,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
attachments,
|
||||||
};
|
};
|
||||||
setMessages(prev => [...prev, userMsg]);
|
setMessages(prev => [...prev, userMsg]);
|
||||||
|
|
||||||
// Auf Disk speichern
|
// Alle Dateien an RVS senden + auf Disk speichern
|
||||||
if (base64) {
|
for (const { file, isPhoto } of pendingAttachments) {
|
||||||
persistAttachment(base64, msgId, name).then(filePath => {
|
const name = isPhoto ? file.fileName : file.name;
|
||||||
setMessages(prev => prev.map(m =>
|
const base64 = file.base64 || '';
|
||||||
m.id === msgId ? { ...m, attachments: m.attachments?.map(a => ({ ...a, uri: filePath })) } : m
|
const mimeType = file.type || '';
|
||||||
));
|
|
||||||
}).catch(() => {});
|
// Auf Disk speichern
|
||||||
|
if (base64) {
|
||||||
|
persistAttachment(base64, msgId + '_' + name, name).then(filePath => {
|
||||||
|
setMessages(prev => prev.map(m =>
|
||||||
|
m.id === msgId ? { ...m, attachments: m.attachments?.map(a =>
|
||||||
|
a.name === name && !a.uri?.startsWith('file://') ? { ...a, uri: filePath } : a
|
||||||
|
)} : m
|
||||||
|
));
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// An RVS senden
|
||||||
|
rvs.send('file', {
|
||||||
|
name,
|
||||||
|
type: mimeType,
|
||||||
|
size: file.size,
|
||||||
|
base64,
|
||||||
|
...(isPhoto && file.width && { width: file.width, height: file.height }),
|
||||||
|
...(location && { location }),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Datei an RVS senden
|
// Text als separate Nachricht (damit ARIA weiss was zu tun ist)
|
||||||
rvs.send('file', {
|
|
||||||
name,
|
|
||||||
type: mimeType,
|
|
||||||
size: file.size,
|
|
||||||
base64,
|
|
||||||
...(isPhoto && file.width && { width: file.width, height: file.height }),
|
|
||||||
...(location && { location }),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wenn Text dabei ist, als separate Chat-Nachricht senden (damit ARIA weiss was zu tun ist)
|
|
||||||
if (messageText) {
|
if (messageText) {
|
||||||
rvs.send('chat', {
|
rvs.send('chat', {
|
||||||
text: messageText,
|
text: messageText,
|
||||||
@@ -519,9 +532,9 @@ const ChatScreen: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setPendingAttachment(null);
|
setPendingAttachments([]);
|
||||||
setInputText('');
|
setInputText('');
|
||||||
}, [pendingAttachment, getCurrentLocation]);
|
}, [pendingAttachments, getCurrentLocation]);
|
||||||
}, [getCurrentLocation]);
|
}, [getCurrentLocation]);
|
||||||
|
|
||||||
// --- Rendering ---
|
// --- Rendering ---
|
||||||
@@ -670,24 +683,36 @@ const ChatScreen: React.FC = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Pending Anhang Vorschau */}
|
{/* Pending Anhaenge Vorschau */}
|
||||||
{pendingAttachment && (
|
{pendingAttachments.length > 0 && (
|
||||||
<View style={styles.pendingBar}>
|
<View style={styles.pendingBar}>
|
||||||
{pendingAttachment.file.type?.startsWith('image/') || pendingAttachment.isPhoto ? (
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={{flex: 1}}>
|
||||||
<Image
|
{pendingAttachments.map((att, idx) => (
|
||||||
source={{ uri: pendingAttachment.file.base64
|
<View key={idx} style={styles.pendingItem}>
|
||||||
? `data:${pendingAttachment.file.type};base64,${pendingAttachment.file.base64}`
|
{att.file.type?.startsWith('image/') || att.isPhoto ? (
|
||||||
: pendingAttachment.file.uri }}
|
<Image
|
||||||
style={styles.pendingThumb}
|
source={{ uri: att.file.base64
|
||||||
/>
|
? `data:${att.file.type};base64,${att.file.base64}`
|
||||||
) : (
|
: att.file.uri }}
|
||||||
<Text style={{fontSize: 24, marginRight: 8}}>{'\uD83D\uDCC4'}</Text>
|
style={styles.pendingThumb}
|
||||||
)}
|
/>
|
||||||
<Text style={styles.pendingName} numberOfLines={1}>
|
) : (
|
||||||
{pendingAttachment.isPhoto ? pendingAttachment.file.fileName : pendingAttachment.file.name}
|
<View style={[styles.pendingThumb, {justifyContent: 'center', alignItems: 'center'}]}>
|
||||||
</Text>
|
<Text style={{fontSize: 20}}>{'\uD83D\uDCC4'}</Text>
|
||||||
<TouchableOpacity onPress={() => setPendingAttachment(null)}>
|
</View>
|
||||||
<Text style={{color: '#FF3B30', fontSize: 18, paddingHorizontal: 8}}>X</Text>
|
)}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.pendingRemove}
|
||||||
|
onPress={() => setPendingAttachments(prev => prev.filter((_, i) => i !== idx))}
|
||||||
|
>
|
||||||
|
<Text style={{color: '#fff', fontSize: 10, fontWeight: 'bold'}}>X</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
<Text style={{color: '#8888AA', fontSize: 11, marginLeft: 8}}>{pendingAttachments.length}</Text>
|
||||||
|
<TouchableOpacity onPress={() => setPendingAttachments([])}>
|
||||||
|
<Text style={{color: '#FF3B30', fontSize: 14, paddingHorizontal: 8}}>Alle X</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@@ -714,7 +739,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
style={styles.textInput}
|
style={styles.textInput}
|
||||||
value={inputText}
|
value={inputText}
|
||||||
onChangeText={setInputText}
|
onChangeText={setInputText}
|
||||||
placeholder={pendingAttachment ? "Text zum Anhang (optional)..." : "Nachricht an ARIA..."}
|
placeholder={pendingAttachments.length > 0 ? "Text zu den Anhaengen (optional)..." : "Nachricht an ARIA..."}
|
||||||
placeholderTextColor="#555570"
|
placeholderTextColor="#555570"
|
||||||
multiline
|
multiline
|
||||||
maxLength={4000}
|
maxLength={4000}
|
||||||
@@ -723,7 +748,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Senden oder Sprache */}
|
{/* Senden oder Sprache */}
|
||||||
{inputText.trim() || pendingAttachment ? (
|
{inputText.trim() || pendingAttachments.length > 0 ? (
|
||||||
<TouchableOpacity style={styles.sendButton} onPress={sendTextMessage}>
|
<TouchableOpacity style={styles.sendButton} onPress={sendTextMessage}>
|
||||||
<Text style={styles.sendIcon}>{'\u2B06\uFE0F'}</Text>
|
<Text style={styles.sendIcon}>{'\u2B06\uFE0F'}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -963,17 +988,26 @@ const styles = StyleSheet.create({
|
|||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
borderTopColor: '#2A2A3E',
|
borderTopColor: '#2A2A3E',
|
||||||
},
|
},
|
||||||
pendingThumb: {
|
pendingItem: {
|
||||||
width: 40,
|
position: 'relative',
|
||||||
height: 40,
|
|
||||||
borderRadius: 6,
|
|
||||||
marginRight: 8,
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
pendingThumb: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 6,
|
||||||
backgroundColor: '#0D0D1A',
|
backgroundColor: '#0D0D1A',
|
||||||
},
|
},
|
||||||
pendingName: {
|
pendingRemove: {
|
||||||
flex: 1,
|
position: 'absolute',
|
||||||
color: '#E0E0F0',
|
top: -4,
|
||||||
fontSize: 13,
|
right: -4,
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
borderRadius: 9,
|
||||||
|
backgroundColor: '#FF3B30',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
searchBar: {
|
searchBar: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|||||||
+81
-7
@@ -205,8 +205,14 @@
|
|||||||
<span><span style="animation:pulse 1s infinite;">💭</span> <span id="thinking-text">ARIA denkt...</span></span>
|
<span><span style="animation:pulse 1s infinite;">💭</span> <span id="thinking-text">ARIA denkt...</span></span>
|
||||||
<button class="btn secondary" onclick="cancelRequest()" style="padding:2px 10px;font-size:11px;color:#FF3B30;border-color:#FF3B30;">Abbrechen</button>
|
<button class="btn secondary" onclick="cancelRequest()" style="padding:2px 10px;font-size:11px;color:#FF3B30;border-color:#FF3B30;">Abbrechen</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="diag-pending-attachments" style="display:none;padding:6px 10px;background:#1E1E2E;border-radius:6px 6px 0 0;margin-bottom:-4px;display:flex;gap:6px;flex-wrap:wrap;align-items:center;">
|
||||||
|
</div>
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<input type="text" id="chat-input" placeholder="Nachricht an ARIA...">
|
<label class="btn secondary" style="padding:6px 10px;cursor:pointer;font-size:14px;" title="Datei anhaengen">
|
||||||
|
📎
|
||||||
|
<input type="file" id="diag-file-input" multiple accept="image/*,application/pdf,.doc,.docx,.txt" style="display:none;" onchange="handleDiagFileSelect(this.files)">
|
||||||
|
</label>
|
||||||
|
<input type="text" id="chat-input" placeholder="Nachricht an ARIA..." onpaste="handleDiagPaste(event)">
|
||||||
<button class="btn" id="btn-gw" onclick="testGateway()">Gateway senden</button>
|
<button class="btn" id="btn-gw" onclick="testGateway()">Gateway senden</button>
|
||||||
<button class="btn" id="btn-rvs" onclick="testRVS()">Via RVS senden</button>
|
<button class="btn" id="btn-rvs" onclick="testRVS()">Via RVS senden</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -939,21 +945,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendDiagAttachments() {
|
||||||
|
// Alle pending Dateien an RVS senden
|
||||||
|
for (const f of diagPendingFiles) {
|
||||||
|
send({ action: 'send_file', name: f.name, type: f.type, size: f.size, base64: f.base64 });
|
||||||
|
}
|
||||||
|
if (diagPendingFiles.length > 0) {
|
||||||
|
addChat('sent', `${diagPendingFiles.length} Anhang/Anhaenge`, 'Datei');
|
||||||
|
}
|
||||||
|
diagPendingFiles = [];
|
||||||
|
renderDiagPending();
|
||||||
|
}
|
||||||
|
|
||||||
function testGateway() {
|
function testGateway() {
|
||||||
const input = document.getElementById('chat-input');
|
const input = document.getElementById('chat-input');
|
||||||
const text = input.value.trim();
|
const text = input.value.trim();
|
||||||
if (!text) return;
|
if (!text && diagPendingFiles.length === 0) return;
|
||||||
addChat('sent', text, 'Gateway direkt');
|
if (diagPendingFiles.length > 0) sendDiagAttachments();
|
||||||
send({ action: 'test_gateway', text });
|
if (text) {
|
||||||
|
addChat('sent', text, 'Gateway direkt');
|
||||||
|
send({ action: 'test_gateway', text });
|
||||||
|
}
|
||||||
input.value = '';
|
input.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function testRVS() {
|
function testRVS() {
|
||||||
const input = document.getElementById('chat-input');
|
const input = document.getElementById('chat-input');
|
||||||
const text = input.value.trim();
|
const text = input.value.trim();
|
||||||
if (!text) return;
|
if (!text && diagPendingFiles.length === 0) return;
|
||||||
addChat('sent', text, 'via RVS');
|
if (diagPendingFiles.length > 0) sendDiagAttachments();
|
||||||
send({ action: 'test_rvs', text });
|
if (text) {
|
||||||
|
addChat('sent', text, 'via RVS');
|
||||||
|
send({ action: 'test_rvs', text });
|
||||||
|
}
|
||||||
input.value = '';
|
input.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1302,6 +1326,56 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Diagnostic Anhang-Handling ─────────────
|
||||||
|
let diagPendingFiles = [];
|
||||||
|
|
||||||
|
function handleDiagFileSelect(files) {
|
||||||
|
for (const file of files) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
const base64 = reader.result.split(',')[1];
|
||||||
|
diagPendingFiles.push({ name: file.name, type: file.type, size: file.size, base64 });
|
||||||
|
renderDiagPending();
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDiagPaste(event) {
|
||||||
|
const items = event.clipboardData?.items;
|
||||||
|
if (!items) return;
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.kind === 'file') {
|
||||||
|
event.preventDefault();
|
||||||
|
const file = item.getAsFile();
|
||||||
|
if (file) handleDiagFileSelect([file]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDiagPending() {
|
||||||
|
const container = document.getElementById('diag-pending-attachments');
|
||||||
|
if (diagPendingFiles.length === 0) {
|
||||||
|
container.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
container.style.display = 'flex';
|
||||||
|
container.innerHTML = diagPendingFiles.map((f, i) => {
|
||||||
|
const isImage = f.type.startsWith('image/');
|
||||||
|
const preview = isImage ? `<img src="data:${f.type};base64,${f.base64}" style="width:40px;height:40px;border-radius:4px;object-fit:cover;">` : `<span style="font-size:20px;">📄</span>`;
|
||||||
|
return `<div style="position:relative;display:inline-block;">
|
||||||
|
${preview}
|
||||||
|
<span onclick="removeDiagPending(${i})" style="position:absolute;top:-4px;right:-4px;width:16px;height:16px;border-radius:8px;background:#FF3B30;color:#fff;font-size:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;">X</span>
|
||||||
|
</div>`;
|
||||||
|
}).join('') + `<span style="color:#8888AA;font-size:11px;margin-left:4px;">${diagPendingFiles.length} Datei(en)</span>
|
||||||
|
<span onclick="diagPendingFiles=[];renderDiagPending();" style="color:#FF3B30;font-size:11px;cursor:pointer;margin-left:8px;">Alle X</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDiagPending(idx) {
|
||||||
|
diagPendingFiles.splice(idx, 1);
|
||||||
|
renderDiagPending();
|
||||||
|
}
|
||||||
|
|
||||||
// ── Abbrechen ──────────────────────────────
|
// ── Abbrechen ──────────────────────────────
|
||||||
function cancelRequest() {
|
function cancelRequest() {
|
||||||
send({ action: 'cancel_request' });
|
send({ action: 'cancel_request' });
|
||||||
|
|||||||
@@ -1181,6 +1181,14 @@ wss.on("connection", (ws) => {
|
|||||||
if (ws._sshSock) ws._sshSock.write(msg.data);
|
if (ws._sshSock) ws._sshSock.write(msg.data);
|
||||||
} else if (msg.action === "live_ssh_close") {
|
} else if (msg.action === "live_ssh_close") {
|
||||||
if (ws._sshSock) { ws._sshSock.end(); ws._sshSock = null; }
|
if (ws._sshSock) { ws._sshSock.end(); ws._sshSock = null; }
|
||||||
|
} else if (msg.action === "send_file") {
|
||||||
|
// Datei von Diagnostic an Bridge via RVS senden
|
||||||
|
sendToRVS_raw({
|
||||||
|
type: "file",
|
||||||
|
payload: { name: msg.name, type: msg.type, size: msg.size, base64: msg.base64 },
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
log("info", "server", `Datei gesendet: ${msg.name} (${msg.type})`);
|
||||||
} else if (msg.action === "cancel_request") {
|
} else if (msg.action === "cancel_request") {
|
||||||
// Laufende Anfrage abbrechen — doctor --fix beendet stuck runs
|
// Laufende Anfrage abbrechen — doctor --fix beendet stuck runs
|
||||||
log("warn", "server", "Anfrage abgebrochen — fuehre doctor --fix aus");
|
log("warn", "server", "Anfrage abgebrochen — fuehre doctor --fix aus");
|
||||||
|
|||||||
Reference in New Issue
Block a user