fix: AI loop prevention (no tools on last iteration), calendar button first, mail filter in toolbar, AI folder rename query invalidation
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
This commit is contained in:
@@ -451,9 +451,12 @@ async def stream_chat(
|
||||
# Agent loop: LLM → tool calls → execute → feed back → repeat
|
||||
max_iterations = 5
|
||||
for iteration in range(max_iterations):
|
||||
# Add tools to params if available
|
||||
if tool_schemas:
|
||||
# Add tools to params if available, but NOT on the last iteration
|
||||
# to force the LLM to give a final answer instead of looping
|
||||
if tool_schemas and iteration < max_iterations - 1:
|
||||
params["tools"] = tool_schemas
|
||||
elif "tools" in params:
|
||||
del params["tools"]
|
||||
|
||||
# Stream LLM response
|
||||
collected_content = ""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
fetchSessions, createSession, deleteSession, updateSession,
|
||||
fetchFolders, createFolder, deleteFolder, updateFolder,
|
||||
@@ -261,6 +262,7 @@ export function SessionList({ activeSessionId, onSelectSession, isSidebar, class
|
||||
const [dragSessionId, setDragSessionId] = useState<string | null>(null);
|
||||
const [dragFolderId, setDragFolderId] = useState<string | null>(null);
|
||||
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
@@ -287,7 +289,10 @@ export function SessionList({ activeSessionId, onSelectSession, isSidebar, class
|
||||
if (!newName) return;
|
||||
try {
|
||||
if (type === 'session') await updateSession(id, { title: newName });
|
||||
else await updateFolder(id, { name: newName });
|
||||
else {
|
||||
await updateFolder(id, { name: newName });
|
||||
queryClient.invalidateQueries({ queryKey: ['ai', 'folders'] });
|
||||
}
|
||||
loadData();
|
||||
} catch (e) { setError(e instanceof Error ? e.message : 'Rename failed'); }
|
||||
};
|
||||
|
||||
@@ -327,6 +327,31 @@ export function CalendarPage() {
|
||||
|
||||
useEffect(() => {
|
||||
const items = [
|
||||
// New appointment — ganz links
|
||||
{
|
||||
id: 'new-appointment',
|
||||
plugin: 'calendar',
|
||||
label: t('calendar.newAppointment'),
|
||||
group: 'actions',
|
||||
onClick: () => {
|
||||
openWindow({
|
||||
title: t('calendar.newAppointment'),
|
||||
type: 'appointment-create',
|
||||
component: AppointmentEditForm,
|
||||
componentProps: {
|
||||
entry: null,
|
||||
prefillDate: null,
|
||||
calendars,
|
||||
defaultCalendarId: activeCalendarId,
|
||||
onSaved: handleSaved,
|
||||
onDeleted: handleDeleted,
|
||||
},
|
||||
});
|
||||
},
|
||||
icon: (
|
||||
<Plus className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
),
|
||||
},
|
||||
// Navigation group
|
||||
{
|
||||
id: 'nav-prev',
|
||||
@@ -384,31 +409,6 @@ export function CalendarPage() {
|
||||
onSelect: (value: string) => setViewMode(value as CalendarViewMode),
|
||||
onClick: () => {},
|
||||
},
|
||||
// Actions group
|
||||
{
|
||||
id: 'new-appointment',
|
||||
plugin: 'calendar',
|
||||
label: t('calendar.newAppointment'),
|
||||
group: 'actions',
|
||||
onClick: () => {
|
||||
openWindow({
|
||||
title: t('calendar.newAppointment'),
|
||||
type: 'appointment-create',
|
||||
component: AppointmentEditForm,
|
||||
componentProps: {
|
||||
entry: null,
|
||||
prefillDate: null,
|
||||
calendars,
|
||||
defaultCalendarId: activeCalendarId,
|
||||
onSaved: handleSaved,
|
||||
onDeleted: handleDeleted,
|
||||
},
|
||||
});
|
||||
},
|
||||
icon: (
|
||||
<Plus className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'ics-import',
|
||||
plugin: 'calendar',
|
||||
|
||||
+37
-21
@@ -594,6 +594,43 @@ export function MailPage() {
|
||||
onSearch: handleSearch,
|
||||
onClick: () => {},
|
||||
},
|
||||
// FilterPanel — SavedFilterBar + TagSelector
|
||||
{
|
||||
id: 'filter-panel',
|
||||
plugin: 'mail',
|
||||
label: 'Filter',
|
||||
type: 'custom' as const,
|
||||
group: 'filter',
|
||||
customComponent: (
|
||||
<SavedFilterBar
|
||||
entityType="mail"
|
||||
currentFilters={{ search: searchQuery, sortBy, sortOrder, folderId: selectedFolderId }}
|
||||
onApplyFilter={(criteria) => {
|
||||
if (criteria.search !== undefined) handleSearch(criteria.search);
|
||||
if (criteria.sortBy) setSortBy(criteria.sortBy as 'date' | 'from' | 'subject');
|
||||
if (criteria.sortOrder) setSortOrder(criteria.sortOrder as 'asc' | 'desc');
|
||||
}}
|
||||
/>
|
||||
),
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
id: 'tag-filter',
|
||||
plugin: 'mail',
|
||||
label: 'Tags',
|
||||
type: 'custom' as const,
|
||||
group: 'filter',
|
||||
customComponent: (
|
||||
<TagSelector
|
||||
entityType="file"
|
||||
entityId={selectedMail?.id ?? ''}
|
||||
selectedTags={selectedTags}
|
||||
onChange={setSelectedTags}
|
||||
placeholder={t('tags.selectPlaceholder', 'Tags filtern...')}
|
||||
/>
|
||||
),
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
id: 'compose',
|
||||
plugin: 'mail',
|
||||
@@ -814,27 +851,6 @@ export function MailPage() {
|
||||
className="border-r border-secondary-200 bg-white"
|
||||
data-testid="mail-list-pane"
|
||||
>
|
||||
{/* Saved filter bar + tag selector */}
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-secondary-200 bg-secondary-50 flex-wrap" data-testid="mail-filter-bar">
|
||||
<SavedFilterBar
|
||||
entityType="mail"
|
||||
currentFilters={{ search: searchQuery, sortBy, sortOrder, folderId: selectedFolderId }}
|
||||
onApplyFilter={(criteria) => {
|
||||
if (criteria.search !== undefined) handleSearch(criteria.search);
|
||||
if (criteria.sortBy) setSortBy(criteria.sortBy as 'date' | 'from' | 'subject');
|
||||
if (criteria.sortOrder) setSortOrder(criteria.sortOrder as 'asc' | 'desc');
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1 min-w-[180px]" data-testid="mail-tag-selector">
|
||||
<TagSelector
|
||||
entityType="file"
|
||||
entityId={selectedMail?.id ?? ''}
|
||||
selectedTags={selectedTags}
|
||||
onChange={setSelectedTags}
|
||||
placeholder={t('tags.selectPlaceholder', 'Tags filtern...')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<MailList
|
||||
mails={mails}
|
||||
selectedMailId={selectedMail?.id || null}
|
||||
|
||||
Reference in New Issue
Block a user