日常调研的时候喜欢和 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']
},
}
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于