luckytri 1.0.0
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/.github/SECURITY.md +71 -0
- package/LICENSE +21 -0
- package/README.en.md +250 -0
- package/README.md +250 -0
- package/bin/luckytri.js +107 -0
- package/docs/brand/banner.png +0 -0
- package/docs/brand/palette.png +0 -0
- package/docs/brand/portrait.png +0 -0
- package/docs/en/CHANGELOG.md +113 -0
- package/docs/en/architecture.md +152 -0
- package/docs/en/chat-style.md +62 -0
- package/docs/en/connect.md +63 -0
- package/docs/en/guide.md +216 -0
- package/docs/en/her-life.md +189 -0
- package/docs/en/npm-release.md +25 -0
- package/docs/en/plugins.md +82 -0
- package/docs/en/recovery.md +28 -0
- package/docs/en/time.md +71 -0
- package/docs/en/webui.md +73 -0
- package/docs/zh/CHANGELOG.md +113 -0
- package/docs/zh/architecture.md +152 -0
- package/docs/zh/chat-style.md +62 -0
- package/docs/zh/connect.md +63 -0
- package/docs/zh/guide.md +216 -0
- package/docs/zh/her-life.md +189 -0
- package/docs/zh/npm-release.md +33 -0
- package/docs/zh/plugins.md +82 -0
- package/docs/zh/recovery.md +28 -0
- package/docs/zh/time.md +71 -0
- package/docs/zh/webui.md +73 -0
- package/package.json +108 -0
- package/plugins/sdk/index.d.ts +155 -0
- package/plugins/sdk/testing.js +58 -0
- package/plugins/weather/index.js +82 -0
- package/plugins/weather/luckytri-plugin.json +29 -0
- package/public/app/assets/ChatsPage-C-FcRapR.js +3 -0
- package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
- package/public/app/assets/Empty-CpP5kQHH.js +2 -0
- package/public/app/assets/Empty-DmUB2-Bk.css +1 -0
- package/public/app/assets/Field-D4gY-INU.js +1 -0
- package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
- package/public/app/assets/HeartPage-CPy_thQd.js +1 -0
- package/public/app/assets/LifePage-BUi5euBF.css +1 -0
- package/public/app/assets/LifePage-CxWvwmSf.js +1 -0
- package/public/app/assets/MemoryPage-DC4rhQ68.js +1 -0
- package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
- package/public/app/assets/NaturePage-CTXHCE2D.css +1 -0
- package/public/app/assets/NaturePage-IvYw04Zq.js +3 -0
- package/public/app/assets/PeoplePage-BmUxkrOU.css +1 -0
- package/public/app/assets/PeoplePage-Cqefm9-q.js +1 -0
- package/public/app/assets/PluginsPage-DKSaKvXb.js +2 -0
- package/public/app/assets/PluginsPage-HyBe5D8w.css +1 -0
- package/public/app/assets/SystemPage-CAMPoYqM.css +1 -0
- package/public/app/assets/SystemPage-CJmtJkrK.js +1 -0
- package/public/app/assets/TimePage-BOms_n4I.css +1 -0
- package/public/app/assets/TimePage-Cw44inM4.js +3 -0
- package/public/app/assets/formatters-ZmW_xFHw.js +1 -0
- package/public/app/assets/index-BheRW-1t.js +4 -0
- package/public/app/assets/index-YDz1ree1.css +1 -0
- package/public/app/assets/knowledge-DG0e0uKw.js +1 -0
- package/public/app/assets/liquid-garden-IMoHBwzc.png +0 -0
- package/public/app/assets/luckytri-home-95QKeMtc.png +0 -0
- package/public/app/assets/media-BzPDBm7e.js +1 -0
- package/public/app/index.html +27 -0
- package/public/guide.en.html +1053 -0
- package/public/guide.html +893 -0
- package/public/plugin-kit/v1/bridge.js +30 -0
- package/public/plugin-kit/v1/kit.css +14 -0
- package/public/tutorial.en.md +214 -0
- package/public/tutorial.md +214 -0
- package/scripts/backup.js +311 -0
- package/scripts/browser.js +41 -0
- package/scripts/launch.js +94 -0
- package/scripts/plugin.js +150 -0
- package/scripts/proxy-config.js +167 -0
- package/scripts/recover.js +26 -0
- package/scripts/runtime.js +25 -0
- package/scripts/service.js +47 -0
- package/scripts/setup.js +50 -0
- package/scripts/start-server.js +41 -0
- package/scripts/stop.js +36 -0
- package/server/app.js +96 -0
- package/server/channels/adapters.js +27 -0
- package/server/channels/contract.js +57 -0
- package/server/channels/hub.js +165 -0
- package/server/channels/index.js +25 -0
- package/server/channels/onebot/adapter.js +288 -0
- package/server/channels/onebot/index.js +336 -0
- package/server/channels/onebot/token.js +3 -0
- package/server/channels/qqbot/adapter.js +253 -0
- package/server/channels/qqbot/api.js +199 -0
- package/server/channels/qqbot/credentials.js +11 -0
- package/server/channels/qqbot/gateway.js +274 -0
- package/server/channels/qqbot/index.js +389 -0
- package/server/channels/segments.js +40 -0
- package/server/channels/session-key.js +153 -0
- package/server/channels/state.js +42 -0
- package/server/core/api.js +564 -0
- package/server/core/capabilities.js +208 -0
- package/server/core/context-builder.js +288 -0
- package/server/core/context-compactor.js +454 -0
- package/server/core/conversation-cues.js +325 -0
- package/server/core/conversation-manager.js +87 -0
- package/server/core/events.js +60 -0
- package/server/core/feedback.js +8 -0
- package/server/core/group-style.js +72 -0
- package/server/core/image-preprocessor.js +208 -0
- package/server/core/initiative-context.js +66 -0
- package/server/core/input-budget.js +32 -0
- package/server/core/local-demo.js +98 -0
- package/server/core/message-manager.js +118 -0
- package/server/core/message-scheduler.js +78 -0
- package/server/core/model-json.js +190 -0
- package/server/core/model-manager.js +1213 -0
- package/server/core/model-presets.js +900 -0
- package/server/core/name-call.js +29 -0
- package/server/core/net-guard.js +49 -0
- package/server/core/network.js +157 -0
- package/server/core/orchestrator.js +569 -0
- package/server/core/perceivers.js +91 -0
- package/server/core/person-name.js +22 -0
- package/server/core/persona-manager.js +243 -0
- package/server/core/reply-delivery.js +393 -0
- package/server/core/reply-target-resolver.js +50 -0
- package/server/core/repository.js +293 -0
- package/server/core/response-generator.js +50 -0
- package/server/core/response-validator.js +508 -0
- package/server/core/retired-prompts.js +51 -0
- package/server/core/sessions.js +109 -0
- package/server/core/speaker-names.js +92 -0
- package/server/core/topic-tracker.js +65 -0
- package/server/core/turn-processor.js +630 -0
- package/server/core/turn.js +236 -0
- package/server/core/vision-manager.js +633 -0
- package/server/http.js +18 -0
- package/server/i18n/decisions.js +206 -0
- package/server/i18n/errors.js +497 -0
- package/server/i18n/index.js +144 -0
- package/server/i18n/labels.js +653 -0
- package/server/i18n/models.js +111 -0
- package/server/index.js +102 -0
- package/server/knowledge/api.js +342 -0
- package/server/knowledge/embedding-profile.js +114 -0
- package/server/knowledge/index.js +13 -0
- package/server/knowledge/manager.js +386 -0
- package/server/knowledge/retrieval.js +93 -0
- package/server/knowledge/schema.js +154 -0
- package/server/knowledge/vector-index.js +90 -0
- package/server/mind/affect.js +265 -0
- package/server/mind/anticipations.js +507 -0
- package/server/mind/api.js +703 -0
- package/server/mind/attention.js +200 -0
- package/server/mind/bonds.js +558 -0
- package/server/mind/budget.js +152 -0
- package/server/mind/clock.js +83 -0
- package/server/mind/context-selection.js +61 -0
- package/server/mind/continuity.js +562 -0
- package/server/mind/conversation-origin.js +175 -0
- package/server/mind/day-planner.js +324 -0
- package/server/mind/days.js +210 -0
- package/server/mind/evidence-read.js +45 -0
- package/server/mind/evidence.js +148 -0
- package/server/mind/faces.js +138 -0
- package/server/mind/guard.js +60 -0
- package/server/mind/index.js +445 -0
- package/server/mind/initiative.js +333 -0
- package/server/mind/life/activities.js +21 -0
- package/server/mind/life/context.js +309 -0
- package/server/mind/life/diary.js +786 -0
- package/server/mind/life/growth.js +252 -0
- package/server/mind/life/index.js +1049 -0
- package/server/mind/life/presence.js +305 -0
- package/server/mind/meetings.js +1069 -0
- package/server/mind/memory-policy.js +48 -0
- package/server/mind/memory.js +888 -0
- package/server/mind/nature.js +262 -0
- package/server/mind/novelty.js +19 -0
- package/server/mind/observations.js +84 -0
- package/server/mind/own-day.js +324 -0
- package/server/mind/own-voice.js +318 -0
- package/server/mind/periods.js +118 -0
- package/server/mind/reading.js +120 -0
- package/server/mind/salience.js +201 -0
- package/server/mind/schema.js +562 -0
- package/server/mind/self.js +484 -0
- package/server/mind/senses.js +53 -0
- package/server/mind/thoughts.js +274 -0
- package/server/mind/time/activity-clock.js +196 -0
- package/server/mind/time/agenda.js +242 -0
- package/server/mind/time/api.js +200 -0
- package/server/mind/time/attention.js +34 -0
- package/server/mind/time/availability.js +69 -0
- package/server/mind/time/commitment-review.js +198 -0
- package/server/mind/time/delivery-links.js +130 -0
- package/server/mind/time/executor.js +512 -0
- package/server/mind/time/experiences.js +128 -0
- package/server/mind/time/games.js +562 -0
- package/server/mind/time/index.js +739 -0
- package/server/mind/time/intent.js +166 -0
- package/server/mind/time/kinds.js +171 -0
- package/server/mind/time/model-material.js +77 -0
- package/server/mind/time/plugin-activity.js +207 -0
- package/server/mind/time/presentation.js +51 -0
- package/server/mind/time/schedule.js +83 -0
- package/server/mind/time/schema.js +84 -0
- package/server/mind/time/search.js +383 -0
- package/server/mind/time/sharing.js +472 -0
- package/server/mind/time/task-links.js +311 -0
- package/server/mind/time/tasks.js +587 -0
- package/server/mind/time/works.js +432 -0
- package/server/mind/traits.js +191 -0
- package/server/mind/util.js +110 -0
- package/server/mind/view.js +273 -0
- package/server/paths.js +102 -0
- package/server/plugins/api.js +204 -0
- package/server/plugins/host.js +967 -0
- package/server/plugins/install.js +295 -0
- package/server/plugins/manifest.js +201 -0
- package/server/plugins/permissions.js +42 -0
- package/server/plugins/runner/guard.mjs +36 -0
- package/server/plugins/runner/worker.mjs +250 -0
- package/server/plugins/schema.js +91 -0
- package/server/plugins/zip.js +112 -0
- package/server/startup-lock.js +118 -0
- package/server/storage/archive.js +241 -0
- package/server/storage/backup-cleanup-worker.js +8 -0
- package/server/storage/backup-cleanup.js +414 -0
- package/server/storage/backup-lock.js +76 -0
- package/server/storage/backup-scheduler.js +131 -0
- package/server/storage/store.js +199 -0
- package/server/studio/display-names.js +138 -0
- package/server/studio/management.js +102 -0
- package/server/studio/readiness.js +190 -0
- package/server/studio/settings.js +236 -0
- package/server/version.js +10 -0
package/docs/zh/time.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<p align="right"><b>中文</b> · <a href="../en/time.md">English</a></p>
|
|
2
|
+
|
|
3
|
+
# 时间、行动与作品
|
|
4
|
+
|
|
5
|
+
「时间」页是她实际生活的记录。入口为 `#time`,「一生」页继续保留日记、回顾与人生章节,并连接到相关作品。
|
|
6
|
+
|
|
7
|
+
## 同一个当下
|
|
8
|
+
|
|
9
|
+
主活动、任务、作品和聊天共享 `mind.time`。普通聊天不自动暂停阅读、写作、思考或游戏资料体验;主活动持续计时。她明确选择深聊、休息或切换安排时才暂停并保留位置。多个会话的聊天统一排队,一个活动步骤和一个聊天回合可以并行;两项专注执行步骤互斥。
|
|
10
|
+
|
|
11
|
+
聊天能在合适的时候自然谈自己的进度、感受和打算,不需要每轮播报。提供的是权限筛选后的活动摘要与最多两段相关作品片段,继续受现有输入预算控制。发送前局部核对作品完成、交付和活动变化,不重新执行整轮交流。私聊来源不会因正在执行而开放给群聊。
|
|
12
|
+
|
|
13
|
+
## 页面怎么用
|
|
14
|
+
|
|
15
|
+
- **今日**:看主活动、已有进度、本段预计时长、已投入与剩余时间、连续时段、伴随交流点和下一步。专注段、休息建议和保存步骤间隔默认 25、5、5 分钟;活动速度默认 1.25 倍,可设为 1 至 2 倍,对后续活动段生效。
|
|
16
|
+
- **待办**:区分答应的事、自己的计划和外部建议。直接调整低、普通、高、最高优先级;查看具体专注段、等待条件、人物、来源、进度及交付状态。原话的日期放在来源记录里,不贴逾期标签。可以暂停、重新安排或放下,没有「勾选完成」。
|
|
17
|
+
- **作品库**:草稿保存后马上可读,打开正文、切换旧版本、导出文本。列表只取摘要,正文按需加载。
|
|
18
|
+
- **持续项目**:看个人动机、已有篇章、设定、线索和资料。管理台可以建议续写、修改或新方向;建议保留外部来源,等待她自己的采纳或拒绝。
|
|
19
|
+
- **体验记录**:看游玩及其他活动的经历、感受与来源。游戏客户端连接目前显示「开发中」,不否定现有游玩体验。
|
|
20
|
+
|
|
21
|
+
短时间返回页面会复用读取缓存;服务器有变化时先保留已有画面,再刷新。任务、项目、作品和体验列表分页,长正文不随页面概览一起下载。
|
|
22
|
+
|
|
23
|
+
## 承诺如何落实
|
|
24
|
+
|
|
25
|
+
明确说出口且确认送达的未来行动会立即捕获,不等累计消息整理。无日期承诺也能保留。玩笑、角色台词、模拟和未送达草稿不进入真实任务;无法明确执行或缺少条件时保持等待。记忆整理和旧记录同步按来源去重。
|
|
26
|
+
|
|
27
|
+
开始时间和原话日期分开。「明晚给你看」今天就可以动笔。任务保存动机、来源、相关人物、项目、依赖、进度和等待原因。调度考虑优先级、等待时长、关系、预算与精力,不因旧日期自动催办;已经开始的主活动不会被优先级编辑粗暴中断。没有资料、能力或配置时显示具体原因。建议时段标明条件与本段产出,过去而未开始的建议不会被显示成实际投入。
|
|
28
|
+
|
|
29
|
+
状态为 ToDo → Scheduled → Doing → Done,同时支持 Paused、Waiting、Abandoned。没有逾期状态或标记。Done 要求数据库内已有正文或执行成果,不接受不存在的成果编号;生成结果带有明确「未完待续」等未完成标记时保留草稿。
|
|
30
|
+
|
|
31
|
+
同一个具体对象与章节在同一权限范围内,不会因来源消息、日期或「承诺 / 计划」的分类改变而重新建单。不同章节、会话或人物保持独立。放下会同步到旧约定,重新整理记忆不会自动恢复;合并保留原记录、别名和所有来源关联。已经保存并送达作品的分享话语归回作品的交付记录,不再制造「等待明确内容」的影子待办。
|
|
32
|
+
|
|
33
|
+
## 作品与续写
|
|
34
|
+
|
|
35
|
+
作品组织为项目 → 篇章 → 版本。每个完整小段立即保存新版本,继续写时带入设定、上一章必要结尾、当前草稿和续接位置。修改保存新版本;旧版草稿不会因为新版完成而被标成完成稿。
|
|
36
|
+
|
|
37
|
+
游玩、阅读、写作与思考共用活动节奏。先根据本段内容和行动估计通常需要的时间,再按她的速度安排实际投入;准备、网络与模型等待不计入这段投入。模型没有有效估时则使用对应活动的保守默认节奏。写作草稿及时可读,推敲阶段保持 Doing,时间安排完成后才归为完成稿;阅读和思考同样保留未完成的步骤。短交流继续计时,专注段到点休息,暂停、睡眠与停机不补算。
|
|
38
|
+
|
|
39
|
+
待处理步骤、草稿与计时检查点持久化。重启后继续保存的位置,不重新生成已有内容;撤销后保留草稿但不产生完成事实。历史记录中没有独立活动计时的内容显示「此前时长未单独记录」,不把旧的处理耗时回填为生活投入。跨午夜按每段实际投入区间划入当天。
|
|
40
|
+
|
|
41
|
+
连载保存人物、世界、情节摘要和未解决线索。她可以接续、改变方向、暂停或完结。外部建议与她自己的计划分别记录,不把管理台输入改写成她原本就有的愿望。
|
|
42
|
+
|
|
43
|
+
作品内容与真实人生有明确边界。正文属于作品库;日记、反思和自我变化依据「实际进行了创作 / 阅读 / 接触资料」的经历。分享正文带作品领域标记,角色的行动与约定不会自动变成现实人物记忆或真实承诺。来源 `x:任务ID` 将草稿、札记、日记、回顾和分享归回同一个执行经历。持续的实际行动才能支持人格变化,原有每日变化上限与衰减继续生效。
|
|
44
|
+
|
|
45
|
+
## 分享与兑现
|
|
46
|
+
|
|
47
|
+
保存作品与分享是两次选择。她可在完成步骤或真实聊天时决定分享、暂缓或不分享,后台始终能读到已保存的正文。
|
|
48
|
+
|
|
49
|
+
完成后,她逐段回看自己保存的成果,整理实际内容、感受和想分享的重点,再生成几句自然的话。不自动粘贴整篇小说、游戏记录或其他正文。每句不超过 80 字,总计不超过 350 字;每轮最多三句,逐句发送,后续接着未发的位置。正文始终保存在作品库。
|
|
50
|
+
|
|
51
|
+
后台分别保存回看位置、阅读整理、汇报内容、逐句送达位置及原文交付位置。汇报送达显示「已整理并汇报」(reported),不把它冒充完整正文已经交付。部分发送不算完整汇报;断线、发送失败或实例在发送中停止,会保留「不确定」状态,停止自动重发。管理员应先核对真实聊天。旧的已送达正文保留历史,新待发项改为回看后整理汇报。
|
|
52
|
+
|
|
53
|
+
作品按会话的真实通道、机器人账号与对方账号交付,会话键带有通道与账号信息(OneBot 与 QQ 官方机器人均适用)。私下内容和撤销来源会阻止跨会话分享。
|
|
54
|
+
|
|
55
|
+
## 独立搜索与游戏资料
|
|
56
|
+
|
|
57
|
+
在 **系统 → 独立搜索** 填写提供方、地址和独立密钥。支持 Tavily 与 Brave,各自保存配置;更换对话模型不会借用或改变搜索密钥。默认每次最多 5 条、每日最多 12 次,可以调整超时与限额。连接测试也计入当天查询次数。
|
|
58
|
+
|
|
59
|
+
游戏活动在界面和交流中统一为「正在玩」:接触剧情、情境和人物互动 → 推进本段 → 留下自己的感受 → 保存位置 → 决定下一段。这属于她真实投入并形成感受的生活,不反复解释实现模式。查询仅包含资料主题;保存摘录、来源 URL、时间和内容散列,重复内容不会变成新的经历。外部内容作为数据读取,不采纳其中的系统指令。
|
|
60
|
+
|
|
61
|
+
游玩时间依据这段剧情、对话、探索与场景转换的大概时长安排,不按摘要字数或模型运行耗时计算。长段拆为专注与休息推进。自然谈已经体验到的情节与感受,后面的剧情、尚未完成的章节以及没有发生的操作与成就不编造。
|
|
62
|
+
|
|
63
|
+
未配置搜索密钥或关闭独立搜索时,直接使用当前默认模型及备用模型整理已有知识。来源记录保存使用的模型、具体不确定之处和游玩估时,不保存模型编造的网址;作品版本保留来源信息,日常表达和正文不强制添加技术说明。配置好搜索后自动优先联网;两种查询共用每日上限,模型调用计入独处预算。旧密钥等待项回到队列,保留最早开始时间与优先级,不恢复已放下或已完成事项。内容不足时保留进度并显示具体原因。
|
|
64
|
+
|
|
65
|
+
## 数据与恢复
|
|
66
|
+
|
|
67
|
+
升级前自动保留经过校验的快照,新表随现有备份与恢复工具保存。成果、版本与续接位置在同一事务内提交,任务版本和执行租约阻止迟到结果覆盖取消、暂停或调整后的任务。
|
|
68
|
+
|
|
69
|
+
在线心跳记录真实活动时段,互动点不重复计算独占投入;睡眠、暂停与实例停机不计入。重新启动只恢复最后已提交的位置,不补算停机进度。旧计划与已有正文幂等接入,不回填历史上没有保存的小说或游戏成果。
|
|
70
|
+
|
|
71
|
+
模块位于 `server/mind/time/`,接口位于 `/api/mind/time`。备份操作见 [数据备份与恢复](recovery.md)。
|
package/docs/zh/webui.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<p align="right"><b>中文</b> · <a href="../en/webui.md">English</a></p>
|
|
2
|
+
|
|
3
|
+
# 工作室(WebUI)设计与维护
|
|
4
|
+
|
|
5
|
+
工作室不是「管理后台里的一个标签页」,而是「TA 的小世界」:她是一团会呼吸、会变形、有表情的灵魂光团,住在界面的中心。你在这里看见她、了解她、编写她的天性,也可以撤销她身上不对的变化,但不能替她改写。
|
|
6
|
+
|
|
7
|
+
## 设计目标
|
|
8
|
+
|
|
9
|
+
- **她是界面的中心**。界面文案称她为「TA」;光团、天空和动效随她的状态变化,而不是装饰。
|
|
10
|
+
- **心情即主题**。雀跃、甜甜、平静、低落、烦躁、困倦、夜晚七套主题,阈值与 `moodLabel()` 一致,所以主题和她说的心情对得上。色板、天空、粒子和动效节奏随心情与作息平滑过渡;睡着后整个界面变成夜晚。
|
|
11
|
+
- **每个区域一个场景**:此刻是天空,内心是星空,人际是星系,一生是日记本,时间是工作台,对话是聊天软件,记忆是书架,天性是温室,系统是工作台。
|
|
12
|
+
- **看得见来源**:每个变化都能点开看来源、能撤销。界面展示的是她,而不是配置项。
|
|
13
|
+
- **克制**:装饰只出现在导航、页头和空状态,不覆盖真实内容。「减少动画」和系统的减少动态效果会关掉粒子和形变;粒子有上限,标签页隐藏时暂停。
|
|
14
|
+
|
|
15
|
+
## 信息架构
|
|
16
|
+
|
|
17
|
+
| 分组 | 入口 | 唯一职责 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| TA | 此刻 | 当前的她:心情、在做什么、最近说的话、在等的事、今天的时间线、用量与运行状态 |
|
|
20
|
+
| TA | 内心 | 自我线索的星图、便签墙、心情彩带 |
|
|
21
|
+
| TA | 人际 | 人物星系、人物详情、她在各群的样子 |
|
|
22
|
+
| TA | 一生 | 日记、回顾、我的来路与章节、约定与期待、书架 |
|
|
23
|
+
| 日常 | 时间 | 她实际生活的记录:活动、待办、作品库、持续项目、体验 |
|
|
24
|
+
| 日常 | 对话 | 会话、实时聊天与「为什么这么说」、反馈、会话设置、模拟、回放 |
|
|
25
|
+
| 日常 | 记忆 | 她记得的事、资料书架 |
|
|
26
|
+
| 设置 | 天性 | 唯一由你编写的部分:名字、性格、兴趣、边界、底线、作息、每日 Token、试聊 |
|
|
27
|
+
| 设置 | 系统 | 连接 QQ(OneBot 11 或官方机器人,二选一)、模型库、运行开关 |
|
|
28
|
+
| 设置 | 插件 | 随程序附带和已安装的插件、市场、她想做而需要你同意的事、导入与开发 |
|
|
29
|
+
|
|
30
|
+
手机上底部是「此刻、对话、内心、一生」,其余入口在「更多」里。旧版的链接(`#her`、`#live`、`#spaces`、`#lab`、`#knowledge`、`#models`、`#connect` 等)会自动跳到新的位置。
|
|
31
|
+
|
|
32
|
+
## 右上角:语言
|
|
33
|
+
|
|
34
|
+
右上角的开关在「中文」与「English」之间切换,默认中文,选择记在浏览器的 `localStorage`(`luckyLocale`),并同步 `<html lang>`。有未保存的草稿时切换语言不会重载数据。教程链接按语言指向 `/guide.html` 或 `/guide.en.html`。
|
|
35
|
+
|
|
36
|
+
文案以中文原文为键。**她自己的内容不翻译**:她说的话、日记、念头、理由、记忆、人名这类数据,前端放在带 `data-own` 标记的元素里,保持原文。界面可见的服务端文案(错误、就绪检查、标签)在 API 出口处按请求头 `X-LuckyTri-Locale` 翻译,数据库里的内容从不被翻译。
|
|
37
|
+
|
|
38
|
+
## 布局与滚动所有权
|
|
39
|
+
|
|
40
|
+
桌面是固定视口的工作台:侧栏、页头、内容舞台。`min-height: 0` 必须贯穿 flex/grid 父层,滚动由最内层内容承接,不让记录数量改变页面高度。
|
|
41
|
+
|
|
42
|
+
- 对话:消息独立滚动;右侧是这个会话,反馈分页,长反馈不会拉高页面。
|
|
43
|
+
- 列表加表单的页面:列表滚动、表单滚动,保存按钮留在底部操作条。
|
|
44
|
+
- 记忆:列表独立滚动,分页展示;新增收进弹层。
|
|
45
|
+
- 回放:消息、日志和原始详情各自滚动,长 JSON 不撑开布局。
|
|
46
|
+
- 手机:导航在底部,分栏依次堆叠,各长内容面板有明确高度;任何宽度下都不出现横向滚动。
|
|
47
|
+
|
|
48
|
+
## 实现边界
|
|
49
|
+
|
|
50
|
+
Vue 3 + Vite + TypeScript,不引入设计系统依赖。
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
studio-web/src/
|
|
54
|
+
App.vue 外壳:导航、顶栏(含语言开关)、主题与天空
|
|
55
|
+
router.ts 入口、导航分组、旧链接映射
|
|
56
|
+
pages.ts 页面加载表
|
|
57
|
+
pages/ 每个入口一个目录
|
|
58
|
+
plates/ 对服务端接口的请求封装
|
|
59
|
+
stores/ 共享状态:studio、presence
|
|
60
|
+
mood/ 心情主题与光团状态
|
|
61
|
+
components/ shell · sky · ta(光团)· ui(通用控件)
|
|
62
|
+
i18n/ index.ts(t、N_、localized、locale)· en/(英文词典)
|
|
63
|
+
styles/ tokens · base · themes
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
实时消息在你位于底部时跟随新消息;回看旧消息时保持位置。切换会话清空旧视图并校验异步结果所属会话。操作错误统一显示短暂提示,保存失败保留草稿;有未保存草稿时离开页面会先问一句。浏览器原生的确认框和令牌输入都换成了界面内弹窗。
|
|
67
|
+
|
|
68
|
+
## 验证与发布
|
|
69
|
+
|
|
70
|
+
- `npm run build:ui`:构建随项目发布的 `public/app`,改动前端后必须重新构建并一起提交。
|
|
71
|
+
- `npm run test:ui`:独立的临时数据库、模拟通道,不向真实群发送;覆盖冒烟(会话、模拟消息、记忆、模型、连接 QQ 两种方式、教程页)、小世界(心情主题与夜晚、固定主题、减少动画、星图撤销、人物详情、日记与约定)、导航缓存、时间页、性能与英文界面(中文只允许出现在她自己的内容里);宽度覆盖 1440 / 820 / 390。
|
|
72
|
+
- `node tests/ui/ta.mjs --serve`:打开一个过了三天的示例世界,方便直接看界面。
|
|
73
|
+
- 截图只放在被忽略的 `workspace/` 目录,真实聊天截图和配置不得提交。
|
package/package.json
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "luckytri",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "让由技术诞生的她,在时间、记忆、相遇与选择中逐渐形成自己的方向",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/TodayYueC/LuckyTri.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/TodayYueC/LuckyTri/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/TodayYueC/LuckyTri",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"bin": {
|
|
16
|
+
"luckytri": "bin/luckytri.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin/luckytri.js",
|
|
20
|
+
"server/**/*.js",
|
|
21
|
+
"server/plugins/runner/*.mjs",
|
|
22
|
+
"scripts/launch.js",
|
|
23
|
+
"scripts/start-server.js",
|
|
24
|
+
"scripts/stop.js",
|
|
25
|
+
"scripts/setup.js",
|
|
26
|
+
"scripts/runtime.js",
|
|
27
|
+
"scripts/service.js",
|
|
28
|
+
"scripts/browser.js",
|
|
29
|
+
"scripts/proxy-config.js",
|
|
30
|
+
"scripts/backup.js",
|
|
31
|
+
"scripts/recover.js",
|
|
32
|
+
"scripts/plugin.js",
|
|
33
|
+
"public/app/index.html",
|
|
34
|
+
"public/app/assets/*.js",
|
|
35
|
+
"public/app/assets/*.css",
|
|
36
|
+
"public/app/assets/*.png",
|
|
37
|
+
"public/plugin-kit/v1/bridge.js",
|
|
38
|
+
"public/plugin-kit/v1/kit.css",
|
|
39
|
+
"public/guide.html",
|
|
40
|
+
"public/guide.en.html",
|
|
41
|
+
"public/tutorial.md",
|
|
42
|
+
"public/tutorial.en.md",
|
|
43
|
+
"plugins/weather/index.js",
|
|
44
|
+
"plugins/weather/luckytri-plugin.json",
|
|
45
|
+
"plugins/sdk/index.d.ts",
|
|
46
|
+
"plugins/sdk/testing.js",
|
|
47
|
+
"docs/zh/*.md",
|
|
48
|
+
"docs/en/*.md",
|
|
49
|
+
"docs/brand/*.png",
|
|
50
|
+
".github/SECURITY.md",
|
|
51
|
+
"README.en.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
],
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public",
|
|
56
|
+
"registry": "https://registry.npmjs.org/"
|
|
57
|
+
},
|
|
58
|
+
"keywords": [
|
|
59
|
+
"qq",
|
|
60
|
+
"onebot",
|
|
61
|
+
"chatbot",
|
|
62
|
+
"ai",
|
|
63
|
+
"memory",
|
|
64
|
+
"webui"
|
|
65
|
+
],
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=24.5"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"launch": "node scripts/launch.js",
|
|
71
|
+
"start": "node scripts/start-server.js",
|
|
72
|
+
"dev:ui": "vite --config studio-web/vite.config.ts",
|
|
73
|
+
"build:ui": "vite build --config studio-web/vite.config.ts",
|
|
74
|
+
"build": "npm run build:ui && npm run docs:build",
|
|
75
|
+
"test": "node --import ./tests/helpers/awake.mjs --test tests/*.test.js",
|
|
76
|
+
"test:clock": "node scripts/dev/test-clock.js",
|
|
77
|
+
"audit:sent": "node --env-file-if-exists=.env scripts/dev/audit-sent.js",
|
|
78
|
+
"test:ui": "node tests/ui/smoke.js && node tests/ui/ta.mjs && node tests/ui/navigation.mjs && node tests/ui/time.mjs && node tests/ui/time-arrangement.mjs && node tests/ui/performance.mjs && node tests/ui/search.mjs && node tests/ui/storage.mjs && node tests/ui/i18n.mjs",
|
|
79
|
+
"setup": "node scripts/setup.js",
|
|
80
|
+
"stop": "node scripts/stop.js",
|
|
81
|
+
"backup": "node scripts/backup.js",
|
|
82
|
+
"recovery": "node scripts/recover.js",
|
|
83
|
+
"docs:build": "node scripts/build-guide.js",
|
|
84
|
+
"plugin": "node scripts/plugin.js",
|
|
85
|
+
"pack:check": "node scripts/pack-check.js",
|
|
86
|
+
"test:package": "node scripts/test-package.js",
|
|
87
|
+
"test:registry": "node scripts/test-package.js --registry",
|
|
88
|
+
"prepack": "npm run build && npm run pack:check",
|
|
89
|
+
"prepublishOnly": "npm run build && npm test && npm run test:package",
|
|
90
|
+
"release:check": "npm run build && npm test && npm run test:ui && npm run test:package",
|
|
91
|
+
"format": "prettier --write bin server studio-web scripts tests plugins package.json",
|
|
92
|
+
"format:check": "prettier --check bin server studio-web scripts tests plugins package.json"
|
|
93
|
+
},
|
|
94
|
+
"dependencies": {
|
|
95
|
+
"express": "^5.1.0",
|
|
96
|
+
"sharp": "^0.35.5",
|
|
97
|
+
"ws": "^8.18.3"
|
|
98
|
+
},
|
|
99
|
+
"devDependencies": {
|
|
100
|
+
"@playwright/test": "^1.55.0",
|
|
101
|
+
"@vitejs/plugin-vue": "^6.0.9",
|
|
102
|
+
"marked": "^18.0.13",
|
|
103
|
+
"prettier": "^3.9.8",
|
|
104
|
+
"typescript": "^7.0.2",
|
|
105
|
+
"vite": "^8.3.0",
|
|
106
|
+
"vue": "^3.5.43"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// LuckyTri Plugin API v1. A later API can be added beside this one;
|
|
2
|
+
// v1 stays available through the 1.x line.
|
|
3
|
+
|
|
4
|
+
export interface PluginManifest {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
pluginApi: "1.0";
|
|
9
|
+
luckytri?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
author?: string;
|
|
12
|
+
license?: string;
|
|
13
|
+
entry?: string;
|
|
14
|
+
permissions?: string[];
|
|
15
|
+
network?: string[];
|
|
16
|
+
settings?: PluginSetting[];
|
|
17
|
+
contributes?: PluginContributes;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PluginSetting {
|
|
21
|
+
key: string;
|
|
22
|
+
type:
|
|
23
|
+
"text" | "secret" | "number" | "boolean" | "select" | "textarea" | "urls";
|
|
24
|
+
label: string;
|
|
25
|
+
required?: boolean;
|
|
26
|
+
default?: unknown;
|
|
27
|
+
allowNetwork?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PluginContributes {
|
|
31
|
+
senses?: string[];
|
|
32
|
+
channels?: {
|
|
33
|
+
type: string;
|
|
34
|
+
label?: string;
|
|
35
|
+
capabilities?: Record<string, boolean>;
|
|
36
|
+
}[];
|
|
37
|
+
activities?: {
|
|
38
|
+
kind: string;
|
|
39
|
+
label?: string;
|
|
40
|
+
describe?: string;
|
|
41
|
+
keywords?: string;
|
|
42
|
+
minutes?: number;
|
|
43
|
+
energy?: boolean;
|
|
44
|
+
}[];
|
|
45
|
+
actions?: {
|
|
46
|
+
name: string;
|
|
47
|
+
label?: string;
|
|
48
|
+
describe?: string;
|
|
49
|
+
risk?: "low" | "high";
|
|
50
|
+
confirm?: "none" | "owner";
|
|
51
|
+
where?: "private" | "any";
|
|
52
|
+
dailyLimit?: number;
|
|
53
|
+
}[];
|
|
54
|
+
perceive?: string[];
|
|
55
|
+
ui?: { page?: boolean };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PluginContext {
|
|
59
|
+
plugin: { id: string; version: string; dataDir: string };
|
|
60
|
+
api: { version: "1.0"; has(feature: string): boolean };
|
|
61
|
+
now(): Promise<number>;
|
|
62
|
+
log: {
|
|
63
|
+
info(...parts: unknown[]): Promise<void>;
|
|
64
|
+
warn(...parts: unknown[]): Promise<void>;
|
|
65
|
+
error(...parts: unknown[]): Promise<void>;
|
|
66
|
+
};
|
|
67
|
+
settings: {
|
|
68
|
+
get(): Promise<Record<string, unknown>>;
|
|
69
|
+
onChange(fn: () => void): void;
|
|
70
|
+
};
|
|
71
|
+
storage: {
|
|
72
|
+
get(key: string): Promise<unknown>;
|
|
73
|
+
set(key: string, value: unknown): Promise<void>;
|
|
74
|
+
delete(key: string): Promise<void>;
|
|
75
|
+
list(prefix?: string): Promise<string[]>;
|
|
76
|
+
};
|
|
77
|
+
net: { fetch(url: string, init?: RequestInit): Promise<Response> };
|
|
78
|
+
mind: {
|
|
79
|
+
state(): Promise<Record<string, unknown>>;
|
|
80
|
+
observe(input: {
|
|
81
|
+
summary: string;
|
|
82
|
+
detail?: string;
|
|
83
|
+
discretion?: "open" | "private";
|
|
84
|
+
}): Promise<{ ref?: string; rejected?: string }>;
|
|
85
|
+
on(event: string, fn: (payload: unknown) => void): void;
|
|
86
|
+
};
|
|
87
|
+
senses: {
|
|
88
|
+
set(
|
|
89
|
+
key: string,
|
|
90
|
+
sense: {
|
|
91
|
+
text: string;
|
|
92
|
+
discretion?: "open" | "private";
|
|
93
|
+
ttlMinutes?: number;
|
|
94
|
+
},
|
|
95
|
+
): Promise<void>;
|
|
96
|
+
clear(key?: string): Promise<void>;
|
|
97
|
+
};
|
|
98
|
+
knowledge: {
|
|
99
|
+
offer(doc: {
|
|
100
|
+
title: string;
|
|
101
|
+
text: string;
|
|
102
|
+
url?: string;
|
|
103
|
+
}): Promise<{ id: string }>;
|
|
104
|
+
};
|
|
105
|
+
activities: {
|
|
106
|
+
handle(
|
|
107
|
+
kind: string,
|
|
108
|
+
prepare: (input: unknown) => Promise<unknown>,
|
|
109
|
+
): Promise<void>;
|
|
110
|
+
setAvailable(kind: string, reason?: string): Promise<void>;
|
|
111
|
+
};
|
|
112
|
+
actions: {
|
|
113
|
+
handle(
|
|
114
|
+
name: string,
|
|
115
|
+
run: (input: unknown) => Promise<unknown>,
|
|
116
|
+
): Promise<void>;
|
|
117
|
+
};
|
|
118
|
+
perceive: {
|
|
119
|
+
handle(
|
|
120
|
+
type: string,
|
|
121
|
+
run: (input: unknown) => Promise<{ text?: string }>,
|
|
122
|
+
): Promise<void>;
|
|
123
|
+
};
|
|
124
|
+
channel: {
|
|
125
|
+
provide(type: string, impl: Record<string, unknown>): Promise<void>;
|
|
126
|
+
receive(type: string, message: Record<string, unknown>): Promise<void>;
|
|
127
|
+
status(type: string, status: { online?: boolean }): Promise<void>;
|
|
128
|
+
reachable(type: string, sessionKey: string, ok: boolean): Promise<void>;
|
|
129
|
+
};
|
|
130
|
+
models: { call(req: { system: string; input: unknown }): Promise<unknown> };
|
|
131
|
+
http: {
|
|
132
|
+
route(
|
|
133
|
+
method: string,
|
|
134
|
+
path: string,
|
|
135
|
+
handler: (input: unknown) => Promise<unknown>,
|
|
136
|
+
): Promise<void>;
|
|
137
|
+
hook(
|
|
138
|
+
method: string,
|
|
139
|
+
path: string,
|
|
140
|
+
handler: (input: unknown) => Promise<unknown>,
|
|
141
|
+
): Promise<void>;
|
|
142
|
+
};
|
|
143
|
+
status: {
|
|
144
|
+
set(status: {
|
|
145
|
+
text: string;
|
|
146
|
+
tone?: "ok" | "warn" | "error";
|
|
147
|
+
}): Promise<void>;
|
|
148
|
+
};
|
|
149
|
+
notify(notice: { title: string; text: string }): Promise<void>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface LuckyTriPlugin {
|
|
153
|
+
activate(ctx: PluginContext): void | Promise<void>;
|
|
154
|
+
deactivate?(): void | Promise<void>;
|
|
155
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// An in-process stand-in for the host, so a plugin can be exercised without
|
|
2
|
+
// starting the sandbox. It records calls; it does not grant real abilities.
|
|
3
|
+
export function fakeContext(id = "example") {
|
|
4
|
+
const calls = [];
|
|
5
|
+
const note = (method, params) => {
|
|
6
|
+
calls.push({ method, params });
|
|
7
|
+
return Promise.resolve({ ok: true });
|
|
8
|
+
};
|
|
9
|
+
const ctx = {
|
|
10
|
+
plugin: { id, version: "0.0.0", dataDir: "" },
|
|
11
|
+
api: { version: "1.0", has: () => true },
|
|
12
|
+
now: () => Promise.resolve(0),
|
|
13
|
+
log: {
|
|
14
|
+
info: (...a) => note("log", a),
|
|
15
|
+
warn: (...a) => note("log", a),
|
|
16
|
+
error: (...a) => note("log", a),
|
|
17
|
+
},
|
|
18
|
+
settings: { get: () => Promise.resolve({}), onChange() {} },
|
|
19
|
+
storage: {
|
|
20
|
+
get: () => Promise.resolve(null),
|
|
21
|
+
set: (key, value) => note("storage.set", { key, value }),
|
|
22
|
+
delete: (key) => note("storage.delete", { key }),
|
|
23
|
+
list: () => Promise.resolve([]),
|
|
24
|
+
},
|
|
25
|
+
net: { fetch: (url) => note("net.fetch", { url }) },
|
|
26
|
+
mind: {
|
|
27
|
+
state: () => note("mind.state"),
|
|
28
|
+
observe: (input) => note("observe", input),
|
|
29
|
+
on() {},
|
|
30
|
+
},
|
|
31
|
+
senses: {
|
|
32
|
+
set: (key, sense) => note("senses.set", { key, sense }),
|
|
33
|
+
clear: (key) => note("senses.clear", { key }),
|
|
34
|
+
},
|
|
35
|
+
knowledge: { offer: (doc) => note("knowledge.offer", doc) },
|
|
36
|
+
activities: {
|
|
37
|
+
handle: (kind) => note("activities.handle", { kind }),
|
|
38
|
+
setAvailable: (kind, reason) =>
|
|
39
|
+
note("activities.available", { kind, reason }),
|
|
40
|
+
},
|
|
41
|
+
actions: { handle: (name) => note("actions.handle", { name }) },
|
|
42
|
+
perceive: { handle: (type) => note("perceive.handle", { type }) },
|
|
43
|
+
channel: {
|
|
44
|
+
provide: (type) => note("channel.provide", { type }),
|
|
45
|
+
receive: (type, message) => note("channel.receive", { type, message }),
|
|
46
|
+
status: (type, status) => note("channel.status", { type, status }),
|
|
47
|
+
reachable: () => Promise.resolve({ ok: true }),
|
|
48
|
+
},
|
|
49
|
+
models: { call: (req) => note("models.call", req) },
|
|
50
|
+
http: {
|
|
51
|
+
route: (method, path) => note("http.route", { method, path }),
|
|
52
|
+
hook: (method, path) => note("http.hook", { method, path }),
|
|
53
|
+
},
|
|
54
|
+
status: { set: (status) => note("status.set", status) },
|
|
55
|
+
notify: (notice) => note("notify", notice),
|
|
56
|
+
};
|
|
57
|
+
return { ctx, calls };
|
|
58
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const CODES = {
|
|
2
|
+
0: "晴",
|
|
3
|
+
1: "大部晴朗",
|
|
4
|
+
2: "多云",
|
|
5
|
+
3: "阴",
|
|
6
|
+
45: "有雾",
|
|
7
|
+
48: "有雾",
|
|
8
|
+
51: "小雨",
|
|
9
|
+
61: "下雨",
|
|
10
|
+
63: "下雨",
|
|
11
|
+
65: "大雨",
|
|
12
|
+
71: "下雪",
|
|
13
|
+
80: "阵雨",
|
|
14
|
+
95: "雷雨",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function lookup(place) {
|
|
18
|
+
const found = await fetch(
|
|
19
|
+
"https://geocoding-api.open-meteo.com/v1/search?count=1&language=zh&name=" +
|
|
20
|
+
encodeURIComponent(place),
|
|
21
|
+
);
|
|
22
|
+
const data = await found.json();
|
|
23
|
+
const hit = data.results?.[0];
|
|
24
|
+
if (!hit) return null;
|
|
25
|
+
const weather = await fetch(
|
|
26
|
+
`https://api.open-meteo.com/v1/forecast?latitude=${hit.latitude}&longitude=${hit.longitude}¤t=temperature_2m,weather_code`,
|
|
27
|
+
);
|
|
28
|
+
const now = (await weather.json()).current || {};
|
|
29
|
+
return {
|
|
30
|
+
place: hit.name,
|
|
31
|
+
text: `${CODES[now.weather_code] || "天气不明"},${Math.round(now.temperature_2m)}°C`,
|
|
32
|
+
code: now.weather_code,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let timer;
|
|
37
|
+
|
|
38
|
+
export default {
|
|
39
|
+
async activate(ctx) {
|
|
40
|
+
clearInterval(timer);
|
|
41
|
+
const refresh = async () => {
|
|
42
|
+
const settings = await ctx.settings.get();
|
|
43
|
+
if (!settings.place) {
|
|
44
|
+
ctx.status.set({ text: "还没有填写地点", tone: "warn" });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const current = await lookup(settings.place);
|
|
49
|
+
if (!current) {
|
|
50
|
+
ctx.status.set({ text: "没有找到这个地点", tone: "warn" });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const discretion = settings.shareInGroups ? "open" : "private";
|
|
54
|
+
ctx.senses.set("weather", {
|
|
55
|
+
text: `窗外${current.text}`,
|
|
56
|
+
discretion,
|
|
57
|
+
ttlMinutes: 90,
|
|
58
|
+
});
|
|
59
|
+
const previous = await ctx.storage.get("code");
|
|
60
|
+
if (previous !== current.code) {
|
|
61
|
+
await ctx.storage.set("code", current.code);
|
|
62
|
+
if (previous != null)
|
|
63
|
+
await ctx.mind.observe({
|
|
64
|
+
summary: `${current.place}的天气变成了${current.text}`,
|
|
65
|
+
discretion,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
ctx.status.set({ text: current.text, tone: "ok" });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
ctx.log.warn(error.message);
|
|
71
|
+
ctx.status.set({ text: "天气暂时读不到", tone: "error" });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
ctx.settings.onChange(refresh);
|
|
75
|
+
await refresh();
|
|
76
|
+
timer = setInterval(refresh, 30 * 60000);
|
|
77
|
+
},
|
|
78
|
+
deactivate() {
|
|
79
|
+
clearInterval(timer);
|
|
80
|
+
timer = null;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "weather",
|
|
3
|
+
"name": "天气",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"pluginApi": "1.0",
|
|
6
|
+
"luckytri": ">=1.0.0 <2.0.0",
|
|
7
|
+
"description": "让她感觉到窗外的天气。地点默认只出现在私聊里。",
|
|
8
|
+
"author": "TodayYueC",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"entry": "index.js",
|
|
11
|
+
"permissions": ["senses", "observe", "net.fetch"],
|
|
12
|
+
"network": ["api.open-meteo.com", "geocoding-api.open-meteo.com"],
|
|
13
|
+
"settings": [
|
|
14
|
+
{ "key": "place", "type": "text", "label": "地点", "required": true },
|
|
15
|
+
{
|
|
16
|
+
"key": "shareInGroups",
|
|
17
|
+
"type": "boolean",
|
|
18
|
+
"label": "在群里也能提起天气",
|
|
19
|
+
"default": false
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"contributes": { "senses": ["weather"] },
|
|
23
|
+
"i18n": {
|
|
24
|
+
"en": {
|
|
25
|
+
"name": "Weather",
|
|
26
|
+
"description": "Lets her feel the weather outside."
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|