Skip to content

Commit

Permalink
feat: ChatGPT like memories (#113)
Browse files Browse the repository at this point in the history
Fixes #110
  • Loading branch information
lollipopkit authored Jul 25, 2024
1 parent 40386e7 commit 83cf2dd
Show file tree
Hide file tree
Showing 20 changed files with 184 additions and 57 deletions.
14 changes: 14 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: lollipopkit # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ A third-party GPT Client for OpenAI API.

<!-- Badges-->
<p align="center">
<a href="https://ko-fi.com/lollipopkit"><img alt="donation" width="130" src="https://storage.ko-fi.com/cdn/brandasset/kofi_button_red.png"></a>
<img alt="lang" src="https://img.shields.io/badge/lang-dart-pink">
<img alt="license" src="https://img.shields.io/badge/license-GPLv3-pink">
</p>
Expand All @@ -18,6 +19,7 @@ Please refrain from using it in production environments or for critical data.


## 🪄 Features
- (🥳 New) Ask GPT to add Memories.
- (🥳 New) Api supports viewing the content of Http links, (developing) running JS scripts locally. [Video](https://cdn.lolli.tech/gptbox/screenshot/tools.mp4)
- Restore from [ChatGPT Next Web backup](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web) / [OpenAI exported file](https://chatgpt.com).
- Text / Image / Audio chat.
Expand Down Expand Up @@ -68,10 +70,17 @@ After you read the above, you can:
- Any positive contribution is welcome.
- [l10n guide](https://blog.lolli.tech/faq/) can be found in my blog.


## 💡 My other apps
- [Server Box](https://github.com/lollipopkit/flutter_server_box) - Server status & tools.
- [More](https://github.com/lollipopkit) - Tools & etc.


## 🎉 Donation
- App will keep free, but if you think my work is helpful, you can [donate](https://ko-fi.com/lollipopkit) me a cup of coffee.
- Thanks to all the donators, your support is my motivation.


## 📝 License
`GPL v3 lollipopkit`

11 changes: 9 additions & 2 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

<!-- Badges-->
<p align="center">
<img alt="lang" src="https://img.shields.io/badge/lang-dart-pink">
<img alt="license" src="https://img.shields.io/badge/license-GPLv3-pink">
<a href="https://ko-fi.com/lollipopkit"><img alt="捐赠" width="130" src="https://storage.ko-fi.com/cdn/brandasset/kofi_button_red.png"></a>
<img alt="语言" src="https://img.shields.io/badge/lang-dart-pink">
<img alt="证书" src="https://img.shields.io/badge/license-GPLv3-pink">
</p>

## 😣 注意
Expand All @@ -18,6 +19,7 @@


## 🪄 特性
- (🥳 新) 让 GPT 记住某些事
- (🥳 新) Api 支持查看 Http 链接的内容、(开发中)本地运行 JS 脚本。[视频](https://cdn.lolli.tech/gptbox/screenshot/tools.mp4)
-[ChatGPT Next Web 备份](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web) / [OpenAI导出文件](https://chatgpt.com) 恢复
- 文本 / 图片 / 音频聊天
Expand Down Expand Up @@ -77,5 +79,10 @@ Linux & Windows | [Github](https://github.com/lollipopkit/flutter_gpt_box/releas
- [更多](https://github.com/lollipopkit) - 工具 & etc.


## 🎉 捐赠
- 应用将保持免费,但是如果你认为我的工作对你有帮助,欢迎[捐赠](https://ko-fi.com/lollipopkit)
- 感谢所有捐赠者,你们的支持是我的动力。


## 📝 协议
`GPL v3 lollipopkit`
6 changes: 4 additions & 2 deletions lib/core/util/tool_func/func/http.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
part of '../tool.dart';

final class _HttpReq extends ToolFunc {
const _HttpReq()
final class TfHttpReq extends ToolFunc {
static const instance = TfHttpReq._();

const TfHttpReq._()
: super(
name: 'httpReq',
description: '''
Expand Down
4 changes: 2 additions & 2 deletions lib/core/util/tool_func/func/iface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ part of '../tool.dart';

abstract final class ToolFunc {
final String name;
final String? description;
final String description;
final _Map parametersSchema;

const ToolFunc({
required this.name,
this.description,
required this.description,
required this.parametersSchema,
});

Expand Down
43 changes: 43 additions & 0 deletions lib/core/util/tool_func/func/memory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
part of '../tool.dart';

final class TfMemory extends ToolFunc {
static const instance = TfMemory._();

const TfMemory._()
: super(
name: 'memory',
description: '''
Memorise the input and add what memorised to the prompt.
If users want to memorise something, you(AI models) should call this function.''',
parametersSchema: const {
'type': 'object',
'properties': {
'memory': {
'type': 'string',
'description': 'What to memorise, will be persisted in db.',
},
},
},
);

@override
String get l10nName => l10n.memory;

@override
String help(_CallResp call, _Map args) {
return l10n.memoryTip(args['memory'] as String? ?? '<?>');
}

@override
Future<_Ret> run(_CallResp call, _Map args, OnToolLog log) async {
final memory = args['memory'] as String?;
if (memory == null) {
return [ChatContent.text(l10n.empty)];
}
final prop = Stores.tool.memories;
final memories = prop.fetch();
prop.put(memories..add(memory));
await Future.delayed(Durations.medium1);
return [ChatContent.text(l10n.memoryAdded(memory))];
}
}
17 changes: 10 additions & 7 deletions lib/core/util/tool_func/tool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ part 'type.dart';
part 'func/iface.dart';
part 'func/http.dart';
part 'func/js.dart';
part 'func/memory.dart';

abstract final class OpenAIFuncCalls {
static const internalTools = [
_HttpReq(),
TfHttpReq.instance,
TfMemory.instance,
//_RunJS(),
];

static List<OpenAIToolModel> get tools {
final tools = <OpenAIToolModel>[];
if (!Stores.tool.enabled.fetch()) return tools;
final enabledTools = Stores.tool.enabledTools.fetch();
for (final tool in internalTools) {
if (enabledTools.contains(tool.name)) {
Expand All @@ -37,17 +40,17 @@ abstract final class OpenAIFuncCalls {
ToolConfirm askConfirm,
OnToolLog onToolLog,
) async {
final tool = tools.firstWhere((t) => t.type == resp.type);
switch (tool.type) {
switch (resp.type) {
case 'function':
final fn = tool.function;
final targetName = resp.function.name;
final func =
internalTools.firstWhereOrNull((e) => e.name == targetName);
if (func == null) throw 'Unknown function $targetName';
final args = await _parseMap(resp.function.arguments);
final func = internalTools.firstWhereOrNull((e) => e.name == fn.name);
if (func == null) throw 'Unknown function ${fn.name}';
if (!await askConfirm(func, func.help(resp, args))) return null;
return await func.run(resp, args, onToolLog);
default:
throw 'Unknown tool type ${tool.type}';
throw 'Unknown tool type ${resp.type}';
}
}
}
5 changes: 2 additions & 3 deletions lib/data/res/openai.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,12 @@ abstract final class OpenAICfg {
}

static RegExp? _modelsUseToolReExp;
static bool canUseTool(String model) {
static bool isToolCompatible({String? model}) {
model ??= current.model;
if (model.isEmpty) return false;
return _modelsUseToolReExp?.hasMatch(model) ?? false;
}

static bool get canUseToolNow => canUseTool(current.model);

static Future<bool> updateModels({bool force = false}) async {
if (current.url.startsWith('https://api.openai.com') &&
current.key.isEmpty) {
Expand Down
5 changes: 5 additions & 0 deletions lib/data/store/tool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ final class ToolStore extends PersistentStore {
/// Tools that are permitted to be used by the user.
/// A dialog will be shown if the tool has not been permitted.
late final permittedTools = property('permittedTools', <String>[]);

/// Memories that are saved by the user.
/// It will be added to prompt when sending a chat req.
/// {id: memory}
late final memories = property('memories', <String>[]);
}
16 changes: 9 additions & 7 deletions lib/intro.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ final class _IntroPage extends StatelessWidget {
final padTop = cons.maxHeight * .12;
final pages_ = pages.map((e) => e(context, padTop)).toList();
return IntroPage(
pages: pages_,
onDone: (ctx) {
Stores.setting.introVer.put(Build.build);
Navigator.of(ctx).pushReplacement(
MaterialPageRoute(builder: (_) => const HomePage()),
);
},
args: IntroPageArgs(
pages: pages_,
onDone: (ctx) {
Stores.setting.introVer.put(Build.build);
Navigator.of(ctx).pushReplacement(
MaterialPageRoute(builder: (_) => const HomePage()),
);
},
),
);
},
);
Expand Down
6 changes: 5 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@
"languageName": "English",
"license": "License",
"licenseMenuItem": "Open-source licenses",
"list": "List",
"manual": "Manual",
"memory": "Memory",
"memoryAdded": "Memory added: {str}",
"memoryTip": "Memorise [{txt}]?",
"message": "Message",
"minute": "min",
"model": "Model",
Expand Down Expand Up @@ -125,14 +129,14 @@
"stt": "stt",
"success": "Success 🏅 ~",
"sureRestoreFmt": "Are you sure to restore Backup({time})?",
"switcher": "Switch",
"syncConflict": "Sync conflict: can't turn on {a} and {b} at the same time.",
"system": "System",
"text": "Text",
"themeColorSeed": "Theme color seed",
"themeMode": "Theme mode",
"thirdParty": "Third Party",
"tool": "Tool",
"toolAvailability": "Only on gpt-4o, gpt-4t & etc.",
"toolConfirmFmt": "Is it permitted to use the tool {tool} ?",
"toolFinishTip": "Tools invocation completed",
"toolHttpReqHelp": "It will fetch data from network. In this time, it will communicate with {host}.",
Expand Down
6 changes: 5 additions & 1 deletion lib/l10n/app_zh.arb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@
"languageName": "简体中文",
"license": "许可证",
"licenseMenuItem": "开放源代码许可",
"list": "列表",
"manual": "手动",
"memory": "记忆",
"memoryAdded": "记忆已添加: {str}",
"memoryTip": "记住 [{txt}]?",
"message": "消息",
"minute": "分钟",
"model": "模型",
Expand Down Expand Up @@ -125,14 +129,14 @@
"stt": "语音转文字",
"success": "成功 🏅~",
"sureRestoreFmt": "确定恢复备份({time})?",
"switcher": "开关",
"syncConflict": "冲突:不能同时开启 {a} 和 {b}",
"system": "系统",
"text": "文字",
"themeColorSeed": "主题颜色种子",
"themeMode": "主题模式",
"thirdParty": "第三方",
"tool": "工具",
"toolAvailability": "仅 gpt-4o、gpt-4t 等支持",
"toolConfirmFmt": "是否同意使用工具 {tool} ?",
"toolFinishTip": "工具调用完成",
"toolHttpReqHelp": "将会与从网络获取数据,本次将会联系 {host}",
Expand Down
5 changes: 5 additions & 0 deletions lib/view/page/backup/impl/gpt_next.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,10 @@ void _onTapRestoreGPTNext(BuildContext context) async {
);
});

if (chats == null) {
context.showSnackBar('null');
return;
}

_askConfirm(context, chats);
}
5 changes: 5 additions & 0 deletions lib/view/page/backup/impl/openai.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,10 @@ void _onTapRestoreOpenAI(BuildContext context) async {
);
});

if (chats == null) {
context.showSnackBar('null');
return;
}

_askConfirm(context, chats);
}
2 changes: 1 addition & 1 deletion lib/view/page/home/history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class _HistoryPageState extends State<_HistoryPage>
children: [
IconBtn(
onTap: () => _onTapRenameChat(chatId, context),
icon: Icons.abc,
icon: BoxIcons.bx_rename,
),
IconBtn(
onTap: () => _onTapDeleteChat(chatId, context),
Expand Down
6 changes: 3 additions & 3 deletions lib/view/page/home/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ class _HomePageState extends State<HomePage>

@override
Widget build(BuildContext context) {
return const ExitConfirm(
onPop: ExitConfirm.exitApp,
child: Scaffold(
return ExitConfirm(
onPop: (_) => ExitConfirm.exitApp(),
child: const Scaffold(
drawer: _Drawer(),
appBar: _CustomAppBar(),
body: _Body(),
Expand Down
9 changes: 5 additions & 4 deletions lib/view/page/home/req.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ Iterable<OpenAIChatCompletionChoiceMessageModel> _historyCarried(
final ignoreCtxCons = workingChat.settings?.ignoreContextConstraint == true;
if (ignoreCtxCons) return workingChat.items.map((e) => e.toOpenAI);

final prompt = config.prompt.isNotEmpty
final promptStr = config.prompt + Stores.tool.memories.fetch().join('\n');
final prompt = promptStr.isNotEmpty
? ChatHistoryItem.single(
role: ChatRole.system,
raw: config.prompt,
raw: promptStr,
).toOpenAI
: null;

Expand Down Expand Up @@ -134,7 +135,7 @@ Future<void> _onCreateText(
_loadingChatIds.add(chatId);
_autoScroll(chatId);

final useTools = Stores.tool.enabled.fetch() && OpenAICfg.canUseToolNow;
final toolCompatible = OpenAICfg.isToolCompatible();

// #104
final singleChatScopeUseTools = workingChat.settings?.useTools != false;
Expand All @@ -145,7 +146,7 @@ Future<void> _onCreateText(

/// TODO: after switching to http img url, remove this condition.
/// To save tokens, we don't use tools for image prompt
if (useTools && !hasImg && singleChatScopeUseTools && !isToolsEmpty) {
if (toolCompatible && !hasImg && singleChatScopeUseTools && !isToolsEmpty) {
final toolReply = ChatHistoryItem.single(role: ChatRole.tool, raw: '');
workingChat.items.add(toolReply);
_loadingChatIds.add(toolReply.id);
Expand Down
Loading

0 comments on commit 83cf2dd

Please sign in to comment.