Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/@types/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1963,6 +1963,73 @@
"note": "Note"
},
"menuName": "Document",
"aiMeeting": {
"titleDefault": "Meeting",
"tab": {
"summary": "Summary",
"notes": "Notes",
"transcript": "Transcript"
},
"readOnlyHint": "This content is read-only on web. Please edit it on the desktop app.",
"notesPlaceholder": "Add your notes… AI will turn them into a clean, share-ready summary.",
"speakerUnknown": "Unknown speaker",
"speakerFallback": "Speaker {{id}}",
"reference": {
"sourcesTooltip": "View sources",
"deletedTooltip": "Source deleted",
"deleted": "Source was deleted"
},
"copy": {
"summary": "Copy summary",
"notes": "Copy notes",
"transcript": "Copy transcript",
"summarySuccess": "Summary copied to clipboard",
"notesSuccess": "Notes copied to clipboard",
"transcriptSuccess": "Transcript copied to clipboard",
"noContent": "No content to copy"
},
"regenerate": {
"regenerate": "Regenerate",
"generating": "Generating",
"summaryTemplate": "Summary template",
"summaryDetail": "Summary detail",
"summaryLanguage": "Summary language",
"noSource": "No transcript or notes available to regenerate summary",
"success": "Summary regenerated",
"failed": "Failed to regenerate summary",
"template": {
"auto": "Auto",
"meetingMinutes": "Meeting minutes",
"actionFocused": "Action focused",
"executive": "Executive"
},
"detail": {
"concise": "Concise",
"balanced": "Balanced",
"detailed": "Detailed"
},
"language": {
"english": "English",
"chineseSimplified": "Chinese (Simplified)",
"chineseTraditional": "Chinese (Traditional)",
"spanish": "Spanish",
"french": "French",
"german": "German",
"japanese": "Japanese",
"korean": "Korean",
"portuguese": "Portuguese",
"russian": "Russian",
"thai": "Thai",
"vietnamese": "Vietnamese",
"danish": "Danish",
"finnish": "Finnish",
"norwegian": "Norwegian",
"dutch": "Dutch",
"italian": "Italian",
"swedish": "Swedish"
}
}
},
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
Expand Down
66 changes: 66 additions & 0 deletions src/@types/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,72 @@
},
"document": {
"menuName": "文档",
"aiMeeting": {
"titleDefault": "会议",
"tab": {
"summary": "摘要",
"notes": "笔记",
"transcript": "转录"
},
"readOnlyHint": "该内容在 Web 上为只读,请在桌面端编辑。",
"speakerUnknown": "未知发言者",
"speakerFallback": "发言者 {{id}}",
"reference": {
"sourcesTooltip": "查看来源",
"deletedTooltip": "来源已删除",
"deleted": "来源已删除"
},
"copy": {
"summary": "复制摘要",
"notes": "复制笔记",
"transcript": "复制转录",
"summarySuccess": "摘要已复制到剪贴板",
"notesSuccess": "笔记已复制到剪贴板",
"transcriptSuccess": "转录已复制到剪贴板",
"noContent": "没有可复制的内容"
},
"regenerate": {
"regenerate": "重新生成",
"generating": "生成中",
"summaryTemplate": "摘要模板",
"summaryDetail": "摘要详略",
"summaryLanguage": "摘要语言",
"noSource": "没有可用于重新生成摘要的转录或笔记内容",
"success": "摘要已重新生成",
"failed": "重新生成摘要失败",
"template": {
"auto": "自动",
"meetingMinutes": "会议纪要",
"actionFocused": "行动导向",
"executive": "管理层摘要"
},
"detail": {
"concise": "简洁",
"balanced": "均衡",
"detailed": "详细"
},
"language": {
"english": "英语",
"chineseSimplified": "简体中文",
"chineseTraditional": "繁體中文",
"spanish": "西班牙语",
"french": "法语",
"german": "德语",
"japanese": "日语",
"korean": "韩语",
"portuguese": "葡萄牙语",
"russian": "俄语",
"thai": "泰语",
"vietnamese": "越南语",
"danish": "丹麦语",
"finnish": "芬兰语",
"norwegian": "挪威语",
"dutch": "荷兰语",
"italian": "意大利语",
"swedish": "瑞典语"
}
}
},
"date": {
"timeHintTextInTwelveHour": "下午 01:00",
"timeHintTextInTwentyFourHour": "13:00"
Expand Down
6 changes: 5 additions & 1 deletion src/application/slate-yjs/command/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export const CONTAINER_BLOCK_TYPES = [
BlockType.SimpleTableBlock,
BlockType.SimpleTableRowBlock,
BlockType.SimpleTableCellBlock,
BlockType.AIMeetingSummaryBlock,
BlockType.AIMeetingNotesBlock,
BlockType.AIMeetingTranscriptionBlock,
BlockType.AIMeetingSpeakerBlock,
];
export const SOFT_BREAK_TYPES = [BlockType.CodeBlock];

Expand All @@ -36,4 +40,4 @@ export const TEXT_BLOCK_TYPES = [

export const isEmbedBlockTypes = (type: BlockType) => {
return ![...LIST_BLOCK_TYPES, ...CONTAINER_BLOCK_TYPES, ...SOFT_BREAK_TYPES, BlockType.HeadingBlock].includes(type);
};
};
7 changes: 6 additions & 1 deletion src/application/slate-yjs/utils/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ export function traverseBlock(id: string, sharedRoot: YSharedRoot): Element | un
if (
slateNode.type === BlockType.SimpleTableBlock ||
slateNode.type === BlockType.SimpleTableRowBlock ||
slateNode.type === BlockType.SimpleTableCellBlock
slateNode.type === BlockType.SimpleTableCellBlock ||
slateNode.type === BlockType.AIMeetingBlock ||
slateNode.type === BlockType.AIMeetingSummaryBlock ||
slateNode.type === BlockType.AIMeetingNotesBlock ||
slateNode.type === BlockType.AIMeetingTranscriptionBlock ||
slateNode.type === BlockType.AIMeetingSpeakerBlock
) {
textId = '';
}
Expand Down
25 changes: 25 additions & 0 deletions src/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export enum BlockType {
ColumnsBlock = 'simple_columns',
ColumnBlock = 'simple_column',
AIMeetingBlock = 'ai_meeting',
AIMeetingSummaryBlock = 'ai_meeting_summary',
AIMeetingNotesBlock = 'ai_meeting_notes',
AIMeetingTranscriptionBlock = 'ai_meeting_transcription',
AIMeetingSpeakerBlock = 'ai_meeting_speaker',
PDFBlock = 'pdf',
}

Expand Down Expand Up @@ -152,6 +156,27 @@ export interface VideoBlockData extends BlockData {

export interface AIMeetingBlockData extends BlockData {
title?: string;
date?: string | number;
audio_file_path?: string;
recording_state?: string;
summary_template?: string;
summary_detail?: string;
summary_language?: string;
transcript_id?: string;
transcription_type?: string;
created_at?: string | number;
last_modified?: string | number;
selected_tab_index?: number | string;
pending_billing_duration?: number;
show_notes_directly?: boolean;
auto_start_recording?: boolean;
speaker_info_map?: string | Record<string, Record<string, unknown>>;
}

export interface AIMeetingSpeakerBlockData extends BlockData {
speaker_id?: string;
timestamp?: number;
end_timestamp?: number;
}

export interface PDFBlockData extends BlockData {
Expand Down
6 changes: 6 additions & 0 deletions src/assets/icons/ai_meeting_transcript_tab.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/ai_notes.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/ai_reference_warning.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/ai_summary_ref_notes.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/assets/icons/ai_summary_ref_transcript.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icons/ai_summary_tab.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icons/ai_template_apply.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions src/components/chat/request/__tests__/stream-json-parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { extractNextJsonObject } from '../stream-json-parser';

describe('extractNextJsonObject', () => {
it('extracts a simple json object', () => {
const result = extractNextJsonObject('prefix {"a":"b"} suffix');

expect(result).toEqual({
jsonStr: '{"a":"b"}',
nextIndex: 'prefix {"a":"b"}'.length,
});
});

it('handles braces inside string values', () => {
const streamChunk = '{"delta":"hello {ref:block_1}"}{"done":true}';
const first = extractNextJsonObject(streamChunk);

expect(first?.jsonStr).toBe('{"delta":"hello {ref:block_1}"}');

const second = extractNextJsonObject(streamChunk.slice(first?.nextIndex ?? 0));

expect(second?.jsonStr).toBe('{"done":true}');
});

it('handles escaped quotes in strings', () => {
const result = extractNextJsonObject('{"delta":"say \\"{hello}\\" now"}');

expect(result?.jsonStr).toBe('{"delta":"say \\"{hello}\\" now"}');
});

it('returns null for incomplete object', () => {
expect(extractNextJsonObject('{"a":"b"')).toBeNull();
});
});
25 changes: 5 additions & 20 deletions src/components/chat/request/chat-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
ChatMessageMetadata, StreamType,
} from '@/components/chat/types';
import { ModelList } from '@/components/chat/types/ai-model';
import { extractNextJsonObject } from './stream-json-parser';

export type UpdateChatSettingsParams = Partial<ChatSettings>;

Expand Down Expand Up @@ -293,28 +294,12 @@ export class ChatRequest {
buffer += decoder.decode(chunk, { stream: true });

while(buffer.length > 0) {
const openBraceIndex = buffer.indexOf('{');
const extracted = extractNextJsonObject(buffer);

if(openBraceIndex === -1) break;

let closeBraceIndex = -1;
let depth = 0;

for(let i = openBraceIndex; i < buffer.length; i++) {
if(buffer[i] === '{') depth++;
if(buffer[i] === '}') depth--;
if(depth === 0) {
closeBraceIndex = i;
break;
}
}

if(closeBraceIndex === -1) break;

const jsonStr = buffer.slice(openBraceIndex, closeBraceIndex + 1);
if(!extracted) break;

try {
const data = JSON.parse(jsonStr);
const data = JSON.parse(extracted.jsonStr);

Object.entries(data).forEach(([key, value]) => {
if (key === StreamType.META_DATA) {
Expand All @@ -341,7 +326,7 @@ export class ChatRequest {
console.error('Failed to parse JSON:', e);
}

buffer = buffer.slice(closeBraceIndex + 1);
buffer = buffer.slice(extracted.nextIndex);
}
}

Expand Down
Loading
Loading