openwriter 0.33.0 → 0.33.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
11
11
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
12
12
|
<link href="https://fonts.googleapis.com/css2?family=Charter:ital,wght@0,400;0,700;1,400&family=Crimson+Pro:ital,wght@0,300;0,400;0,600;0,700;1,400&family=DM+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=DM+Serif+Display&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600&family=Inter:wght@400;500;600;700&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Literata:ital,opsz,wght@0,7..72,400;0,7..72,600;0,7..72,700;1,7..72,400&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,600;1,6..72,400&family=Playfair+Display:wght@400;600;700;900&family=Source+Serif+4:ital,opsz,wght@0,8..60,400;0,8..60,600;0,8..60,700;1,8..60,400&family=Space+Grotesk:wght@400;500;600;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet" />
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-7TJTVuDI.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/index-BFw23tzV.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
@@ -28,6 +28,12 @@ interface PluginContextMenuItem {
|
|
|
28
28
|
condition?: 'has-selection' | 'empty-node' | 'always';
|
|
29
29
|
promptForInput?: boolean;
|
|
30
30
|
}
|
|
31
|
+
interface PluginSidebarMenuItem {
|
|
32
|
+
label: string;
|
|
33
|
+
action: string;
|
|
34
|
+
promptForFocus?: boolean;
|
|
35
|
+
folderCapable?: boolean;
|
|
36
|
+
}
|
|
31
37
|
interface OpenWriterPlugin {
|
|
32
38
|
name: string;
|
|
33
39
|
version: string;
|
|
@@ -36,6 +42,7 @@ interface OpenWriterPlugin {
|
|
|
36
42
|
configSchema?: Record<string, PluginConfigField>;
|
|
37
43
|
registerRoutes?(ctx: PluginRouteContext): void | Promise<void>;
|
|
38
44
|
contextMenuItems?(): PluginContextMenuItem[];
|
|
45
|
+
sidebarMenuItems?(): PluginSidebarMenuItem[];
|
|
39
46
|
}
|
|
40
47
|
declare const plugin: OpenWriterPlugin;
|
|
41
48
|
export default plugin;
|
|
@@ -27,6 +27,16 @@ function liveModelTier(fallback) {
|
|
|
27
27
|
return fallback;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
/** Strip a leading YAML frontmatter block so it never lands in the voice corpus. */
|
|
31
|
+
function stripFrontmatter(md) {
|
|
32
|
+
return md.replace(/^?---\r?\n[\s\S]*?\r?\n---\r?\n?/, '').trim();
|
|
33
|
+
}
|
|
34
|
+
/** Pull OpenWriter's stable doc id out of the on-disk markdown (frontmatter carries
|
|
35
|
+
* `"docId":"..."`). Returns null when absent so the caller can fall back to the filename. */
|
|
36
|
+
function extractOwDocId(md) {
|
|
37
|
+
const m = typeof md === 'string' ? md.match(/"docId"\s*:\s*"([^"]+)"/) : null;
|
|
38
|
+
return m ? m[1] : null;
|
|
39
|
+
}
|
|
30
40
|
const plugin = {
|
|
31
41
|
name: '@openwriter/plugin-authors-voice',
|
|
32
42
|
version: '0.4.0',
|
|
@@ -53,9 +63,9 @@ const plugin = {
|
|
|
53
63
|
description: 'Writing model',
|
|
54
64
|
options: [
|
|
55
65
|
{ value: '', label: 'Default (Strongest)' },
|
|
56
|
-
{ value: 'strongest', label: 'Strongest — Claude Opus (best quality,
|
|
57
|
-
{ value: 'balanced', label: 'Balanced — Claude Sonnet (
|
|
58
|
-
{ value: 'fast-plus', label: 'Fast+ — Gemini 3.5 Flash (newest, great,
|
|
66
|
+
{ value: 'strongest', label: 'Strongest — Claude Opus (best quality, $$$)' },
|
|
67
|
+
{ value: 'balanced', label: 'Balanced — Claude Sonnet ($$)' },
|
|
68
|
+
{ value: 'fast-plus', label: 'Fast+ — Gemini 3.5 Flash (newest, great, $)' },
|
|
59
69
|
{ value: 'fast', label: 'Fast — Gemini 2.5 Flash (free)' },
|
|
60
70
|
],
|
|
61
71
|
},
|
|
@@ -85,6 +95,55 @@ const plugin = {
|
|
|
85
95
|
return body;
|
|
86
96
|
return { ...body, modelTier: tier };
|
|
87
97
|
};
|
|
98
|
+
// Sidebar ingestion: right-click a doc (or a whole workspace/container) → "Add to
|
|
99
|
+
// Author's Voice". OpenWriter pre-fetches the doc's on-disk markdown and hands it here
|
|
100
|
+
// as `content`, so no file-upload primitive is needed — the doc IS the sample. We strip
|
|
101
|
+
// frontmatter, key the corpus docId off the OW doc's stable id (idempotent re-sync:
|
|
102
|
+
// re-running replaces that doc's chunks), and post to the AV corpus endpoint. Registered
|
|
103
|
+
// BEFORE the /api/voice/* wildcard so it isn't proxied straight through to the backend.
|
|
104
|
+
ctx.app.post('/api/voice/sidebar-action', async (req, res) => {
|
|
105
|
+
try {
|
|
106
|
+
const { filename, title, content } = req.body || {};
|
|
107
|
+
const body = stripFrontmatter(typeof content === 'string' ? content : '');
|
|
108
|
+
if (!body.trim()) {
|
|
109
|
+
res.status(400).json({ error: 'Document is empty — nothing to add to your voice.' });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Stable, namespaced corpus id so re-adding the same doc replaces its prior chunks.
|
|
113
|
+
const owId = extractOwDocId(content) || String(filename || '').replace(/\.[^.]+$/, '');
|
|
114
|
+
const docId = `ow-${owId}`;
|
|
115
|
+
const upstream = await fetch(`${backendUrl}/api/voice/content`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: authHeaders(),
|
|
118
|
+
body: JSON.stringify({ docId, content: body, categories: ['openwriter'], authenticity: 'Human' }),
|
|
119
|
+
});
|
|
120
|
+
const text = await upstream.text();
|
|
121
|
+
let data;
|
|
122
|
+
try {
|
|
123
|
+
data = JSON.parse(text);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
console.error('[AV Plugin] Non-JSON response (sidebar-action):', text.substring(0, 500));
|
|
127
|
+
res.status(502).json({ error: 'AV backend returned non-JSON response' });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!upstream.ok) {
|
|
131
|
+
res.status(upstream.status).json(data);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
res.json({
|
|
135
|
+
success: true,
|
|
136
|
+
action: 'add-to-corpus',
|
|
137
|
+
title: title || docId,
|
|
138
|
+
docId,
|
|
139
|
+
chunksCreated: data?.chunksCreated ?? 0,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
console.error('[AV Plugin] sidebar-action error:', err?.message || err);
|
|
144
|
+
res.status(502).json({ error: 'AV backend unreachable' });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
88
147
|
// Wildcard proxy for /api/voice/* routes. Pure pass-through: the AV API owns the
|
|
89
148
|
// engine choice (v1/v2) via its own AV_DEFAULT_ENGINE setting, so the plugin injects
|
|
90
149
|
// nothing but the optional owner-only dev debug flag.
|
|
@@ -164,5 +223,13 @@ const plugin = {
|
|
|
164
223
|
{ label: 'Fill sentence', action: 'av:fill-sentence', condition: 'empty-node' },
|
|
165
224
|
];
|
|
166
225
|
},
|
|
226
|
+
// Sidebar (file tree) right-click. `voice:` prefix routes the dispatch to this plugin's
|
|
227
|
+
// POST /api/voice/sidebar-action. folderCapable → also offered on workspaces/containers,
|
|
228
|
+
// where OpenWriter applies it to every doc in the folder.
|
|
229
|
+
sidebarMenuItems() {
|
|
230
|
+
return [
|
|
231
|
+
{ label: "Add to Author's Voice", action: 'voice:add-to-corpus', folderCapable: true },
|
|
232
|
+
];
|
|
233
|
+
},
|
|
167
234
|
};
|
|
168
235
|
export default plugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openwriter",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.2",
|
|
4
4
|
"description": "The open-source writing surface for AI agents. Markdown-native editor with pending change review — your agent writes, you accept or reject.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|