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:
@@ -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++
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user