日常调研的时候喜欢和 GPT 对话,有些对话可能需要保存,以供后续参考。之前的做法是复制或者导出到思源笔记本地。不过这个做法感觉也有一些不爽的地方。
- 很多对话动辄就是大几万字,偏偏很多对话的信息量其实没那么大,就是做个只读的参考草稿用。这些内容明明没有编辑的需求,放到笔记里却会占用思源的块索引空间,感觉有点浪费。
- 我经常同时和多个模型进行对话,不同模型可能给出不同偏向的回答。但是在保存整理的时候,在如何整理上有些为难:放到不同文档有点分散,放到同一个文档又感觉有点拥挤。
所以最近我换了一个思路:这些草稿、只读性质的内容干脆就不放到本地里面了,而是丢到附件当中。在思源中创建 Markdown 附件,把一些草稿、相对低价值、或者只读的参考内容丢到附件里,不要占用思源的索引空间。
fmisc 里面之前就实现了一个创建资源文件的功能,前两天稍微整理了一下,把用户创建的文件全都丢到一个 assets/user 目录下面去。需要的时候在编辑器里面 /new
创建一个 markdown 文件就行。
很久以前买了吃灰 Typora 也终于能用上,点开 md 文件,把乱七八糟的内容复制进去就行。然后这些 md 文件就作为附件保存到 asset 下面去就好。
好消息:首先不占用思源的块索引空间了,然后不同的草稿对话也只要简单的放在一起就行。资源文件本身就是一个管理粒度,而且可以随便引用到笔记的各个地方也基本不占用额外空间。
存储用附件好是好,还需要解决查看问题,最好能在思源内直接预览附件 MD 文件的内容。
其实也好办,之前我用 Query View 写过一个简单的 Preview 组件,大致思路是用 fetch
获得文件文本内容,然后调用 dv.md
渲染 Markdown (实现代码参考 https://github.com/frostime/sy-query-view/discussions/7)。
为了方便预览,我们实现的 Qv 查询主要做这么几件事情:
- 自动查询当前文档下所有的 markdown 附件
- 创建对应的按钮
- 点击按钮后,调用
Preview
组件创建 Markdown 文件的预览
//!js const useButton = (title, onclick) => { let button = document.createElement('button'); button.className = 'b3-button b3-button--text'; button.innerText = title; button.onclick = onclick; return button; } const query = async () => { let dv = Query.DataView(protyle, item, top); dv.render(); let assets = await Query.request('/api/asset/getDocAssets', { id: dv.root_id }); assets = assets.filter(a => a.endsWith('.md')); const onclick = asset => { //dv.Preview 是一个自定义组件(自定义组件请参考插件说明文档);代码见 https://github.com/frostime/sy-query-view/discussions/7 let view = dv.replaceView(main.dataset.id, dv.Preview(asset, {maxHeight: '800px'})); } const container = document.createElement('div'); container.style.fontSize = '0.9em'; assets.forEach(asset => { const button = useButton(asset.replace('assets/', ''), () => { onclick(asset); }); button.style.margin = '5px'; container.appendChild(button); }); dv.adddetail('Assets', container); dv.addmd('---'); let main = dv.addele(''); main.style.maxHeight = '800px'; main.style.overflowY = 'auto'; } return query();
效果大概这个样子:
🤔 小缺憾:目前发现一个问题,思源里面 assets 文件不方便重命名;如果一开始 markdown 没有创建好命名,后面想要换一个名称就会很麻烦。
附: Preview 组件的实现
const custom = { assetPreview: { use: (dv) => { return { render: async (container, fileUrl, options) => { try { const response = await fetch(fileUrl); const contentType = response.headers.get('content-type'); const fileContent = await response.text(); let element; // 如果是 html,那么就放入 iframe 中 if (contentType.startsWith('application/')) { const codeType = contentType.split('/')[1]; element = dv.md(`\`\`\`${codeType}\n${fileContent}\n\`\`\``); } else if (contentType.startsWith('text/html')) { element = document.createElement('iframe'); element.src = fileUrl; element.style.width = '100%'; } else if (contentType.startsWith('image/')) { element = document.createElement('img'); element.src = fileUrl; element.style.maxWidth = '100%'; } else if (contentType.startsWith('text/')) { element = dv.md(fileContent); } else { element = document.createElement('div'); element.innerText = '无法预览'; } const title = document.createElement('p'); title.innerText = `预览: ${fileUrl}`; Object.assign(title.style, { backgroundColor: 'var(--b3-theme-primary-light)', color: 'var(--b3-theme-on-primary)', borderRadius: '4px', fontWeight: 'bold', padding: '4px 8px', }); if (options?.maxHeight) { Object.assign(element.style, { maxHeight: options.maxHeight, overflowY: 'auto', }); } container.appendChild(title); container.appendChild(element); } catch (error) { console.error('Error fetching file:', error); const errorElement = document.createElement('div'); errorElement.innerText = '无法预览'; container.appendChild(errorElement); } } } }, alias: ['Preview', 'Asset'] }, }
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于