feat(ux): epic UX design improvements across agents, chat, notes, and i18n

Comprehensive UI/UX updates including agent card redesign, chat container
improvements, note editor enhancements, memory echo notifications, and
updated translations for all 15 locales.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Sepehr Ramezani
2026-04-19 23:01:04 +02:00
parent c2a4c22e5f
commit 402e88b788
208 changed files with 493 additions and 318 deletions

View File

@@ -259,7 +259,7 @@ async function executeScraperAgent(
const r = articleResults[i]
if (r.status === 'fulfilled' && r.value) {
const article = articlesToScrape[i]
const dateStr = article.pubDate ? `${new Date(article.pubDate).toLocaleDateString(dateLocale)}` : ''
const dateStr = article.pubDate ? `${new Date(article.pubDate).toISOString().split('T')[0]}` : ''
scrapedParts.push(`## ${article.title}\n_Source: ${article.link}_${dateStr}\n\n${r.value.substring(0, 3000)}`)
sourceLinks.push(article.link)
articleCount++

View File

@@ -14,12 +14,13 @@ toolRegistry.register({
isInternal: true,
buildTool: (ctx) =>
tool({
description: 'Search the user\'s notes by keyword or semantic meaning. Returns matching notes with titles and content excerpts.',
description: 'Search the user\'s notes by keyword or semantic meaning. Returns matching notes with titles and content excerpts. Optionally restrict to a specific notebook.',
inputSchema: z.object({
query: z.string().describe('The search query'),
limit: z.number().optional().describe('Max results to return (default 5)').default(5),
notebookId: z.string().optional().describe('Optional notebook ID to restrict search to a specific notebook'),
}),
execute: async ({ query, limit = 5 }) => {
execute: async ({ query, limit = 5, notebookId }) => {
try {
// Keyword fallback search using Prisma
const keywords = query.toLowerCase().split(/\s+/).filter(w => w.length > 2)
@@ -31,6 +32,7 @@ toolRegistry.register({
const notes = await prisma.note.findMany({
where: {
userId: ctx.userId,
...(notebookId ? { notebookId } : {}),
...(conditions.length > 0 ? { OR: conditions } : {}),
isArchived: false,
trashedAt: null,

View File

@@ -9,8 +9,11 @@ import { z } from 'zod'
export interface ToolContext {
userId: string
agentId: string
actionId: string
agentId?: string
actionId?: string
conversationId?: string
notebookId?: string
webSearch?: boolean
config: Record<string, string>
}
@@ -43,6 +46,29 @@ class ToolRegistry {
return built
}
/**
* Build tools for the chat endpoint.
* Includes internal tools (note_search, note_read) and web tools when configured.
*/
buildToolsForChat(ctx: ToolContext): Record<string, any> {
const toolNames: string[] = ['note_search', 'note_read']
// Add web tools only when user toggled web search AND config is present
if (ctx.webSearch) {
const hasWebSearch = ctx.config.WEB_SEARCH_PROVIDER || ctx.config.BRAVE_SEARCH_API_KEY || ctx.config.SEARXNG_URL
if (hasWebSearch) {
toolNames.push('web_search')
}
const hasWebScrape = ctx.config.JINA_API_KEY
if (hasWebScrape) {
toolNames.push('web_scrape')
}
}
return this.buildToolsForAgent(toolNames, ctx)
}
getAvailableTools(): Array<{ name: string; description: string; isInternal: boolean }> {
return Array.from(this.tools.values()).map(t => ({
name: t.name,

View File

@@ -75,6 +75,7 @@ export interface Note {
notebookId?: string | null;
notebook?: Notebook | null;
autoGenerated?: boolean | null;
aiProvider?: string | null;
// Search result metadata (optional)
matchType?: 'exact' | 'related' | null;
searchScore?: number | null;

View File

@@ -77,13 +77,14 @@ export function getHashColor(name: string): LabelColorName {
}
export function cosineSimilarity(vecA: number[], vecB: number[]): number {
if (vecA.length !== vecB.length) return 0;
if (!vecA.length || !vecB.length) return 0;
const minLen = Math.min(vecA.length, vecB.length);
let dotProduct = 0;
let mA = 0;
let mB = 0;
for (let i = 0; i < vecA.length; i++) {
for (let i = 0; i < minLen; i++) {
dotProduct += vecA[i] * vecB[i];
mA += vecA[i] * vecA[i];
mB += vecB[i] * vecB[i];