feat(projects): Threads im Hauptchat verankert (Stefan-Konzept)
Projekte sind benannte Thema-Bündel die voice-gesteuert via Brain-Tools
geöffnet/verlassen werden. Default-Mode bleibt der Hauptthread — Projekte
sind eine optionale Bühne. Anchored-not-replaced: App-Open landet immer
im Hauptchat, Projekte sind nur sichtbar wenn aktiv betreten.
Brain:
- projects.py: CRUD + Fuzzy-Find + Active-State-Pointer
(/shared/config/projects.json + active_project.txt).
- conversation.py: Turn.project_id-Feld + window(project_id) Filter.
- agent.py: 6 Meta-Tools — project_create / _enter / _exit / _list /
_summary / _end. chat() liest aktive Projekt-ID, taggt User+Assistant-
Turns damit, filtert das LLM-Window auf Projekt-Kontext und ergaenzt
den System-Prompt um den aktiven Projekt-Hinweis. touch_project pflegt
last_activity_at + turn_count.
- main.py: REST-Endpoints /projects/{status,list,create,switch,
{id}/end,{id}/archive, PATCH /{id}}.
Bridge + RVS:
- aria_bridge.py: project_changed Event-Propagation Brain → RVS-Broadcast
damit App + Diagnostic ihre Banner refreshen.
- rvs/server.js: project_changed in ALLOWED_TYPES.
App:
- brainApi.ts: Project-Type + 6 API-Methoden.
- ProjectsBrowser.tsx (neue Komponente, ~340 Zeilen): Status-Header,
Hauptchat als Erster-Eintrag, Projekt-Liste mit Aktiv-Marker, Long-Press
zum Editieren, Modals fuer Neu/Edit/End/Archiv.
- ChatScreen.tsx: Banner unterhalb des Status-Bars zeigt aktives Projekt
oder „Hauptchat" — Tap öffnet ProjectsBrowser als Modal. Aktive Projekt-
Info wird bei Mount + bei project_changed-Events refreshed.
- SettingsScreen.tsx: Neue Section 📁 „Projekte" zeigt ProjectsBrowser inline.
Diagnostic:
- Neue Sektion im Brain-Tab mit Liste, Aktiv-Marker, Beenden/Archivieren
pro Zeile, Modal fuer Neu. Lädt automatisch bei Brain-Tab + bei
project_changed-Event-Broadcast.
Was bewusst NICHT drin ist (Folgeschritte):
- Per-Message Filter im Chat-Verlauf (zeigt aktuell alle Bubbles, Banner
zeigt Kontext) — App müsste Chat-History per project_id filtern.
- Files-by-Project Tagging.
- Inline-Collapse-Bloecke im Chat-Verlauf.
- Sub-Projekte (Stefan-Entscheidung: weglassen, „Mama-tauglich").
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,8 @@ import ErrorBoundary from '../components/ErrorBoundary';
|
||||
import rvs, { RVSMessage, ConnectionState } from '../services/rvs';
|
||||
import audioService from '../services/audio';
|
||||
import wakeWordService, { loadPassiveListenMs } from '../services/wakeword';
|
||||
import ProjectsBrowser from '../components/ProjectsBrowser';
|
||||
import brainApi, { Project as BrainProject } from '../services/brainApi';
|
||||
import phoneCallService from '../services/phoneCall';
|
||||
import { playWakeReadySound } from '../services/wakeReadySound';
|
||||
import {
|
||||
@@ -280,6 +282,8 @@ const ChatScreen: React.FC = () => {
|
||||
const [showJumpDown, setShowJumpDown] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchVisible, setSearchVisible] = useState(false);
|
||||
const [projectsVisible, setProjectsVisible] = useState(false);
|
||||
const [activeProject, setActiveProject] = useState<BrainProject | null>(null);
|
||||
const [searchIndex, setSearchIndex] = useState(0); // welcher Treffer aktiv ist
|
||||
const [pendingAttachments, setPendingAttachments] = useState<{file: any, isPhoto: boolean}[]>([]);
|
||||
const [agentActivity, setAgentActivity] = useState<{activity: string, tool: string}>({activity: 'idle', tool: ''});
|
||||
@@ -457,6 +461,19 @@ const ChatScreen: React.FC = () => {
|
||||
|
||||
// TTS- + GPS-Settings beim Mount + alle 2s neu laden (damit Settings-Toggle
|
||||
// sofort greift, ohne Context- oder Event-System)
|
||||
// Aktives Projekt initial laden + bei RVS-Reconnect refreshen.
|
||||
// Wird zusaetzlich nach jedem chat-Response refreshed (siehe handleAriaMessage).
|
||||
useEffect(() => {
|
||||
const loadProject = () => {
|
||||
brainApi.getProjectStatus()
|
||||
.then(s => setActiveProject(s.active || null))
|
||||
.catch(() => {});
|
||||
};
|
||||
loadProject();
|
||||
const unsub = rvs.onStateChange(state => { if (state === 'connected') loadProject(); });
|
||||
return () => unsub();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadSettings = async () => {
|
||||
const enabled = await AsyncStorage.getItem('aria_tts_enabled');
|
||||
@@ -795,6 +812,15 @@ const ChatScreen: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// project_changed: ARIA hat in einem Tool-Call ein Projekt erstellt /
|
||||
// betreten / verlassen / beendet. Banner refreshen.
|
||||
if (message.type === 'project_changed') {
|
||||
brainApi.getProjectStatus()
|
||||
.then(s => setActiveProject(s.active || null))
|
||||
.catch(() => {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'skill_created') {
|
||||
const p = (message.payload || {}) as any;
|
||||
const skillMsg: ChatMessage = {
|
||||
@@ -2457,6 +2483,32 @@ const ChatScreen: React.FC = () => {
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Projekt-Indicator: zeigt Hauptchat oder aktives Projekt, Tap öffnet Liste */}
|
||||
<TouchableOpacity
|
||||
onPress={() => setProjectsVisible(true)}
|
||||
style={{
|
||||
flexDirection: 'row', alignItems: 'center',
|
||||
paddingHorizontal: 12, paddingVertical: 6,
|
||||
backgroundColor: activeProject ? 'rgba(52,199,89,0.10)' : 'transparent',
|
||||
borderBottomWidth: activeProject ? 2 : 1,
|
||||
borderColor: activeProject ? '#34C759' : '#1E1E2E',
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 13, color: activeProject ? '#34C759' : '#8888AA', fontWeight: activeProject ? '700' : '500', flex: 1 }} numberOfLines={1}>
|
||||
{activeProject ? `📁 ${activeProject.name}` : '💬 Hauptchat'}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 11, color: '#555570' }}>
|
||||
{activeProject ? 'wechseln ›' : 'Projekte ›'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Projekt-Modal */}
|
||||
<ProjectsBrowser
|
||||
visible={projectsVisible}
|
||||
onClose={() => setProjectsVisible(false)}
|
||||
onActiveChanged={(p) => setActiveProject(p)}
|
||||
/>
|
||||
|
||||
{/* Suchleiste mit Treffer-Navigation */}
|
||||
{searchVisible && (
|
||||
<View style={styles.searchBar}>
|
||||
|
||||
Reference in New Issue
Block a user