mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat(chat): add conversation pinning and date-based sections
- Add pin/unpin functionality with UI button and visual indicator - Group conversations by date sections (Today, Yesterday, This Week, This Month, Older) - Pinned conversations appear in separate "Angepinnt" section at top - Refactor services and store to use shared types from @chat/types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
819e4c9a2f
commit
4f06301fe3
9 changed files with 298 additions and 230 deletions
|
|
@ -42,6 +42,76 @@
|
|||
: conversations
|
||||
);
|
||||
|
||||
// Split into pinned and unpinned
|
||||
let pinnedConversations = $derived(filteredConversations.filter((conv) => conv.isPinned));
|
||||
let unpinnedConversations = $derived(filteredConversations.filter((conv) => !conv.isPinned));
|
||||
|
||||
// Date section types
|
||||
type DateSection = 'today' | 'yesterday' | 'thisWeek' | 'thisMonth' | 'older';
|
||||
|
||||
const sectionLabels: Record<DateSection, string> = {
|
||||
today: 'Heute',
|
||||
yesterday: 'Gestern',
|
||||
thisWeek: 'Diese Woche',
|
||||
thisMonth: 'Dieser Monat',
|
||||
older: 'Älter'
|
||||
};
|
||||
|
||||
function getDateSection(dateString: string): DateSection {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
|
||||
// Reset time to compare just dates
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
|
||||
const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||||
|
||||
if (dateOnly.getTime() === today.getTime()) {
|
||||
return 'today';
|
||||
}
|
||||
if (dateOnly.getTime() === yesterday.getTime()) {
|
||||
return 'yesterday';
|
||||
}
|
||||
|
||||
// This week (last 7 days)
|
||||
const weekAgo = new Date(today);
|
||||
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||
if (dateOnly > weekAgo) {
|
||||
return 'thisWeek';
|
||||
}
|
||||
|
||||
// This month
|
||||
const monthAgo = new Date(today);
|
||||
monthAgo.setDate(monthAgo.getDate() - 30);
|
||||
if (dateOnly > monthAgo) {
|
||||
return 'thisMonth';
|
||||
}
|
||||
|
||||
return 'older';
|
||||
}
|
||||
|
||||
// Group unpinned conversations by date sections
|
||||
let groupedConversations = $derived(() => {
|
||||
const groups: Record<DateSection, typeof unpinnedConversations> = {
|
||||
today: [],
|
||||
yesterday: [],
|
||||
thisWeek: [],
|
||||
thisMonth: [],
|
||||
older: []
|
||||
};
|
||||
|
||||
for (const conv of unpinnedConversations) {
|
||||
const section = getDateSection(conv.updatedAt || conv.createdAt);
|
||||
groups[section].push(conv);
|
||||
}
|
||||
|
||||
return groups;
|
||||
});
|
||||
|
||||
const sectionOrder: DateSection[] = ['today', 'yesterday', 'thisWeek', 'thisMonth', 'older'];
|
||||
|
||||
// Resizer handlers
|
||||
function startResize(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
|
|
@ -66,7 +136,7 @@
|
|||
window.addEventListener('mouseup', stopResize);
|
||||
|
||||
if (authStore.user) {
|
||||
conversationsStore.loadConversations(authStore.user.id);
|
||||
conversationsStore.loadConversations();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -240,81 +310,160 @@
|
|||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
{#each filteredConversations as conv (conv.id)}
|
||||
<a
|
||||
href="/chat/{conv.id}"
|
||||
class="group block w-full rounded-xl bg-white/60 dark:bg-white/5 backdrop-blur-sm border border-black/10 dark:border-white/20 p-4 text-left transition-all mb-3 hover:shadow-md hover:bg-white/80 dark:hover:bg-white/10
|
||||
{isActive(conv.id)
|
||||
? 'bg-white/90 dark:bg-white/15 shadow-md border-primary/30'
|
||||
: ''}"
|
||||
>
|
||||
<!-- Title Row -->
|
||||
<div class="mb-1.5 flex items-center gap-2">
|
||||
{#if conv.isPinned}
|
||||
<PushPin
|
||||
size={16}
|
||||
weight="fill"
|
||||
class="flex-shrink-0 text-primary"
|
||||
/>
|
||||
{:else}
|
||||
<ChatCircle
|
||||
size={16}
|
||||
weight={isActive(conv.id) ? 'fill' : 'regular'}
|
||||
class="flex-shrink-0 {isActive(conv.id)
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'}"
|
||||
/>
|
||||
{/if}
|
||||
<h3 class="text-sm font-semibold line-clamp-1 text-foreground flex-1">
|
||||
{conv.title || 'Neue Konversation'}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- Preview -->
|
||||
<p class="mb-2 text-sm text-muted-foreground line-clamp-2">
|
||||
{getPreview(conv.title)}
|
||||
</p>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs text-muted-foreground">
|
||||
{formatDate(conv.updatedAt || conv.createdAt)}
|
||||
</span>
|
||||
<div class="flex items-center gap-1">
|
||||
{#if conv.documentMode}
|
||||
<span
|
||||
class="text-xs text-primary bg-primary/10 px-2 py-0.5 rounded-full"
|
||||
>
|
||||
Dokument
|
||||
</span>
|
||||
{/if}
|
||||
<!-- Action Buttons (visible on hover) -->
|
||||
<div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onclick={(e) => handleTogglePin(e, conv.id, conv.isPinned)}
|
||||
class="p-1.5 text-muted-foreground hover:text-primary hover:bg-primary/10 rounded-lg transition-colors {conv.isPinned ? 'text-primary' : ''}"
|
||||
title={conv.isPinned ? 'Nicht mehr anpinnen' : 'Anpinnen'}
|
||||
>
|
||||
<PushPin size={14} weight={conv.isPinned ? 'fill' : 'bold'} />
|
||||
</button>
|
||||
<button
|
||||
onclick={(e) => handleArchive(e, conv.id)}
|
||||
class="p-1.5 text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/10 rounded-lg transition-colors"
|
||||
title="Archivieren"
|
||||
>
|
||||
<Archive size={14} weight="bold" />
|
||||
</button>
|
||||
<button
|
||||
onclick={(e) => handleDelete(e, conv.id)}
|
||||
class="p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
|
||||
title="Löschen"
|
||||
>
|
||||
<Trash size={14} weight="bold" />
|
||||
</button>
|
||||
<!-- Pinned Section -->
|
||||
{#if pinnedConversations.length > 0}
|
||||
<div class="mb-5">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 flex items-center gap-1.5">
|
||||
<PushPin size={12} weight="fill" class="text-primary" />
|
||||
Angepinnt
|
||||
</h4>
|
||||
{#each pinnedConversations as conv (conv.id)}
|
||||
<a
|
||||
href="/chat/{conv.id}"
|
||||
class="group block w-full rounded-xl bg-white/60 dark:bg-white/5 backdrop-blur-sm border border-black/10 dark:border-white/20 p-4 text-left transition-all mb-3 hover:shadow-md hover:bg-white/80 dark:hover:bg-white/10
|
||||
{isActive(conv.id)
|
||||
? 'bg-white/90 dark:bg-white/15 shadow-md border-primary/30'
|
||||
: ''}"
|
||||
>
|
||||
<!-- Title Row -->
|
||||
<div class="mb-1.5 flex items-center gap-2">
|
||||
<PushPin
|
||||
size={16}
|
||||
weight="fill"
|
||||
class="flex-shrink-0 text-primary"
|
||||
/>
|
||||
<h3 class="text-sm font-semibold line-clamp-1 text-foreground flex-1">
|
||||
{conv.title || 'Neue Konversation'}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview -->
|
||||
<p class="mb-2 text-sm text-muted-foreground line-clamp-2">
|
||||
{getPreview(conv.title)}
|
||||
</p>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs text-muted-foreground">
|
||||
{formatDate(conv.updatedAt || conv.createdAt)}
|
||||
</span>
|
||||
<div class="flex items-center gap-1">
|
||||
{#if conv.documentMode}
|
||||
<span
|
||||
class="text-xs text-primary bg-primary/10 px-2 py-0.5 rounded-full"
|
||||
>
|
||||
Dokument
|
||||
</span>
|
||||
{/if}
|
||||
<!-- Action Buttons (visible on hover) -->
|
||||
<div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onclick={(e) => handleTogglePin(e, conv.id, true)}
|
||||
class="p-1.5 text-primary hover:text-primary hover:bg-primary/10 rounded-lg transition-colors"
|
||||
title="Nicht mehr anpinnen"
|
||||
>
|
||||
<PushPin size={14} weight="fill" />
|
||||
</button>
|
||||
<button
|
||||
onclick={(e) => handleArchive(e, conv.id)}
|
||||
class="p-1.5 text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/10 rounded-lg transition-colors"
|
||||
title="Archivieren"
|
||||
>
|
||||
<Archive size={14} weight="bold" />
|
||||
</button>
|
||||
<button
|
||||
onclick={(e) => handleDelete(e, conv.id)}
|
||||
class="p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
|
||||
title="Löschen"
|
||||
>
|
||||
<Trash size={14} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Grouped Conversations by Date -->
|
||||
{#each sectionOrder as section}
|
||||
{@const convs = groupedConversations()[section]}
|
||||
{#if convs.length > 0}
|
||||
<div class="mb-5">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">
|
||||
{sectionLabels[section]}
|
||||
</h4>
|
||||
{#each convs as conv (conv.id)}
|
||||
<a
|
||||
href="/chat/{conv.id}"
|
||||
class="group block w-full rounded-xl bg-white/60 dark:bg-white/5 backdrop-blur-sm border border-black/10 dark:border-white/20 p-4 text-left transition-all mb-3 hover:shadow-md hover:bg-white/80 dark:hover:bg-white/10
|
||||
{isActive(conv.id)
|
||||
? 'bg-white/90 dark:bg-white/15 shadow-md border-primary/30'
|
||||
: ''}"
|
||||
>
|
||||
<!-- Title Row -->
|
||||
<div class="mb-1.5 flex items-center gap-2">
|
||||
<ChatCircle
|
||||
size={16}
|
||||
weight={isActive(conv.id) ? 'fill' : 'regular'}
|
||||
class="flex-shrink-0 {isActive(conv.id)
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'}"
|
||||
/>
|
||||
<h3 class="text-sm font-semibold line-clamp-1 text-foreground flex-1">
|
||||
{conv.title || 'Neue Konversation'}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- Preview -->
|
||||
<p class="mb-2 text-sm text-muted-foreground line-clamp-2">
|
||||
{getPreview(conv.title)}
|
||||
</p>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs text-muted-foreground">
|
||||
{formatDate(conv.updatedAt || conv.createdAt)}
|
||||
</span>
|
||||
<div class="flex items-center gap-1">
|
||||
{#if conv.documentMode}
|
||||
<span
|
||||
class="text-xs text-primary bg-primary/10 px-2 py-0.5 rounded-full"
|
||||
>
|
||||
Dokument
|
||||
</span>
|
||||
{/if}
|
||||
<!-- Action Buttons (visible on hover) -->
|
||||
<div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onclick={(e) => handleTogglePin(e, conv.id, false)}
|
||||
class="p-1.5 text-muted-foreground hover:text-primary hover:bg-primary/10 rounded-lg transition-colors"
|
||||
title="Anpinnen"
|
||||
>
|
||||
<PushPin size={14} weight="bold" />
|
||||
</button>
|
||||
<button
|
||||
onclick={(e) => handleArchive(e, conv.id)}
|
||||
class="p-1.5 text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/10 rounded-lg transition-colors"
|
||||
title="Archivieren"
|
||||
>
|
||||
<Archive size={14} weight="bold" />
|
||||
</button>
|
||||
<button
|
||||
onclick={(e) => handleDelete(e, conv.id)}
|
||||
class="p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
|
||||
title="Löschen"
|
||||
>
|
||||
<Trash size={14} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,30 @@
|
|||
|
||||
import { browser } from '$app/environment';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import type {
|
||||
Conversation,
|
||||
Message,
|
||||
Template,
|
||||
Space,
|
||||
SpaceMember,
|
||||
Document,
|
||||
AIModel,
|
||||
ChatMessage,
|
||||
ChatCompletionResponse,
|
||||
} from '@chat/types';
|
||||
|
||||
// Re-export types for convenience
|
||||
export type {
|
||||
Conversation,
|
||||
Message,
|
||||
Template,
|
||||
Space,
|
||||
SpaceMember,
|
||||
Document,
|
||||
AIModel,
|
||||
ChatMessage,
|
||||
ChatCompletionResponse,
|
||||
};
|
||||
|
||||
const API_BASE = env.PUBLIC_BACKEND_URL || 'http://localhost:3002';
|
||||
|
||||
|
|
@ -63,30 +87,6 @@ async function fetchApi<T>(
|
|||
|
||||
// ============ Conversation API ============
|
||||
|
||||
export type Conversation = {
|
||||
id: string;
|
||||
userId: string;
|
||||
modelId: string;
|
||||
templateId?: string;
|
||||
spaceId?: string;
|
||||
title?: string;
|
||||
conversationMode: 'free' | 'guided' | 'template';
|
||||
documentMode: boolean;
|
||||
isArchived: boolean;
|
||||
isPinned: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type Message = {
|
||||
id: string;
|
||||
conversationId: string;
|
||||
sender: 'user' | 'assistant' | 'system';
|
||||
messageText: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export const conversationApi = {
|
||||
async getConversations(spaceId?: string): Promise<Conversation[]> {
|
||||
const query = spaceId ? `?spaceId=${spaceId}` : '';
|
||||
|
|
@ -230,21 +230,6 @@ export const conversationApi = {
|
|||
|
||||
// ============ Template API ============
|
||||
|
||||
export type Template = {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
systemPrompt: string;
|
||||
initialQuestion: string | null;
|
||||
modelId: string | null;
|
||||
color: string;
|
||||
isDefault: boolean;
|
||||
documentMode: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export const templateApi = {
|
||||
async getTemplates(): Promise<Template[]> {
|
||||
const { data, error } = await fetchApi<Template[]>('/templates');
|
||||
|
|
@ -341,29 +326,6 @@ export const templateApi = {
|
|||
|
||||
// ============ Space API ============
|
||||
|
||||
export type Space = {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
isArchived: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type SpaceMember = {
|
||||
id: string;
|
||||
spaceId: string;
|
||||
userId: string;
|
||||
role: 'owner' | 'admin' | 'member' | 'viewer';
|
||||
invitationStatus: 'pending' | 'accepted' | 'declined';
|
||||
invitedBy?: string;
|
||||
invitedAt: string;
|
||||
joinedAt?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export const spaceApi = {
|
||||
async getUserSpaces(): Promise<Space[]> {
|
||||
const { data, error } = await fetchApi<Space[]>('/spaces');
|
||||
|
|
@ -520,15 +482,6 @@ export const spaceApi = {
|
|||
|
||||
// ============ Document API ============
|
||||
|
||||
export type Document = {
|
||||
id: string;
|
||||
conversationId: string;
|
||||
version: number;
|
||||
content: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export const documentApi = {
|
||||
async getLatestDocument(conversationId: string): Promise<Document | null> {
|
||||
const { data, error } = await fetchApi<Document | null>(
|
||||
|
|
@ -604,26 +557,9 @@ export const documentApi = {
|
|||
|
||||
// ============ Model API ============
|
||||
|
||||
export type Model = {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
provider: 'gemini' | 'azure' | 'openai';
|
||||
parameters?: {
|
||||
deployment?: string;
|
||||
temperature?: number;
|
||||
max_tokens?: number;
|
||||
top_p?: number;
|
||||
};
|
||||
isActive: boolean;
|
||||
isDefault: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export const modelApi = {
|
||||
async getModels(): Promise<Model[]> {
|
||||
const { data, error } = await fetchApi<Model[]>('/models');
|
||||
async getModels(): Promise<AIModel[]> {
|
||||
const { data, error } = await fetchApi<AIModel[]>('/models');
|
||||
if (error) {
|
||||
console.error('Error loading models:', error);
|
||||
return [];
|
||||
|
|
@ -631,8 +567,8 @@ export const modelApi = {
|
|||
return data || [];
|
||||
},
|
||||
|
||||
async getModel(id: string): Promise<Model | null> {
|
||||
const { data, error } = await fetchApi<Model>(`/models/${id}`);
|
||||
async getModel(id: string): Promise<AIModel | null> {
|
||||
const { data, error } = await fetchApi<AIModel>(`/models/${id}`);
|
||||
if (error) {
|
||||
console.error('Error loading model:', error);
|
||||
return null;
|
||||
|
|
@ -643,20 +579,6 @@ export const modelApi = {
|
|||
|
||||
// ============ Chat API ============
|
||||
|
||||
export type ChatMessage = {
|
||||
role: 'system' | 'user' | 'assistant';
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type ChatCompletionResponse = {
|
||||
content: string;
|
||||
usage: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
};
|
||||
|
||||
export const chatApi = {
|
||||
async createCompletion(options: {
|
||||
messages: ChatMessage[];
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import {
|
|||
modelApi,
|
||||
type ChatMessage,
|
||||
type ChatCompletionResponse,
|
||||
type Model,
|
||||
type AIModel,
|
||||
} from './api';
|
||||
|
||||
export type { ChatMessage, ChatCompletionResponse };
|
||||
export type { ChatMessage, ChatCompletionResponse, AIModel };
|
||||
|
||||
export interface ChatCompletionRequest {
|
||||
messages: ChatMessage[];
|
||||
|
|
@ -23,7 +23,7 @@ export const chatService = {
|
|||
/**
|
||||
* Get available AI models
|
||||
*/
|
||||
async getModels(): Promise<Model[]> {
|
||||
async getModels(): Promise<AIModel[]> {
|
||||
return modelApi.getModels();
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
/**
|
||||
* Conversation Service - CRUD operations via Backend API
|
||||
*
|
||||
* Note: userId is derived from JWT token on the backend,
|
||||
* so we don't need to pass it from the frontend.
|
||||
*/
|
||||
|
||||
import { conversationApi, chatApi, type Conversation, type Message, type ChatMessage } from './api';
|
||||
|
|
@ -10,36 +13,35 @@ export const conversationService = {
|
|||
/**
|
||||
* Create a new conversation
|
||||
*/
|
||||
async createConversation(
|
||||
userId: string,
|
||||
modelId: string,
|
||||
mode: 'free' | 'guided' | 'template' = 'free',
|
||||
templateId?: string,
|
||||
documentMode: boolean = false,
|
||||
spaceId?: string
|
||||
): Promise<string | null> {
|
||||
async createConversation(options: {
|
||||
modelId: string;
|
||||
mode?: 'free' | 'guided' | 'template';
|
||||
templateId?: string;
|
||||
documentMode?: boolean;
|
||||
spaceId?: string;
|
||||
}): Promise<string | null> {
|
||||
const conversation = await conversationApi.createConversation({
|
||||
modelId,
|
||||
conversationMode: mode,
|
||||
templateId,
|
||||
documentMode,
|
||||
spaceId,
|
||||
modelId: options.modelId,
|
||||
conversationMode: options.mode ?? 'free',
|
||||
templateId: options.templateId,
|
||||
documentMode: options.documentMode ?? false,
|
||||
spaceId: options.spaceId,
|
||||
});
|
||||
|
||||
return conversation?.id || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all active conversations for a user
|
||||
* Get all active conversations
|
||||
*/
|
||||
async getConversations(userId: string, spaceId?: string): Promise<Conversation[]> {
|
||||
async getConversations(spaceId?: string): Promise<Conversation[]> {
|
||||
return conversationApi.getConversations(spaceId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get archived conversations
|
||||
*/
|
||||
async getArchivedConversations(userId: string): Promise<Conversation[]> {
|
||||
async getArchivedConversations(): Promise<Conversation[]> {
|
||||
return conversationApi.getArchivedConversations();
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ export const conversationsStore = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Load conversations for a user
|
||||
* Load conversations (userId is derived from JWT on backend)
|
||||
*/
|
||||
async loadConversations(userId: string, spaceId?: string) {
|
||||
async loadConversations(spaceId?: string) {
|
||||
isLoading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
conversations = await conversationService.getConversations(userId, spaceId);
|
||||
conversations = await conversationService.getConversations(spaceId);
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to load conversations';
|
||||
conversations = [];
|
||||
|
|
@ -46,12 +46,12 @@ export const conversationsStore = {
|
|||
/**
|
||||
* Load archived conversations
|
||||
*/
|
||||
async loadArchivedConversations(userId: string) {
|
||||
async loadArchivedConversations() {
|
||||
isLoading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
archivedConversations = await conversationService.getArchivedConversations(userId);
|
||||
archivedConversations = await conversationService.getArchivedConversations();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to load archived conversations';
|
||||
archivedConversations = [];
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
onMount(async () => {
|
||||
if (authStore.user) {
|
||||
await conversationsStore.loadArchivedConversations(authStore.user.id);
|
||||
await conversationsStore.loadArchivedConversations();
|
||||
conversations = conversationsStore.archivedConversations;
|
||||
}
|
||||
isLoading = false;
|
||||
|
|
|
|||
|
|
@ -72,13 +72,12 @@
|
|||
const docMode = selectedTemplate?.documentMode || documentMode;
|
||||
|
||||
// Create new conversation
|
||||
const conversationId = await conversationService.createConversation(
|
||||
authStore.user.id,
|
||||
modelToUse,
|
||||
mode as 'free' | 'guided' | 'template',
|
||||
selectedTemplate?.id,
|
||||
docMode
|
||||
);
|
||||
const conversationId = await conversationService.createConversation({
|
||||
modelId: modelToUse,
|
||||
mode: mode as 'free' | 'guided' | 'template',
|
||||
templateId: selectedTemplate?.id,
|
||||
documentMode: docMode,
|
||||
});
|
||||
|
||||
if (!conversationId) {
|
||||
throw new Error('Konversation konnte nicht erstellt werden');
|
||||
|
|
@ -92,7 +91,7 @@
|
|||
);
|
||||
|
||||
// Reload conversations list
|
||||
await conversationsStore.loadConversations(authStore.user.id);
|
||||
await conversationsStore.loadConversations();
|
||||
|
||||
// Navigate to the new conversation
|
||||
goto(`/chat/${conversationId}`);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
// Load conversations in this space
|
||||
if (authStore.user) {
|
||||
conversations = await conversationService.getConversations(authStore.user.id, spaceId);
|
||||
conversations = await conversationService.getConversations(spaceId);
|
||||
}
|
||||
|
||||
// Load models
|
||||
|
|
@ -56,14 +56,11 @@
|
|||
async function handleNewChat() {
|
||||
if (!authStore.user || !selectedModelId) return;
|
||||
|
||||
const conversationId = await conversationService.createConversation(
|
||||
authStore.user.id,
|
||||
selectedModelId,
|
||||
'free',
|
||||
undefined,
|
||||
false,
|
||||
spaceId
|
||||
);
|
||||
const conversationId = await conversationService.createConversation({
|
||||
modelId: selectedModelId,
|
||||
mode: 'free',
|
||||
spaceId,
|
||||
});
|
||||
|
||||
if (conversationId) {
|
||||
goto(`/chat/${conversationId}`);
|
||||
|
|
|
|||
|
|
@ -48,16 +48,15 @@
|
|||
if (!template || !authStore.user) return;
|
||||
|
||||
// Create a new conversation with this template
|
||||
const conversationId = await conversationService.createConversation(
|
||||
authStore.user.id,
|
||||
template.modelId || '550e8400-e29b-41d4-a716-446655440101', // Default to Gemini 2.5 Flash
|
||||
'template',
|
||||
template.id,
|
||||
template.documentMode
|
||||
);
|
||||
const conversationId = await conversationService.createConversation({
|
||||
modelId: template.modelId || '550e8400-e29b-41d4-a716-446655440101', // Default to Gemini 2.5 Flash
|
||||
mode: 'template',
|
||||
templateId: template.id,
|
||||
documentMode: template.documentMode,
|
||||
});
|
||||
|
||||
if (conversationId) {
|
||||
await conversationsStore.loadConversations(authStore.user.id);
|
||||
await conversationsStore.loadConversations();
|
||||
goto(`/chat/${conversationId}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue