fix+feat(projects): Spinner-Bug, Back-Button, kollabierbare Chat-Bloecke, File-Filter
Drei Stefan-Bugs aus dem ersten Deploy-Test plus die fehlenden Polish-
Features fuer die Projekt-Funktion.
Fixes:
- ProjectsBrowser-Spinner-Hang: useRef-Pattern statt useCallback([onActive
Changed]) — Parent uebergibt inline-arrow-Callbacks, neue Identitaet
jedes Render → useCallback recomputes → useEffect refeuert → infinite
Spinner. Fix: Ref-Bridge fuer Callbacks, useCallback mit empty deps.
- ChatScreen Banner: zusaetzlicher × Hauptchat-Button rechts (sichtbar
nur wenn Projekt aktiv) — ein Tap und zurueck zum Hauptthread, ohne
Modal-Umweg.
Features:
- Brain ChatOut.project_id: aktive Projekt-ID NACH dem Turn (kann
durch project_enter/exit-Tools waehrend Turn gewechselt sein). Bridge
liest sie aus dem /chat-Response und haengt sie an jeden ARIA-Chat-
Broadcast als payload.projectId.
- App: ChatMessage.projectId-Feld. User-Bubbles werden mit aktiver
Projekt-ID getaggt vor dem Senden (auch im RVS-Payload). ARIA-Bubbles
kriegen die ID vom Bridge.
- App: Chat-Verlauf rendert aufeinanderfolgende Project-Messages als
einklappbaren Block mit Header (▶/▼ + Projekt-Name + Count). Auto-
Collapse beim Projekt-Wechsel (altes ein, neues aus), Default beim
ersten Render: alle inaktiven Projekte eingeklappt.
- File-Manager Project-Tagging:
- diagnostic/server.js: Manifest /shared/config/file_projects.json
+ /api/files-list returnt projectId pro Datei + neuer Endpoint
/api/files-set-project.
- bridge/aria_bridge.py: nach App-Upload Auto-Tag mit aktivem Projekt
(Brain-Status-Query, best-effort fail-silent).
- App SettingsScreen: scrollbare Projekt-Pill-Reihe als Filter, default
auf aktives Projekt wenn vorhanden, sonst "Alle Projekte".
- Diagnostic: zweites Dropdown im Files-Tab, baut Projekt-Optionen
dynamisch aus /api/brain/projects/list.
Bewusst nicht drin (Folgeschritt):
- Per-File "Projekt zuweisen"-Action (Long-Press / Right-Click)
- Filter-Sync zwischen ChatScreen-Banner und SettingsScreen-Filter
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,7 @@ import SkillBrowser from '../components/SkillBrowser';
|
||||
import OAuthBrowser from '../components/OAuthBrowser';
|
||||
import VoiceIdEnrollment from '../components/VoiceIdEnrollment';
|
||||
import ProjectsBrowser from '../components/ProjectsBrowser';
|
||||
import brainApi from '../services/brainApi';
|
||||
import { isVerboseLogging, setVerboseLogging, isDebugLogsToBridge, setDebugLogsToBridge, APP_LOG_EVENT } from '../services/logger';
|
||||
import {
|
||||
isWakeReadySoundEnabled,
|
||||
@@ -204,7 +205,9 @@ const SettingsScreen: React.FC = () => {
|
||||
const [availableVoices, setAvailableVoices] = useState<Array<{name: string, size: number}>>([]);
|
||||
// Datei-Manager
|
||||
const [fileManagerOpen, setFileManagerOpen] = useState(false);
|
||||
const [fileManagerFiles, setFileManagerFiles] = useState<Array<{name: string; path: string; size: number; mtime: number; fromAria: boolean}>>([]);
|
||||
const [fileManagerFiles, setFileManagerFiles] = useState<Array<{name: string; path: string; size: number; mtime: number; fromAria: boolean; projectId?: string}>>([]);
|
||||
const [fileFilterProjectId, setFileFilterProjectId] = useState<string>('__all__');
|
||||
const [fileFilterProjects, setFileFilterProjects] = useState<Array<{id: string; name: string}>>([]);
|
||||
const [fileManagerLoading, setFileManagerLoading] = useState(false);
|
||||
const [fileManagerError, setFileManagerError] = useState('');
|
||||
const [fileManagerSearch, setFileManagerSearch] = useState('');
|
||||
@@ -726,6 +729,20 @@ const SettingsScreen: React.FC = () => {
|
||||
return () => unsub();
|
||||
}, [fileManagerOpen]);
|
||||
|
||||
// Beim Oeffnen des Datei-Managers: Projekt-Liste laden fuer den Filter.
|
||||
useEffect(() => {
|
||||
if (!fileManagerOpen) return;
|
||||
brainApi.listProjects(true)
|
||||
.then(list => setFileFilterProjects(list.map(p => ({ id: p.id, name: p.name }))))
|
||||
.catch(() => {});
|
||||
// Default-Filter: aktives Projekt (falls vorhanden), sonst "alle".
|
||||
brainApi.getProjectStatus()
|
||||
.then(s => {
|
||||
if (s.active_id) setFileFilterProjectId(s.active_id);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [fileManagerOpen]);
|
||||
|
||||
// --- QR-Code scannen ---
|
||||
|
||||
const openQRScanner = useCallback(() => {
|
||||
@@ -962,6 +979,29 @@ const SettingsScreen: React.FC = () => {
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
{/* Projekt-Filter: scrollbare Pill-Reihe. „Alle Projekte" + „Hauptchat" +
|
||||
ein Pill pro Projekt. Default = aktives Projekt (siehe useEffect oben). */}
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}
|
||||
style={{marginTop:6}} contentContainerStyle={{gap:6, paddingRight:8}}>
|
||||
{[
|
||||
{ id: '__all__', name: '📁 Alle Projekte' },
|
||||
{ id: '', name: '💬 Hauptchat' },
|
||||
...fileFilterProjects,
|
||||
].map(p => (
|
||||
<TouchableOpacity
|
||||
key={p.id || 'mainchat'}
|
||||
onPress={() => setFileFilterProjectId(p.id)}
|
||||
style={{
|
||||
paddingVertical:6, paddingHorizontal:12, borderRadius:14,
|
||||
backgroundColor: fileFilterProjectId === p.id ? '#34C759' : '#1E1E2E',
|
||||
}}
|
||||
>
|
||||
<Text style={{color: fileFilterProjectId === p.id ? '#fff' : '#8888AA', fontSize:12}}>
|
||||
{p.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
{fileManagerLoading ? (
|
||||
<Text style={{color:'#8888AA', textAlign:'center', marginTop:20}}>Lade...</Text>
|
||||
@@ -972,6 +1012,11 @@ const SettingsScreen: React.FC = () => {
|
||||
let files = fileManagerFiles;
|
||||
if (fileManagerFilter === 'aria') files = files.filter(f => f.fromAria);
|
||||
else if (fileManagerFilter === 'user') files = files.filter(f => !f.fromAria);
|
||||
// Projekt-Filter: '__all__' = alles, '' = Hauptchat (kein project_id),
|
||||
// sonst exakte project_id-Match.
|
||||
if (fileFilterProjectId !== '__all__') {
|
||||
files = files.filter(f => (f.projectId || '') === fileFilterProjectId);
|
||||
}
|
||||
if (fileManagerSearch) {
|
||||
const q = fileManagerSearch.toLowerCase();
|
||||
files = files.filter(f => f.name.toLowerCase().includes(q));
|
||||
|
||||
Reference in New Issue
Block a user