下面是之前的聊天记录
Q: 我现在希望开发一个卡片笔记软件。UI 界面类似于 imessage 。 请问叫什么名字比较好?
A 为了取一个好名字,需要考虑软件的特点和目标用户。考虑到你的软件类似于 iMessage ,那么你可以考虑使用类似的命名方式。以下是一些可能的建议:
CardMessage NoteChat CardNote SnapNote MemoTalk NoteMessage 在考虑具体的名字时,可以考虑软件的品牌形象,功能特点等方面,以便从众多同类软件中脱颖而出
搜了一下,memo talk 之前没人开发过,就叫这个名字吧。
建了一个 GitHub 仓库 https://github.com/hamsterbase/memo-talk
Q: sha256 转化为字符串,长度是多少。
A:
SHA256 是一种加密算法,它生成的哈希值( hash value )通常表示为 64 个字符的十六进制字符串( hexadecimal string ),因此 SHA256 转化为字符串后的长度是 64 个字符。每个字符是 0-9 和 A-F 之间的一个 16 进制数字,因此它们可以表示 256 个可能的值( 16 的 4 次方)。
5
Q:
我希望你能帮我编写一下服务端的代码。
需要有以下四个接口接口为
获取文件夹列表
根据文件夹名字,文件名获取文件内容
根据文件夹名字,文件名删除文件
根据文件夹名字,文件名删除文件
要求。
A: 以下是一个基于 Koa 和 TypeScript 的简单实现,其中包含了四个接口:
import Koa from "koa";
import bodyParser from "koa-bodyparser";
const app = new Koa();
// 定义文件夹列表
let folders: string[] = [];
// 定义文件内容
let files: { [key: string]: string } = {};
// 获取文件夹列表
app.use(async (ctx, next) => {
if (ctx.path === "/folders" && ctx.method === "GET") {
ctx.body = folders;
ctx.status = 200;
} else {
await next();
}
});
// 获取文件内容
app.use(async (ctx, next) => {
if (ctx.path === "/files" && ctx.method === "GET") {
const folderName = ctx.request.body.folderName;
const fileName = ctx.request.body.fileName;
const filePath = `${folderName}/${fileName}`;
if (files[filePath]) {
ctx.body = files[filePath];
ctx.status = 200;
} else {
ctx.body = "File not found";
ctx.status = 404;
}
} else {
await next();
}
});
// 添加文件
app.use(bodyParser());
app.use(async (ctx, next) => {
if (ctx.path === "/files" && ctx.method === "POST") {
const folderName = ctx.request.body.folderName;
const fileName = ctx.request.body.fileName;
const fileContent = ctx.request.body.fileContent;
const filePath = `${folderName}/${fileName}`;
if (files[filePath]) {
ctx.body = "File already exists";
ctx.status = 409;
} else {
files[filePath] = fileContent;
ctx.status = 201;
}
} else {
await next();
}
});
// 删除文件
app.use(async (ctx, next) => {
if (ctx.path === "/files" && ctx.method === "DELETE") {
const folderName = ctx.request.body.folderName;
const fileName = ctx.request.body.fileName;
const filePath = `${folderName}/${fileName}`;
if (files[filePath]) {
delete files[filePath];
ctx.status = 204;
} else {
ctx.body = "File not found";
ctx.status = 404;
}
} else {
await next();
}
});
app.listen(3000, () => {
console.log("Server is running on port 3000");
});
请注意,此代码仅供参考,可能需要根据具体需求进行修改和调整。
1
hamsterbase OP 6. 他没把文件写到本地,所以改一下 prompt
Q: 我希望你能帮我编写一下服务端的代码。 需要有以下四个接口接口为 1. 获取文件夹列表 2. 根据文件夹名字,文件名获取文件内容 3. 根据文件夹名字,文件名删除文件 4. 根据文件夹名字,文件名删除文件 要求。 1. 基于 koa 编写 2. 使用 typescript 编写 3. 文件名,文件名必须为 64 个字符,每个字符是 0-9 和 A-F 之间的一个 16 进制数字 4. 文件夹名和文件名,文件内容都通过 body 传递。 5. 我希望你能用 koa-router 编写 6. 文件希望写入到本地的 data 文件夹下 7. API 必须以 public/api/v1 开头 Q: ```ts import Koa from "koa"; import Router from "koa-router"; import fs from "fs/promises"; import path from "path"; const app = new Koa(); const router = new Router(); const DATA_FOLDER = "data"; // 获取文件夹列表 router.get("/public/api/v1/folders", async (ctx) => { try { const folders = await fs.readdir(DATA_FOLDER); ctx.body = folders; } catch (err) { ctx.status = 500; ctx.body = "Internal Server Error"; console.error(err); } }); // 根据文件夹名字,文件名获取文件内容 router.get( "/public/api/v1/folders/:folderName/files/:fileName", async (ctx) => { const { folderName, fileName } = ctx.params; const filePath = path.join(DATA_FOLDER, folderName, fileName); try { const fileContent = await fs.readFile(filePath, "utf-8"); ctx.body = fileContent; } catch (err) { if (err.code === "ENOENT") { ctx.status = 404; ctx.body = "File Not Found"; } else { ctx.status = 500; ctx.body = "Internal Server Error"; console.error(err); } } } ); // 根据文件夹名字,文件名删除文件 router.del( "/public/api/v1/folders/:folderName/files/:fileName", async (ctx) => { const { folderName, fileName } = ctx.params; const filePath = path.join(DATA_FOLDER, folderName, fileName); try { await fs.unlink(filePath); ctx.body = "File Deleted"; } catch (err) { if (err.code === "ENOENT") { ctx.status = 404; ctx.body = "File Not Found"; } else { ctx.status = 500; ctx.body = "Internal Server Error"; console.error(err); } } } ); // 根据文件夹名字,文件名删除文件 router.post( "/public/api/v1/folders/:folderName/files/:fileName", async (ctx) => { const { folderName, fileName } = ctx.params; const filePath = path.join(DATA_FOLDER, folderName, fileName); try { await fs.mkdir(path.join(DATA_FOLDER, folderName), { recursive: true }); await fs.writeFile(filePath, ctx.request.body); ctx.body = "File Created"; } catch (err) { ctx.status = 500; ctx.body = "Internal Server Error"; console.error(err); } } ); app.use(router.routes()); app.use(router.allowedMethods()); app.listen(3000, () => { console.log("Server listening on port 3000"); }); ``` 接口说明: 获取文件夹列表:GET /public/api/v1/folders 根据文件夹名字,文件名获取文件内容:GET /public/api/v1/folders/:folderName/files/:fileName 根据文件夹名字,文件名删除文件:DELETE /public/api/v1/folders/:folderName/files/:fileName 根据文件夹名字,文件名删除文件:POST /public/api/v1/folders/:folderName/files/:fileName 其中,文件名和文件夹名都是 64 个字符的 16 进制数字字符串。文件内容通过 Request Body 传递,文件保存在本地的 data 文件夹下。如果出现错误,会返回适当的 HTTP 状态码和错误信息。 |
2
hamsterbase OP 7. 之后不贴 chatgpt 的回答了,复制粘贴太麻烦啦。
Q: 我希望你能帮我编写一下服务端的代码。 参数命名规则为 folder , file , content 。 需要有以下 4 个接口为。 1. 获取 folder 获取文件列表。 2. 根据 folder ,file 获取 content 3. 根据 folder ,file 删除文件 4. 根据 folder ,file, content 写入文件 要求。 1. 基于 koa 编写,使用 typescript 编写 2. folder , file 必须为 64 个字符,每个字符是 0-9 和 A-F 之间的一个 16 进制数字. 3. 我希望你能用 koa-router 编写 4. 文件希望写入到本地的 data 文件夹下 5. 规则为 /data/folder/file 6. API 必须以 public/api/v1 开头 7. content 限制最大 1mb 8. folder, file, content 都通过 body 获取,不要用 query,path 获取。 |
3
hamsterbase OP 8. koa-router 好像没有类型,换 @koa/router. 初始化项目,把 chatgpt 回答拷贝一下
https://github.com/hamsterbase/memo-talk/commit/2cc9318a7255e2dafef2716e78b2e844953042cc |
4
hamsterbase OP 9 。chatgpt 没有写文件名校验逻辑,让他单独写一个
帮我用 ts 写一个字符串检测函数。 字符串长度必须为 64 个字符,每个字符是 0-9 和 A-F 之间的一个 16 进制数字. 忽略大小写。 最终输出转换为小写后的结果。 如果不满足,则抛错。 |
5
hamsterbase OP 10. 白嫖 render.com , 服务器上线了 https://memo-talk.onrender.com
|
6
hamsterbase OP 11. 帮我想一下 memo-talk 前端仓库的名字
以下是一些可能的 Memo-Talk 前端仓库的名字: - memo-talk-frontend:一个简单而明确的仓库名称,指示该仓库包含 Memo-Talk 的前端代码。 - memo-talk-web:一个更简洁的仓库名称,指示该仓库包含 Memo-Talk 的 Web 界面代码。 - memo-talk-ui:一个仓库名称,指示该仓库包含 Memo-Talk 的用户界面部分。 - memo-talk-client:一个仓库名称,指示该仓库包含 Memo-Talk 的客户端代码。 - memo-talk-portal:一个仓库名称,指示该仓库包含 Memo-Talk 的 Web 门户代码。 你可以根据你的喜好和项目的具体需求选择其中任何一个名称。无论你选择哪个名称,都需要确保它能够准确地描述该仓库的内容,并且易于理解和记忆。此外,你可能还需要考虑使用标准的命名约定和格式,以便其他开发人员能够轻松地找到和使用你的仓库。 |
7
hamsterbase OP 12. 建立了前端的仓库 https://github.com/hamsterbase/memo-talk-web
|
8
hamsterbase OP 13. 使用 vite 的 ts + react 模板,数据核心准备使用 yjs
|
9
hamsterbase OP |
10
hamsterbase OP 15.
Q: 帮我设计一个 ts interface. NoteCore 1. 新建笔记,返回 ID 2. 根据 ID 返回笔记内容。 3. 获取笔记列表. 4. 根据 ID 删除笔记 |
11
hamsterbase OP 16. 设计一下核心的数据结构。 这个是我自己设计的
export interface MemoTalk { id: string; content: string; createTime: number; } /** * MemoTalkCore 核心为一个 Y.Doc 对象 * 保存的时候 encode 为字符串 * 可以合并别的 MemoTalkCore, 入参为字符串 */ export interface MemoTalkCore { createMemoTalk(content: string): string; getMemoTalkById(id: string): MemoTalk; getMemoTalkList(): MemoTalk[]; deleteMemoTalkById(id: string): void; /** * 把整个数据库序列化为字符串 */ encode(): string; merge(data: string): void; } |
12
hamsterbase OP 17. 在 copilot 的帮助下,完成了核心逻辑的实现
const enum YDocKey { /** * 避免 id 重复 */ id = "id", memoTalks = "memoTalks", } export class MemoTalkCore implements IMemoTalkCore { private ydoc: Y.Doc; constructor() { this.ydoc = new Y.Doc(); } createMemoTalk(content: string): string { if (typeof content !== "string") { throw new Error("content must be string"); } const idMap = this.ydoc.getMap(YDocKey.id); let id: string = nanoid(); while (idMap.has(id)) { id = nanoid(); } const memoTalksArray = this.ydoc.getArray<string>(YDocKey.memoTalks); const memoTalk = this.ydoc.getMap(id); memoTalk.set("content", content ?? ""); memoTalk.set("createTime", Date.now()); memoTalksArray.push([id]); return id; } getMemoTalkById(id: string): MemoTalk { const memoTalk = this.ydoc.getMap(id); return { id, content: memoTalk.get("content") as string, createTime: memoTalk.get("createTime") as number, }; } getMemoTalkList(): MemoTalk[] { const memoTalksArray = this.ydoc.getArray<string>(YDocKey.memoTalks); return memoTalksArray.map((id) => this.getMemoTalkById(id)); } deleteMemoTalkById(id: string): void { const memoTalksArray = this.ydoc.getArray<string>(YDocKey.memoTalks); memoTalksArray.delete(memoTalksArray.toArray().indexOf(id)); } encode(): string { return fromUint8Array(Y.encodeStateAsUpdate(this.ydoc)); } merge(data: string): void { Y.applyUpdate(this.ydoc, toUint8Array(data)); } } |
13
hamsterbase OP 18 。 帮我为 IMemoTalkCore 编写集成单元测试。 使用 jest
```ts /** * MemoTalkCore 核心为一个 Y.Doc 对象 * 保存的时候 encode 为字符串 * 可以合并别的 MemoTalkCore, 入参为字符串 */ export interface IMemoTalkCore { createMemoTalk(content: string): string; getMemoTalkById(id: string): MemoTalk; getMemoTalkList(): MemoTalk[]; deleteMemoTalkById(id: string): void; /** * 把整个数据库序列化为字符串 */ encode(): string; merge(data: string): void; } ``` |
14
hamsterbase OP 19. 在 chatgpt 的帮助下测出了一个 bug, 如果删除了,再次获取的时候应该返回 null 。 所有测试都通过啦
|
15
hamsterbase OP 20. 完善一下单元测试
Q: 帮我用 jest 编写测试。 如果重复删除,应该抛错 |
16
hamsterbase OP 21 Q: 帮我写一个 react 的页面,需要用 typescript 开发。
最顶上是一个提示框。 红色的字 “此项目还在开发中,请不要使用” 中间为一个消息窗口。 消息都靠右。 消息为圆角。消息的背景为蓝色。 参考 imessage 页面背景为白色。 最下面是一个 input 框。 里面有一个按钮。 按钮上面的字为 “发送” 颜色要好看一点。 圆角保存统一。 props 为 memotalks:MemoTalk, onCreateMemoTalk(content:string):void export interface MemoTalk { id: string; content: string; createTime: number; } |
17
hamsterbase OP |
18
hamsterbase OP 23: 前端最基础的功能开发玩了,还没接入服务器。
1. 支持新增消息了, 消息更新后会储存到 local storage 里。 2. 刷新页面后支持恢复消息 在线地址: https://memo-talk-web.onrender.com/ 明天晚上继续开始直播。 1. 接入服务 2. 支持设置密码和账户 3. 支持下载当前数据库,支持上传数据库。 |
19
findex 2023-06-09 05:40:32 +08:00 via iPhone
关注一下
|
20
ShuaiYH 2023-06-09 08:59:48 +08:00
@hamsterbase #5 来试试 zeabur.com 哈哈
|
21
llsongll 2023-06-09 09:44:08 +08:00
感觉不错~
|
22
hamsterbase OP |
23
hamsterbase OP |
24
HulkLee 2023-06-09 20:22:08 +08:00
哈哈哈 今天晚上的几点开始,蹲一波
|
25
hamsterbase OP @HulkLee 开始开始。~~~
|
26
hamsterbase OP 1. 今天的目标: 发布 1.0 !!!
1. 接入 antd mobile ,重写 UI 2. 接入后端,实现登录功能. 1. 服务器不储存任何账户密码。只储存用户的 token ,token 由前端生成。 2. 把 token 作为文件夹名 3. token 的生成规则:sha256( `${sha256(user)} ${sha256(password)}`) 4. 文件储存之前使用 aes 加密。 加密 为 sha256(password) 3. 接入 PWA , 让软件可离线使用 |
27
hamsterbase OP 2. localstorage 存储量有限,准备基于 IndexedDB 实现本地存储
Q: 帮我基于 IndexedDB 实现一个 ISettingService 吧,我要把所有的配置都存在 IndexedDB 里面。 export type SettingsValue = string | boolean; export enum StorageKeys { backend = "backend", "backend.hamsterbase.entrypoint" = "backend.hamsterbase.entrypoint", "backend.hamsterbase.token" = "backend.hamsterbase.token", "backend.notion.token" = "backend.notion.token", "backend.notion.databaseId" = "backend.notion.databaseId", "backend.notion.license" = "backend.notion.license", autoOn = "autoOn", autoOnBlockList = "autoOnBlockList", } export interface ISettingService { _serviceBrand: undefined; get<V extends SettingsValue>(key: string, defaultValue: V): Promise<V>; set(key: string, value: SettingsValue): Promise<void>; readConfig<T extends Record<StorageKeys, SettingsValue>>( defaultValue: T ): Promise<T>; } |
28
hamsterbase OP 好的,我可以帮您实现一个基于 IndexedDB 的 ISettingService 。以下是实现代码示例:
typescript Copy export interface ConfigDatabase { settings: Record<string, SettingsValue>; } export class IndexedDBSettingService implements ISettingService { private readonly dbName: string = "my-settings-db"; private readonly dbVersion: number = 1; private readonly storeName: string = "settings"; private db: IDBDatabase; constructor() { this.openDB(); } private async openDB(): Promise<void> { return new Promise<void>((resolve, reject) => { const request = indexedDB.open(this.dbName, this.dbVersion); request.onerror = (event) => { console.error("Error opening database", event); reject(event); }; request.onsuccess = (event) => { this.db = request.result; resolve(); }; request.onupgradeneeded = (event) => { const db = request.result; if (!db.objectStoreNames.contains(this.storeName)) { db.createObjectStore(this.storeName); } }; }); } public async get<V extends SettingsValue>( key: string, defaultValue: V ): Promise<V> { return new Promise<V>((resolve, reject) => { const transaction = this.db.transaction(this.storeName, "readonly"); const objectStore = transaction.objectStore(this.storeName); const request = objectStore.get(key); request.onerror = (event) => { console.error(`Error getting value for key "${key}"`, event); reject(event); }; request.onsuccess = (event) => { const value = request.result !== undefined ? request.result : defaultValue; resolve(value); }; }); } public async set(key: string, value: SettingsValue): Promise<void> { return new Promise<void>((resolve, reject) => { const transaction = this.db.transaction(this.storeName, "readwrite"); const objectStore = transaction.objectStore(this.storeName); const request = objectStore.put(value, key); request.onerror = (event) => { console.error(`Error setting value for key "${key}"`, event); reject(event); }; request.onsuccess = (event) => { resolve(); }; }); } public async readConfig<T extends Record<StorageKeys, SettingsValue>>( defaultValue: T ): Promise<T> { const config: T = {} as T; // Read all values from the database and merge with default values for (const key in defaultValue) { if (defaultValue.hasOwnProperty(key)) { const value = await this.get(key, defaultValue[key]); config[key as keyof T] = value as T[keyof T]; } } return config; } public async clearAll(): Promise<void> { return new Promise<void>((resolve, reject) => { const transaction = this.db.transaction(this.storeName, "readwrite"); const objectStore = transaction.objectStore(this.storeName); const request = objectStore.clear(); request.onerror = (event) => { console.error("Error clearing settings", event); reject(event); }; request.onsuccess = (event) => { resolve(); }; }); } } 在这个实现中,我们创建了一个名为 my-settings-db 的 IndexedDB 数据库,并创建了一个名为 settings 的对象存储。在 get 和 set 方法中,我们分别使用事务来读取和写入数据。在 readConfig 方法中,我们遍历默认配置对象中的所有键,并使用 get 方法来获取每个键的值。最后,我们还为 IndexedDBSettingService 类添加了一个清除所有设置的 clearAll 方法。 请注意,由于 IndexedDB 是异步的,因此所有方法都返回一个 Promise 。此外,如果 IndexedDB 连接失败或出现错误,这些方法将会拒绝 Promise 并输出错误信息。 |
29
hamsterbase OP 3. 对 chatgpt 写的代码不太放心,准备写一个单元测试
Q: 帮我用 mocha 写单元测试 ```ts export interface ISettingService { get<V extends SettingsValue>(key: string, defaultValue: V): Promise<V>; set(key: string, value: SettingsValue): Promise<void>; readConfig<T extends Record<string, SettingsValue>>( defaultValue: T ): Promise<T>; } ``` |
30
hamsterbase OP 4. 本来准备在 vite 里跑 mocha ,试了一下发现不太行,准备换一条路。 用假的 indexedDB 测试。 测试继续在 vitest 里跑。
|
31
hamsterbase OP 5. 逻辑和测试都让 chatgpt 写好了, 看了一下测试似乎没问题
他给的是在 IndexedDBSettingService 构造函数里调用 openDB, 我改成了每次 set , get 的时候初始化,似乎没问题。 https://github.com/hamsterbase/memo-talk-web/commit/31cc5a97a31d69f015f6ecfd089fb3147c1db7dd |
32
hamsterbase OP 6. 使用 vite 的多页面模式开发。 初始化一下 settings 页面。 配置一下 vite 的构建配置
https://github.com/hamsterbase/memo-talk-web/commit/d7024d70c6c63cca47aa550175504aa95e22f3eb |
33
echoless 2023-06-09 21:59:11 +08:00
|
34
hamsterbase OP 7. 因为是极简的稍后读工具,所以在不校验账户密码。 只需要确保账户密码非空就行了
简单完成了账户密码的保存功能。 未来会加上警告,用户协议。 目前暂时先不加. 因为 antdmobile 是在 2019 年之后出的,chatgpt 估计不认识,就手写了 https://github.com/hamsterbase/memo-talk-web/commit/c5559845d27cdeba278c9969330c474e26db9ac6 |
35
hamsterbase OP 8. 简单更新了一下首页的 UI , 然后把储存从 localstorage 换到了 IndexedDB
开始真正对接服务器了。 第一版做的简单一点,在页面加载的时候异步合并一下服务器的数据,合并后把数据上传到服务器。 |
36
hamsterbase OP 9 。 开始编写服务器需要的一些工具函数
Q: 帮我用 crypto-js 编写函数。 输入账户,密码。 生成两个 key 第一个 key sha256(memo:sha256(username):sha256(password):talk); 第二 key 。sha256(memo:password:talk 用 ts 编写 第一个 key 名字为 userToken , 第二个 key 名字为 加密密钥(用英文) |
37
hamsterbase OP 10. 帮我编写一个 aes 加密函数. 输入 key 和 data, 返回加密后的数据. 输入 data 和 key , 返回解密后的数据
基于 crypto-js 和 ts |
38
hamsterbase OP 11: 帮上面的 encryptedData ,decryptedData 写测试。 随机的 key 和 data 。 加密和解密后数据不变
|
39
hamsterbase OP 12:帮我写一个 sha256 的函数。 基于 ts . crypto-js. 输入字符串,输出 hex
|
40
hamsterbase OP 13: POE 一天一次的 gpt4 体验卡
帮我基于下面的代码,写一个 js sdk 出来。 https://github.com/hamsterbase/memo-talk/blob/main/server/index.ts |
41
hamsterbase OP ```
class FileAPISDK { constructor(baseUrl = "http://localhost:9999") { this.baseUrl = baseUrl; } async getList(folder) { const response = await fetch(`${this.baseUrl}/public/api/v1/folder`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ folder }), }); this._handleErrors(response); return await response.json(); } async getFile(folder, file) { const response = await fetch(`${this.baseUrl}/public/api/v1/file`, { method: "GET", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ folder, file }), }); this._handleErrors(response); return await response.text(); } async deleteFile(folder, file) { const response = await fetch(`${this.baseUrl}/public/api/v1/file`, { method: "DELETE", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ folder, file }), }); this._handleErrors(response); return await response.text(); } async createFile(folder, file, content) { const response = await fetch(`${this.baseUrl}/public/api/v1/file`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ folder, file, content }), }); this._handleErrors(response); return await response.text(); } _handleErrors(response) { if (!response.ok) { throw new Error(`API request failed with status ${response.status}`); } } } ``` |
42
hamsterbase OP 14. 发现服务器忘记配置跨域了。
q:koajs 如何开启跨区请求。 |
43
hamsterbase OP 11. 服务器问题修复后,开始对接。 核心的云同步逻辑非常简单
const sdk = new FileAPISDK(url); // 生成加密密钥 // userToken 用来标记用户身份,也是服务器文件夹的名字 // encryptionKey 是文件加密密码,encryptionKey 不会发送到服务 const { userToken, encryptionKey } = generateKeys(username, passowrd); // 获取服务器数据库列表 const files: string[] = await sdk.getList(userToken); const mergedFiles: string[] = []; // 把服务器上的文件和本地合并,合并后加入等待删除列表 for (const file of files) { try { const remoteData = await sdk.getFile(userToken, file); const database = decryptData(remoteData, encryptionKey); memoTalkCore.merge(database); mergedFiles.push(file); } catch (error) { console.log(error); } } // 获取本地最新数据,加密 const currentData = encryptData(memoTalkCore.encode(), encryptionKey); const fileName = sha256(currentData); // 把加密后的文档存到服务器 await sdk.createFile(userToken, fileName, currentData); // 因为数据已经合并到本地了,所以把服务器的删掉 for (const mergedFile of mergedFiles) { try { await sdk.deleteFile(userToken, mergedFile); } catch (error) { console.log(error); } } } |
44
hamsterbase OP 12. 经过 2 天 6 小时的开发。 一个本地优先的极简卡片笔记就开发完了。(只有最核心功能)
可以访问 https://memo-talk-web.onrender.com 体验 注意: 项目还没正式发布,只能拿来玩。,不要在这里储存任何数据。 1. 纯 web 实现, 压缩后不到 173 k 。 2. 支持离线编辑,再次联网后自动同步。 3. 两台设备输入相同的账户密码。 刷新页面刷新后,他们会自动同步到同一个状态。 4. 所有数据在本地加密,服务器不储存明文数据。 |
45
HulkLee 2023-06-10 15:04:34 +08:00
牛,面向 chatgpt 编程。
|
46
llsongll 2023-06-12 10:32:41 +08:00
nb ,我就想着有这种笔记,跟微信聊天似的,能搜索,快捷输入,(最好能匹配像是 alfred ,utools 这种工具,安卓可以一键输入。)
|
47
temberature 2023-06-12 13:20:29 +08:00
@llsongll 直接用微信不行吗 :)
|
48
llsongll 2023-06-12 20:03:36 +08:00
@temberature #47 微信不是有监测吗。。。而且微信以前有插件可以用 alfred 输入,现在好像不行了。
|
49
temberature 2023-06-13 15:46:36 +08:00
@llsongll 这些确实也都是问题。但反过来说,为什么要像微信聊天呢~没有这个要求有很多应用差不多吧
|
50
llsongll 2023-06-13 21:14:20 +08:00
@temberature #49 嗯呢,我也有用 Obsidian 的 memos ,也有类似的效果,alfred 也可以在不打开 ob 的情况下快捷输入。就是虽然界面体验比微信的感觉差了好多
|
51
temberature 2023-06-13 21:21:25 +08:00
@llsongll 具体能说出来界面差在哪里吗 :)
|
52
dvbs2000 2023-06-13 22:16:16 +08:00
能麻烦直接把对话的 url 共享出来么 谢谢~ gpt 现在是有共享功能的。直接就可以共享
|
55
llsongll 2023-06-14 10:31:28 +08:00
@temberature #51 我其实也说不出来个具体,可以简单描述下我需要的功能。比如 1 、微信的联系人可以看做 ob 不同的库,例:inbox ;剪藏;日记; note 等等,这个栏可折叠可隐藏可快捷键切换,我以前用 typora 时,就是分文件夹的,打标签的话,搜索的时候会多输入,再一个限制自己少分类,我一般都是用 alfred 搜索的,倒是无所谓,但是手机端搜索就会麻烦,这样会好点。
2 、输入框在下面,焦点永远在输入框内,在写长文的时候可放大输入框,隐藏聊天记录,写完发送自动显示聊天记录,恢复刚才的界面。 3 、消息框,右键复制、删除、引用、转发到其他库、多选 4 、图片、文件、文字,标签和多选批量打标签 5 、搜索:可在单独库搜索,可全部库搜索。可搜文件,图片,视频,链接,可日期排序。可定位到当时聊天的地方,可定位在 finder 中。微信或者 notion 那种弹出式搜索就非常舒服,ob 侧边栏搜索超级难受。 6 、显示问题,我是用笔记本的,比较少用台式,我认为屏幕太大,会分散焦点,在我 13 寸屏幕上,显示三分之二或者五分之三会是一个比较合适的大小。 --- 以上这些应该是基本功能,下面我是常用的功能,每个笔记软件我都期待的。 1 、速度,我调教后的 ob ,使用了 6 个插件,一个小主题,因为老电脑所以打开还是比较慢,但是常驻后台会占用我为数不多的内存,所以,要么打开速度快,要么占用低,比较个人,穷逼的苦楚。 2 、快捷输入,mac:alfred 可以快捷输入到任何一个 md 文件中。手机端,启动软件后直接弹出输入框,因为要查看的话,不建议多点一下输入框外的部分,输入的话更需要快速,否则对于我这种老年人,怕不是过一会就忘了。。。 3 、mac 和手机端剪藏,这个感觉 md 的都不太好,所以我现在都手动复制,不太了解。 4 、本地。同步对我来说不是刚需,md 的同步都会被检测到,如何要同步反而数据库其实更安全。 5 、界面简洁好看,我的 ob 比微信界面还小,没有侧边栏,标签栏,全部都隐藏了,光秃秃的。 --- 大概想的就是这些,我可能说的不太有逻辑性,你凑乎看看。。。其实刚需就只有快捷输入,和快捷搜索。其他的都是体验类,能忍受,但会随时想换一个试试。 |
56
temberature 2023-06-15 13:18:57 +08:00
@llsongll 我还在用 12 年的 mac :)
基础功能主要考验细节把控能力,微信到今天打磨的确实挺不错的。 我主要关注的还是核心需求,快分为两种,一种绝对值的快,类似技术性能上的参数;一种是能产生质变的快,笔记这件事,根本限制还是来自大脑的认知能力,人又不擅长同时处理多件事情,所以快可以让人集中应对更丰富内聚的内容。 |
57
llsongll 2023-06-17 19:26:47 +08:00
@temberature #56 哈哈哈,我在用 13 年的 mac 。你说的对,ob 、vc 等对于老电脑来说,速度确实不丝滑。我想要的快捷输入和小界面,就像你说的集中应对。不过我是个设计师,所以可能对界面美观度要求更高一点。我找过一些软件,比如 nvALT ; FSnotes ;打开速度都很快,只是一些细节,样式不太好,所以我只拿来记录一些摘抄。你可以试一试,后者基于前者开发
|
58
Oz37sW2w3MIZf56o 2023-06-17 22:58:07 +08:00
歪个楼,求一个可以上 ChatGPT 不封号的梯子
|
59
temberature 2023-06-18 16:32:35 +08:00
@chenyduan 最近好多了,网络一般只是不让用,疯号好多是因为黑卡
|
60
temberature 2023-06-18 16:37:09 +08:00
@llsongll 感谢推荐,其实我在开发一个 IM 架构的给自己用,就是好奇为什么你也有这种需求,样式我喜欢简洁的那种,一般都不会太丑。
|
61
llsongll 2023-06-18 22:15:58 +08:00
@temberature #60 哇~厉害,希望有机会我可以体验一下
|
62
temberature 2023-06-19 13:37:38 +08:00
@llsongll 等我再调整调整 :)
|