Merge branch 'dev'
This commit is contained in:
commit
7d71d202f9
|
|
@ -41,6 +41,33 @@ const TOOL_HINTS: ToolHint[] = [
|
|||
{ tagName: "folk-choice-rank", label: "Ranking", icon: "📊", keywords: ["rank", "order", "priority", "sort"] },
|
||||
{ tagName: "folk-choice-spider", label: "Spider Chart", icon: "🕸️", keywords: ["spider", "radar", "criteria", "evaluate"] },
|
||||
{ tagName: "folk-spider-3d", label: "3D Spider", icon: "📊", keywords: ["spider", "radar", "3d", "overlap", "membrane", "governance", "permeability"] },
|
||||
{ tagName: "folk-choice-conviction", label: "Conviction Vote", icon: "🔥", keywords: ["conviction", "stake", "weight", "governance", "token vote"] },
|
||||
// Travel
|
||||
{ tagName: "folk-itinerary", label: "Itinerary", icon: "🗓️", keywords: ["itinerary", "trip", "travel", "plan", "schedule"] },
|
||||
{ tagName: "folk-destination", label: "Destination", icon: "📍", keywords: ["destination", "city", "place", "travel", "visit"] },
|
||||
{ tagName: "folk-booking", label: "Booking", icon: "🎫", keywords: ["booking", "reservation", "flight", "hotel", "transport"] },
|
||||
{ tagName: "folk-budget", label: "Budget", icon: "💰", keywords: ["budget", "expense", "cost", "money", "spending"] },
|
||||
{ tagName: "folk-packing-list", label: "Packing List", icon: "🎒", keywords: ["packing", "list", "luggage", "gear", "pack"] },
|
||||
// Tokens
|
||||
{ tagName: "folk-token-mint", label: "Token Mint", icon: "🪙", keywords: ["token", "mint", "create token", "currency", "coin"] },
|
||||
{ tagName: "folk-token-ledger", label: "Token Ledger", icon: "📒", keywords: ["ledger", "balance", "token", "transactions", "holdings"] },
|
||||
{ tagName: "folk-transaction-builder", label: "Transaction", icon: "💸", keywords: ["transaction", "transfer", "send", "multisig", "safe"] },
|
||||
// Creative / CAD
|
||||
{ tagName: "folk-blender", label: "3D Scene", icon: "🎲", keywords: ["blender", "3d", "render", "scene", "model"] },
|
||||
{ tagName: "folk-freecad", label: "CAD Part", icon: "🔧", keywords: ["cad", "freecad", "part", "mechanical", "parametric"] },
|
||||
{ tagName: "folk-kicad", label: "PCB Design", icon: "🔌", keywords: ["pcb", "kicad", "circuit", "schematic", "electronics"] },
|
||||
{ tagName: "folk-design-agent", label: "Print Design", icon: "🖨️", keywords: ["design", "poster", "flyer", "brochure", "print", "layout", "scribus"] },
|
||||
// Zine
|
||||
{ tagName: "folk-zine-gen", label: "Zine", icon: "📰", keywords: ["zine", "magazine", "publication", "pamphlet", "print"] },
|
||||
// Geo
|
||||
{ tagName: "folk-holon", label: "Holon", icon: "🌐", keywords: ["holon", "h3", "hexagon", "geospatial", "region"] },
|
||||
{ tagName: "folk-holon-browser", label: "Holon Browser", icon: "🌍", keywords: ["holon", "browse", "explore", "territory", "map"] },
|
||||
// Meta
|
||||
{ tagName: "folk-canvas", label: "Nested Canvas", icon: "🔲", keywords: ["canvas", "nested", "subspace", "embed canvas"] },
|
||||
{ tagName: "folk-wrapper", label: "Wrapper", icon: "📦", keywords: ["wrapper", "container", "group", "frame"] },
|
||||
{ tagName: "folk-image", label: "Image", icon: "🖼️", keywords: ["image", "photo", "picture", "png", "jpg"] },
|
||||
{ tagName: "folk-bookmark", label: "Bookmark", icon: "🔖", keywords: ["bookmark", "link", "save", "reference"] },
|
||||
{ tagName: "folk-obs-note", label: "Obsidian Note", icon: "📓", keywords: ["obsidian", "note", "vault", "knowledge"] },
|
||||
// Module content hints (these create content in rApps, not canvas shapes)
|
||||
{ tagName: "rcal-event", label: "Calendar Event", icon: "📅", keywords: ["event", "meeting", "schedule", "standup", "appointment"], moduleAction: { module: "rcal", contentType: "event" } },
|
||||
{ tagName: "rtasks-task", label: "Task", icon: "✅", keywords: ["task", "todo", "assign", "deadline", "backlog"], moduleAction: { module: "rtasks", contentType: "task" } },
|
||||
|
|
|
|||
|
|
@ -10,19 +10,62 @@ import type { TriageManager } from "./mi-content-triage";
|
|||
|
||||
/** Icon lookup by tagName — matches TOOL_HINTS from mi-tool-schema.ts */
|
||||
const SHAPE_ICONS: Record<string, { icon: string; label: string }> = {
|
||||
// Core
|
||||
"folk-markdown": { icon: "📝", label: "Note" },
|
||||
"folk-wrapper": { icon: "📦", label: "Wrapper" },
|
||||
"folk-embed": { icon: "🔗", label: "Embed" },
|
||||
"folk-calendar": { icon: "📅", label: "Calendar" },
|
||||
"folk-map": { icon: "🗺️", label: "Map" },
|
||||
"folk-image": { icon: "🖼️", label: "Image" },
|
||||
"folk-bookmark": { icon: "🔖", label: "Bookmark" },
|
||||
"folk-slide": { icon: "🖼️", label: "Slide" },
|
||||
"folk-chat": { icon: "💬", label: "Chat" },
|
||||
"folk-piano": { icon: "🎹", label: "Piano" },
|
||||
"folk-canvas": { icon: "🔲", label: "Canvas" },
|
||||
"folk-rapp": { icon: "📦", label: "rApp" },
|
||||
"folk-feed": { icon: "📡", label: "Feed" },
|
||||
"folk-obs-note": { icon: "📓", label: "Obsidian" },
|
||||
"folk-workflow-block": { icon: "⚙️", label: "Workflow" },
|
||||
"folk-google-item": { icon: "🔍", label: "Google" },
|
||||
// AI
|
||||
"folk-prompt": { icon: "🤖", label: "AI Chat" },
|
||||
"folk-image-gen": { icon: "🎨", label: "AI Image" },
|
||||
"folk-image-studio": { icon: "🖌️", label: "Studio" },
|
||||
"folk-video-gen": { icon: "🎬", label: "AI Video" },
|
||||
"folk-zine-gen": { icon: "📰", label: "Zine" },
|
||||
"folk-transcription": { icon: "🎙️", label: "Transcribe" },
|
||||
// Creative
|
||||
"folk-splat": { icon: "💎", label: "3D Splat" },
|
||||
"folk-drawfast": { icon: "✏️", label: "Drawing" },
|
||||
"folk-blender": { icon: "🎲", label: "3D Scene" },
|
||||
"folk-freecad": { icon: "🔧", label: "CAD" },
|
||||
"folk-kicad": { icon: "🔌", label: "PCB" },
|
||||
"folk-design-agent": { icon: "🖨️", label: "Design" },
|
||||
// Social
|
||||
"folk-social-post": { icon: "📣", label: "Social Post" },
|
||||
"folk-social-thread": { icon: "🧵", label: "Thread" },
|
||||
"folk-social-campaign": { icon: "📢", label: "Campaign" },
|
||||
"folk-social-newsletter": { icon: "📧", label: "Newsletter" },
|
||||
// Decisions
|
||||
"folk-choice-vote": { icon: "🗳️", label: "Vote" },
|
||||
"folk-prompt": { icon: "🤖", label: "AI Chat" },
|
||||
"folk-image-gen": { icon: "🎨", label: "AI Image" },
|
||||
"folk-slide": { icon: "🖼️", label: "Slide" },
|
||||
"folk-choice-rank": { icon: "📊", label: "Ranking" },
|
||||
"folk-choice-spider": { icon: "🕸️", label: "Spider" },
|
||||
"folk-choice-conviction": { icon: "🔥", label: "Conviction" },
|
||||
"folk-spider-3d": { icon: "📊", label: "3D Spider" },
|
||||
// Travel
|
||||
"folk-itinerary": { icon: "🗓️", label: "Itinerary" },
|
||||
"folk-destination": { icon: "📍", label: "Destination" },
|
||||
"folk-booking": { icon: "🎫", label: "Booking" },
|
||||
"folk-budget": { icon: "💰", label: "Budget" },
|
||||
"folk-packing-list": { icon: "🎒", label: "Packing" },
|
||||
// Tokens
|
||||
"folk-token-mint": { icon: "🪙", label: "Token Mint" },
|
||||
"folk-token-ledger": { icon: "📒", label: "Ledger" },
|
||||
"folk-transaction-builder": { icon: "💸", label: "Transaction" },
|
||||
// Geo
|
||||
"folk-calendar": { icon: "📅", label: "Calendar" },
|
||||
"folk-map": { icon: "🗺️", label: "Map" },
|
||||
"folk-holon": { icon: "🌐", label: "Holon" },
|
||||
"folk-holon-browser": { icon: "🌍", label: "Holons" },
|
||||
"folk-video-chat": { icon: "📹", label: "Video Call" },
|
||||
};
|
||||
|
||||
export class MiTriagePanel {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ class FolkNotesApp extends HTMLElement {
|
|||
private sidebarOpen = true;
|
||||
private mobileEditing = false;
|
||||
private _resizeHandler: (() => void) | null = null;
|
||||
private _suggestionSyncTimer: any = null;
|
||||
|
||||
// Zone-based rendering
|
||||
private navZone!: HTMLDivElement;
|
||||
|
|
@ -884,6 +885,92 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
}
|
||||
}
|
||||
|
||||
/** Move a note from one notebook to another via Automerge docs. */
|
||||
private async moveNoteToNotebook(noteId: string, sourceNotebookId: string, targetNotebookId: string) {
|
||||
if (sourceNotebookId === targetNotebookId) return;
|
||||
const runtime = (window as any).__rspaceOfflineRuntime;
|
||||
if (!runtime?.isInitialized) return;
|
||||
|
||||
const dataSpace = runtime.resolveDocSpace?.('rnotes') || this.space;
|
||||
const sourceDocId = `${dataSpace}:notes:notebooks:${sourceNotebookId}` as DocumentId;
|
||||
const targetDocId = `${dataSpace}:notes:notebooks:${targetNotebookId}` as DocumentId;
|
||||
|
||||
// Get the note data from source
|
||||
const sourceDoc = runtime.get(sourceDocId) as NotebookDoc | undefined;
|
||||
if (!sourceDoc?.items?.[noteId]) return;
|
||||
|
||||
// Deep-clone the note item (plain object from Automerge)
|
||||
const noteItem = JSON.parse(JSON.stringify(sourceDoc.items[noteId]));
|
||||
noteItem.notebookId = targetNotebookId;
|
||||
noteItem.updatedAt = Date.now();
|
||||
|
||||
// Subscribe to target doc if needed, add the note, then unsubscribe
|
||||
let targetDoc: NotebookDoc | undefined;
|
||||
try {
|
||||
targetDoc = await runtime.subscribe(targetDocId, notebookSchema);
|
||||
} catch {
|
||||
return; // target notebook not accessible
|
||||
}
|
||||
|
||||
// Add to target
|
||||
runtime.change(targetDocId, `Move note ${noteId}`, (d: NotebookDoc) => {
|
||||
if (!d.items) (d as any).items = {};
|
||||
d.items[noteId] = noteItem;
|
||||
});
|
||||
|
||||
// Remove from source
|
||||
runtime.change(sourceDocId, `Move note ${noteId} out`, (d: NotebookDoc) => {
|
||||
delete d.items[noteId];
|
||||
});
|
||||
|
||||
// If we're viewing the source notebook, refresh
|
||||
if (this.subscribedDocId === sourceDocId) {
|
||||
this.doc = runtime.get(sourceDocId);
|
||||
this.renderFromDoc();
|
||||
}
|
||||
|
||||
// Update sidebar counts
|
||||
const srcNb = this.notebooks.find(n => n.id === sourceNotebookId);
|
||||
const tgtNb = this.notebooks.find(n => n.id === targetNotebookId);
|
||||
if (srcNb) srcNb.note_count = String(Math.max(0, parseInt(srcNb.note_count) - 1));
|
||||
if (tgtNb) tgtNb.note_count = String(parseInt(tgtNb.note_count) + 1);
|
||||
|
||||
// Refresh sidebar note cache for source
|
||||
const srcNotes = this.notebookNotes.get(sourceNotebookId);
|
||||
if (srcNotes) this.notebookNotes.set(sourceNotebookId, srcNotes.filter(n => n.id !== noteId));
|
||||
|
||||
// If target is expanded, refresh its notes
|
||||
if (this.expandedNotebooks.has(targetNotebookId)) {
|
||||
const tgtDoc = runtime.get(targetDocId) as NotebookDoc | undefined;
|
||||
if (tgtDoc?.items) {
|
||||
const notes: Note[] = Object.values(tgtDoc.items).map((item: any) => ({
|
||||
id: item.id, title: item.title || 'Untitled', content: item.content || '',
|
||||
content_plain: item.contentPlain || '', type: item.type || 'NOTE',
|
||||
tags: item.tags?.length ? Array.from(item.tags) : null,
|
||||
is_pinned: item.isPinned || false, url: item.url || null,
|
||||
language: item.language || null, fileUrl: item.fileUrl || null,
|
||||
mimeType: item.mimeType || null, duration: item.duration ?? null,
|
||||
created_at: item.createdAt ? new Date(item.createdAt).toISOString() : new Date().toISOString(),
|
||||
updated_at: item.updatedAt ? new Date(item.updatedAt).toISOString() : new Date().toISOString(),
|
||||
}));
|
||||
this.notebookNotes.set(targetNotebookId, notes);
|
||||
}
|
||||
}
|
||||
|
||||
// Unsubscribe from target if it's not the active notebook
|
||||
if (this.subscribedDocId !== targetDocId) {
|
||||
runtime.unsubscribe(targetDocId);
|
||||
}
|
||||
|
||||
// Close editor if we were editing the moved note
|
||||
if (this.selectedNote?.id === noteId) {
|
||||
this.selectedNote = null;
|
||||
this.renderContent();
|
||||
}
|
||||
|
||||
this.renderNav();
|
||||
}
|
||||
|
||||
// ── Note summarization ──
|
||||
|
||||
private async summarizeNote(btn: HTMLElement) {
|
||||
|
|
@ -2418,7 +2505,7 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
if (this.editor) {
|
||||
acceptSuggestion(this.editor, suggestionId);
|
||||
this.updateSuggestionReviewBar();
|
||||
this.syncSuggestionsToPanel();
|
||||
this.syncSuggestionsToPanel(true);
|
||||
}
|
||||
pop.remove();
|
||||
});
|
||||
|
|
@ -2426,7 +2513,7 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
if (this.editor) {
|
||||
rejectSuggestion(this.editor, suggestionId);
|
||||
this.updateSuggestionReviewBar();
|
||||
this.syncSuggestionsToPanel();
|
||||
this.syncSuggestionsToPanel(true);
|
||||
}
|
||||
pop.remove();
|
||||
});
|
||||
|
|
@ -2471,16 +2558,23 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
/** Push current suggestions to the comment panel and ensure sidebar is visible. */
|
||||
private syncSuggestionsToPanel() {
|
||||
const panel = this.shadow.querySelector('notes-comment-panel') as any;
|
||||
if (!panel) return;
|
||||
const suggestions = this.collectSuggestions();
|
||||
panel.suggestions = suggestions;
|
||||
// Show sidebar if there are suggestions or comments
|
||||
const sidebar = this.shadow.getElementById('comment-sidebar');
|
||||
if (sidebar && suggestions.length > 0) {
|
||||
sidebar.classList.add('has-comments');
|
||||
/** Push current suggestions to the comment panel (debounced to avoid letter-by-letter flicker). */
|
||||
private syncSuggestionsToPanel(immediate = false) {
|
||||
clearTimeout(this._suggestionSyncTimer);
|
||||
const flush = () => {
|
||||
const panel = this.shadow.querySelector('notes-comment-panel') as any;
|
||||
if (!panel) return;
|
||||
const suggestions = this.collectSuggestions();
|
||||
panel.suggestions = suggestions;
|
||||
const sidebar = this.shadow.getElementById('comment-sidebar');
|
||||
if (sidebar && suggestions.length > 0) {
|
||||
sidebar.classList.add('has-comments');
|
||||
}
|
||||
};
|
||||
if (immediate) {
|
||||
flush();
|
||||
} else {
|
||||
this._suggestionSyncTimer = setTimeout(flush, 400);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2503,14 +2597,14 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
if (this.editor && e.detail?.suggestionId) {
|
||||
acceptSuggestion(this.editor, e.detail.suggestionId);
|
||||
this.updateSuggestionReviewBar();
|
||||
this.syncSuggestionsToPanel();
|
||||
this.syncSuggestionsToPanel(true);
|
||||
}
|
||||
});
|
||||
panel.addEventListener('suggestion-reject', (e: CustomEvent) => {
|
||||
if (this.editor && e.detail?.suggestionId) {
|
||||
rejectSuggestion(this.editor, e.detail.suggestionId);
|
||||
this.updateSuggestionReviewBar();
|
||||
this.syncSuggestionsToPanel();
|
||||
this.syncSuggestionsToPanel(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -2554,10 +2648,10 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
}
|
||||
});
|
||||
|
||||
// On any change, update the suggestion review bar + sidebar panel
|
||||
// On any change, update the suggestion review bar + sidebar panel (debounced)
|
||||
this.editor.on('update', () => {
|
||||
this.updateSuggestionReviewBar();
|
||||
this.syncSuggestionsToPanel();
|
||||
this.syncSuggestionsToPanel(); // debounced — avoids letter-by-letter flicker
|
||||
});
|
||||
|
||||
// Direct click on comment highlight or suggestion marks in the DOM
|
||||
|
|
@ -3070,12 +3164,47 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
});
|
||||
});
|
||||
|
||||
// Make sidebar notes draggable
|
||||
// Make sidebar notes draggable (cross-rApp + intra-sidebar)
|
||||
makeDraggableAll(this.shadow, ".sbt-note[data-note]", (el) => {
|
||||
const title = el.querySelector(".sbt-note-title")?.textContent || "";
|
||||
const id = el.dataset.note || "";
|
||||
return title ? { title, module: "rnotes", entityId: id, label: "Note", color: "#f59e0b" } : null;
|
||||
});
|
||||
|
||||
// Also set native drag data for intra-sidebar notebook moves
|
||||
this.shadow.querySelectorAll(".sbt-note[data-note]").forEach(el => {
|
||||
(el as HTMLElement).addEventListener("dragstart", (e) => {
|
||||
const noteId = (el as HTMLElement).dataset.note!;
|
||||
const nbId = (el as HTMLElement).dataset.notebook!;
|
||||
e.dataTransfer?.setData("application/x-rnotes-move", JSON.stringify({ noteId, sourceNotebookId: nbId }));
|
||||
});
|
||||
});
|
||||
|
||||
// Notebook headers accept dropped notes
|
||||
this.shadow.querySelectorAll(".sbt-notebook-header[data-toggle-notebook]").forEach(el => {
|
||||
(el as HTMLElement).addEventListener("dragover", (e) => {
|
||||
if (e.dataTransfer?.types.includes("application/x-rnotes-move")) {
|
||||
e.preventDefault();
|
||||
(el as HTMLElement).classList.add("drop-target");
|
||||
}
|
||||
});
|
||||
(el as HTMLElement).addEventListener("dragleave", () => {
|
||||
(el as HTMLElement).classList.remove("drop-target");
|
||||
});
|
||||
(el as HTMLElement).addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
(el as HTMLElement).classList.remove("drop-target");
|
||||
const raw = e.dataTransfer?.getData("application/x-rnotes-move");
|
||||
if (!raw) return;
|
||||
try {
|
||||
const { noteId, sourceNotebookId } = JSON.parse(raw);
|
||||
const targetNotebookId = (el as HTMLElement).dataset.toggleNotebook!;
|
||||
if (noteId && sourceNotebookId && targetNotebookId) {
|
||||
this.moveNoteToNotebook(noteId, sourceNotebookId, targetNotebookId);
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private demoUpdateNoteField(noteId: string, field: string, value: string) {
|
||||
|
|
@ -3250,6 +3379,7 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
transition: background 0.1s; font-size: 13px;
|
||||
}
|
||||
.sbt-notebook-header:hover { background: var(--rs-bg-hover); }
|
||||
.sbt-notebook-header.drop-target { background: rgba(99, 102, 241, 0.15); border: 1px dashed var(--rs-primary, #6366f1); border-radius: 4px; }
|
||||
.sbt-toggle {
|
||||
width: 16px; text-align: center; font-size: 10px;
|
||||
color: var(--rs-text-muted); flex-shrink: 0;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,30 @@ function makeSuggestionId(): string {
|
|||
return `s_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
// ── Typing session tracker ──
|
||||
// Reuses the same suggestionId while the user types consecutively,
|
||||
// so an entire typed word/phrase becomes ONE suggestion in the sidebar.
|
||||
let _sessionSuggestionId: string | null = null;
|
||||
let _sessionNextPos: number = -1; // the position where the next char is expected
|
||||
|
||||
function getOrCreateSessionId(insertPos: number): string {
|
||||
if (_sessionSuggestionId && insertPos === _sessionNextPos) {
|
||||
return _sessionSuggestionId;
|
||||
}
|
||||
_sessionSuggestionId = makeSuggestionId();
|
||||
return _sessionSuggestionId;
|
||||
}
|
||||
|
||||
function advanceSession(id: string, nextPos: number): void {
|
||||
_sessionSuggestionId = id;
|
||||
_sessionNextPos = nextPos;
|
||||
}
|
||||
|
||||
function resetSession(): void {
|
||||
_sessionSuggestionId = null;
|
||||
_sessionNextPos = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the suggestion mode ProseMirror plugin.
|
||||
* @param getSuggesting - callback that returns current suggesting mode state
|
||||
|
|
@ -42,7 +66,10 @@ export function createSuggestionPlugin(
|
|||
|
||||
const { state } = view;
|
||||
const { authorId, authorName } = getAuthor();
|
||||
const suggestionId = makeSuggestionId();
|
||||
// Reuse session ID for consecutive typing at the same position
|
||||
const suggestionId = (from !== to)
|
||||
? makeSuggestionId() // replacement → new suggestion
|
||||
: getOrCreateSessionId(from); // plain insert → batch with session
|
||||
const tr = state.tr;
|
||||
|
||||
// If there's a selection (replacement), mark the selected text as deleted
|
||||
|
|
@ -76,9 +103,11 @@ export function createSuggestionPlugin(
|
|||
tr.setMeta('suggestion-applied', true);
|
||||
|
||||
// Place cursor after the inserted text
|
||||
tr.setSelection(TextSelection.create(tr.doc, insertPos + text.length));
|
||||
const newCursorPos = insertPos + text.length;
|
||||
tr.setSelection(TextSelection.create(tr.doc, newCursorPos));
|
||||
|
||||
view.dispatch(tr);
|
||||
advanceSession(suggestionId, newCursorPos);
|
||||
return true;
|
||||
},
|
||||
|
||||
|
|
@ -86,6 +115,7 @@ export function createSuggestionPlugin(
|
|||
handleKeyDown(view: EditorView, event: KeyboardEvent): boolean {
|
||||
if (!getSuggesting()) return false;
|
||||
if (event.key !== 'Backspace' && event.key !== 'Delete') return false;
|
||||
resetSession(); // break typing session on delete actions
|
||||
|
||||
const { state } = view;
|
||||
const { from, to, empty } = state.selection;
|
||||
|
|
@ -152,6 +182,7 @@ export function createSuggestionPlugin(
|
|||
/** Intercept paste — insert pasted text as a suggestion. */
|
||||
handlePaste(view: EditorView, _event: ClipboardEvent, slice: Slice): boolean {
|
||||
if (!getSuggesting()) return false;
|
||||
resetSession(); // paste is a discrete action, break typing session
|
||||
|
||||
const { state } = view;
|
||||
const { from, to } = state.selection;
|
||||
|
|
|
|||
|
|
@ -233,10 +233,17 @@ include action markers in your response. Each marker is on its own line:
|
|||
[MI_ACTION:{"type":"navigate","path":"/myspace/rspace"}]
|
||||
|
||||
Use "$1", "$2", etc. as ref values when creating shapes, then reference them in subsequent connect actions.
|
||||
Available shape types: folk-markdown, folk-wrapper, folk-image-gen, folk-video-gen, folk-prompt,
|
||||
folk-embed, folk-calendar, folk-map, folk-chat, folk-slide, folk-obs-note, folk-workflow-block,
|
||||
folk-social-post, folk-social-thread, folk-social-campaign, folk-social-newsletter,
|
||||
folk-splat, folk-drawfast, folk-rapp, folk-feed.
|
||||
Available shape types (grouped by category):
|
||||
|
||||
Core: folk-markdown, folk-wrapper, folk-embed, folk-image, folk-bookmark, folk-slide, folk-chat, folk-piano, folk-canvas, folk-rapp, folk-feed, folk-obs-note, folk-workflow-block, folk-google-item.
|
||||
AI: folk-prompt, folk-image-gen, folk-image-studio, folk-video-gen, folk-zine-gen, folk-transcription.
|
||||
Creative: folk-splat, folk-drawfast, folk-blender, folk-freecad, folk-kicad, folk-design-agent.
|
||||
Social: folk-social-post, folk-social-thread, folk-social-campaign, folk-social-newsletter.
|
||||
Decisions: folk-choice-vote, folk-choice-rank, folk-choice-spider, folk-choice-conviction, folk-spider-3d.
|
||||
Travel: folk-itinerary, folk-destination, folk-booking, folk-budget, folk-packing-list.
|
||||
Tokens: folk-token-mint, folk-token-ledger, folk-transaction-builder.
|
||||
Geo: folk-holon, folk-holon-browser, folk-map, folk-calendar.
|
||||
Video: folk-video-chat.
|
||||
|
||||
## Transforms
|
||||
When the user asks to align, distribute, or arrange selected shapes:
|
||||
|
|
@ -324,6 +331,25 @@ analyze it and classify each distinct piece into the most appropriate canvas sha
|
|||
- Marketing campaigns / content plans → folk-social-campaign (set title, description, platforms props)
|
||||
- Newsletters / email campaigns → folk-social-newsletter (set subject, listName props)
|
||||
- Decisions / polls / questions for voting → folk-choice-vote (set question prop)
|
||||
- Ranked choices / priority lists → folk-choice-rank (set question prop)
|
||||
- Multi-criteria evaluation → folk-choice-spider (set question prop)
|
||||
- Travel destinations → folk-destination (set destName, country props)
|
||||
- Trip itineraries → folk-itinerary (set tripTitle, itemsJson props)
|
||||
- Bookings / reservations → folk-booking (set bookingType, provider props)
|
||||
- Budget / expenses → folk-budget (set budgetTotal props)
|
||||
- 3D models / scenes → folk-splat or folk-blender (set src prop)
|
||||
- Circuit / PCB design → folk-kicad (set brief prop)
|
||||
- CAD / 3D parts → folk-freecad (set brief prop)
|
||||
- Print / layout design → folk-design-agent (set brief prop)
|
||||
- AI chat / assistant → folk-prompt (start a conversation)
|
||||
- Image generation requests → folk-image-gen (set prompt prop)
|
||||
- Video generation requests → folk-video-gen (set prompt prop)
|
||||
- Zine / publication content → folk-zine-gen (set prompt prop)
|
||||
- Audio / transcription → folk-transcription
|
||||
- Data feeds from modules → folk-feed (set sourceModule, feedId props)
|
||||
- Embed another rApp → folk-rapp (set moduleId prop)
|
||||
- Token minting → folk-token-mint (set tokenName, symbol props)
|
||||
- Token ledger / balances → folk-token-ledger (set tokenId prop)
|
||||
- Everything else (prose, notes, transcripts, summaries) → folk-markdown (set content prop in markdown format)
|
||||
|
||||
## Output Format
|
||||
|
|
@ -342,11 +368,28 @@ Return a JSON object with:
|
|||
- If the content is too short or trivial for multiple shapes, still return at least one shape`;
|
||||
|
||||
const KNOWN_TRIAGE_SHAPES = new Set([
|
||||
"folk-markdown", "folk-embed", "folk-image", "folk-bookmark",
|
||||
"folk-calendar", "folk-map",
|
||||
"folk-workflow-block", "folk-social-post", "folk-social-thread",
|
||||
"folk-social-campaign", "folk-social-newsletter", "folk-choice-vote",
|
||||
"folk-prompt", "folk-image-gen", "folk-slide",
|
||||
// Core
|
||||
"folk-markdown", "folk-wrapper", "folk-embed", "folk-image", "folk-bookmark",
|
||||
"folk-slide", "folk-chat", "folk-piano", "folk-canvas", "folk-rapp", "folk-feed",
|
||||
"folk-obs-note", "folk-workflow-block", "folk-google-item",
|
||||
// AI
|
||||
"folk-prompt", "folk-image-gen", "folk-image-studio", "folk-video-gen",
|
||||
"folk-zine-gen", "folk-transcription",
|
||||
// Creative
|
||||
"folk-splat", "folk-drawfast", "folk-blender", "folk-freecad", "folk-kicad",
|
||||
"folk-design-agent",
|
||||
// Social
|
||||
"folk-social-post", "folk-social-thread", "folk-social-campaign", "folk-social-newsletter",
|
||||
// Decisions
|
||||
"folk-choice-vote", "folk-choice-rank", "folk-choice-spider",
|
||||
"folk-choice-conviction", "folk-spider-3d",
|
||||
// Travel
|
||||
"folk-itinerary", "folk-destination", "folk-booking", "folk-budget", "folk-packing-list",
|
||||
// Tokens
|
||||
"folk-token-mint", "folk-token-ledger", "folk-transaction-builder",
|
||||
// Geo & Video
|
||||
"folk-map", "folk-calendar", "folk-video-chat",
|
||||
"folk-holon", "folk-holon-browser",
|
||||
]);
|
||||
|
||||
function sanitizeTriageResponse(raw: any): { shapes: any[]; connections: any[]; summary: string } {
|
||||
|
|
|
|||
|
|
@ -2433,6 +2433,7 @@
|
|||
FolkCalendar,
|
||||
FolkMap,
|
||||
FolkImageGen,
|
||||
FolkImageStudio,
|
||||
FolkVideoGen,
|
||||
FolkPrompt,
|
||||
FolkZineGen,
|
||||
|
|
@ -2682,6 +2683,7 @@
|
|||
FolkCalendar.define();
|
||||
FolkMap.define();
|
||||
FolkImageGen.define();
|
||||
FolkImageStudio.define();
|
||||
FolkVideoGen.define();
|
||||
FolkPrompt.define();
|
||||
FolkTranscription.define();
|
||||
|
|
@ -2695,6 +2697,7 @@
|
|||
FolkBooking.define();
|
||||
FolkTokenMint.define();
|
||||
FolkTokenLedger.define();
|
||||
FolkTransactionBuilder.define();
|
||||
FolkChoiceVote.define();
|
||||
FolkChoiceRank.define();
|
||||
FolkChoiceSpider.define();
|
||||
|
|
@ -2731,6 +2734,7 @@
|
|||
shapeRegistry.register("folk-calendar", FolkCalendar);
|
||||
shapeRegistry.register("folk-map", FolkMap);
|
||||
shapeRegistry.register("folk-image-gen", FolkImageGen);
|
||||
shapeRegistry.register("folk-image-studio", FolkImageStudio);
|
||||
shapeRegistry.register("folk-video-gen", FolkVideoGen);
|
||||
shapeRegistry.register("folk-prompt", FolkPrompt);
|
||||
shapeRegistry.register("folk-zine-gen", FolkZineGen);
|
||||
|
|
@ -2745,6 +2749,7 @@
|
|||
shapeRegistry.register("folk-booking", FolkBooking);
|
||||
shapeRegistry.register("folk-token-mint", FolkTokenMint);
|
||||
shapeRegistry.register("folk-token-ledger", FolkTokenLedger);
|
||||
shapeRegistry.register("folk-transaction-builder", FolkTransactionBuilder);
|
||||
shapeRegistry.register("folk-choice-vote", FolkChoiceVote);
|
||||
shapeRegistry.register("folk-choice-rank", FolkChoiceRank);
|
||||
shapeRegistry.register("folk-choice-spider", FolkChoiceSpider);
|
||||
|
|
@ -3937,7 +3942,12 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest
|
|||
"folk-choice-spider": { width: 440, height: 540 },
|
||||
"folk-spider-3d": { width: 440, height: 480 },
|
||||
"folk-choice-conviction": { width: 380, height: 480 },
|
||||
"folk-social-post": { width: 300, height: 380 },
|
||||
"folk-social-post": { width: 300, height: 380 },
|
||||
"folk-social-thread": { width: 280, height: 320 },
|
||||
"folk-social-campaign": { width: 300, height: 380 },
|
||||
"folk-social-newsletter": { width: 280, height: 300 },
|
||||
"folk-design-agent": { width: 450, height: 550 },
|
||||
"folk-image-studio": { width: 420, height: 520 },
|
||||
"folk-multisig-email": { width: 400, height: 380 },
|
||||
"folk-splat": { width: 480, height: 420 },
|
||||
"folk-blender": { width: 420, height: 520 },
|
||||
|
|
|
|||
Loading…
Reference in New Issue