Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/background-task #12

Merged
merged 8 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,377 changes: 1,377 additions & 0 deletions .komment/00000.json

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions .komment/komment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"meta": {
"version": "1",
"updated_at": "2024-07-26T08:41:23.869Z",
"created_at": "2024-07-26T08:41:34.521Z",
"pipelines": [
"7142b758-d47d-470d-92bb-effdfadd3bab"
]
},
"lookup": [
[
"web/src/app/not-found.tsx",
"web/src/components/ui/sonner.tsx",
"web/src/components/ui/toast.tsx",
"web/src/components/ui/toaster.tsx",
"web/src/components/ui/use-toast.ts",
"server/domain/service/RememberService.py",
"server/domain/service/TriggerService.py",
"web/capacitor.config.ts",
"web/public/background.js",
"web/src/app/integration/page.tsx",
"web/src/app/login/page.tsx",
"web/src/app/page.tsx",
"web/src/app/setting/page.tsx",
"web/src/services/NotificationService.ts"
]
]
}
114 changes: 107 additions & 7 deletions server/domain/service/RememberService.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import os
from openai import OpenAI
from mem0 import Memory
from dotenv import load_dotenv
import google.generativeai as genai
from google.generativeai.types import HarmCategory, HarmBlockThreshold
from google.ai.generativelanguage_v1beta.types import content
import re
import json

load_dotenv()

Expand All @@ -14,13 +16,63 @@
"top_p": 0.95,
"top_k": 64,
"max_output_tokens": 8192,
"response_mime_type": "text/plain",
"response_schema": content.Schema(
type=content.Type.OBJECT,
properties={
'message': content.Schema(
type=content.Type.STRING,
),
'action': content.Schema(
type=content.Type.ARRAY,
items=content.Schema(
type=content.Type.OBJECT,
properties={
'type': content.Schema(
type=content.Type.STRING,
),
'title': content.Schema(
type=content.Type.STRING,
),
'body': content.Schema(
type=content.Type.STRING,
),
'at': content.Schema(
type=content.Type.STRING,
),
},
),
),
},
),
"response_mime_type": "application/json",

}


# Initialize the Gemini client

class RememberService:
"""
Provides a conversational AI assistant named Bear that helps users remember
events and things they discuss. It uses a generative model to respond to user
questions, stores user conversations, and allows searching of stored memories.

Attributes:
memory (Memory): Initialized from a configuration dictionary. It provides
functionality to store and retrieve user memories, allowing the service
to keep track of previous conversations and events.
client (genaiGenerativeModel): Initialized with a specific model name,
generation configuration, and safety settings. It represents a generative
AI model that can generate human-like responses to user input.
app_id (str): Assigned the value "remembear-app". Its purpose is not
explicitly stated, but it could be used to identify the application
or service within a larger system.
messages (List[Dict[str,List[str]]]): Used to store chat messages between
a user and Bear, with each message having a role (user or model) and
one or more parts representing the content of the message.

"""

def __init__(self):
"""
Initialize the PersonalAI with memory configuration and Gemini client.
Expand Down Expand Up @@ -57,13 +109,18 @@ def __init__(self):
},
system_instruction=
"""
Return json format.
You are a personal assistant named Bear who will communicate using the Indonesian language.
You will help the user remember every event and thing they talk about.
Do not display memory explicitly to the user.
Do not display memory that is not requested.
Include this string if user wants to create notification or reminder. Assume the notification will appear on the present date,
example:
'{"message":"Oke siap! akan bear jadwalkan!",
"action": [{"type":"notification", "title":"Pengingat Meeting", "body":"Jangan lupa meeting sekarang di Tangerang bersama Pak Rudi","at":"2024-07-26T09:00:00"}]}'
""",
)
self.app_id = "app-1"
self.app_id = "remembear-app"
self.messages = [
{
"role": "user",
Expand Down Expand Up @@ -100,10 +157,10 @@ async def ask(self, question, user_id=1):

response = self.client.generate_content([
"input: " + prompt,
# "output: ",
])

answer = response.text
parsed_response = json.loads(response.text)

self.messages.append({
"role": "user",
"parts": [
Expand All @@ -114,21 +171,64 @@ async def ask(self, question, user_id=1):
self.messages.append({
"role": "model",
"parts": [
answer
parsed_response.get('message', "")
],
})

# Store the question in memory
self.memory.add(question, user_id=user_id)

return dict({
"message": answer
"message": parsed_response.get('message', ""),
"action": parsed_response.get('action', []),
"code": 200
})

def get_memories(self, user_id):
"""
Retrieves all memories associated with a specified user ID from the memory
storage and returns them as a list of text strings, filtered from the
original data structure.

Args:
user_id (str): Required, as it determines which user's memories are
retrieved from the memory storage.

Returns:
List[str]: A list of memory text strings, each string representing a
single memory retrieved from the database using the provided user ID.

"""
memories = self.memory.get_all(user_id=user_id)
return [m['text'] for m in memories]

def search_memories(self, query, user_id):
"""
Searches for memories related to a given query and user ID using an internal
memory storage component (self.memory). It returns a list of text strings
representing the matching memories.

Args:
query (str | List[str]): Used to search for matching memories based
on its content, either as a single string or a list of strings.
user_id (int | str): Used to filter the results returned by the
`self.memory.search(query)` method, which searches for memories
related to the specified user ID.

Returns:
List[str]: A list of strings. Each string represents the text content
of a memory retrieved from the database using the query and user_id provided.

"""
memories = self.memory.search(query, user_id=user_id)
return [m['text'] for m in memories]


def extract_message(text):
"""Extracts the message from a text string using regular expressions."""
pattern = r'^([^\{]+)'
match = re.match(pattern, text)
if match:
return match.group(1).strip()
else:
return text # Return None if no match is found
69 changes: 68 additions & 1 deletion server/domain/service/TriggerService.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,69 @@
from datetime import datetime
import json
import re


class TriggerService:
pass
"""
Parses a given message and extracts specific data from it. It checks if the
message contains an "action" key with a list of actions, then processes each
action to extract its type, title, body, and timestamp (if present). The
processed actions are returned as a list.

"""

def __init__(self):
return

@staticmethod
def parse(raw_message: str) -> list:
"""
:param raw_message: String
:return: tuple
"""
try:
data = json.loads(extract_json(raw_message))
actions = data.get("action", [])
parsed_actions = []
for action in actions:
if action.get("type") == "notification":
parsed_action = {
"type": action.get("type"),
"title": action.get("title"),
"body": action.get("body"),
"at": datetime.strptime(action.get("at"), "%Y-%m-%dT%H:%M:%S") if action.get("at") else None
}
parsed_actions.append(parsed_action)
return parsed_actions
except (json.JSONDecodeError, ValueError) as e:
print(f"Error parsing message: {e}")
return None


def extract_json(input_str):
# Use regular expressions to find the JSON part
"""
Searches for a JSON string within an input string, decodes it if valid, and
returns the decoded JSON as a formatted string with indentation. If the input
contains invalid JSON or no JSON at all, it returns an error message accordingly.

Args:
input_str (str): Expected to be a string containing potentially a JSON
object within it, possibly with other text or characters around it.

Returns:
str|InvalidJSONdetected|NoJSONfoundintheinput: A) a formatted JSON string
if the input contains valid JSON, b) "Invalid JSON detected." if the JSON
is invalid, or c) "No JSON found in the input." if no JSON is present.

"""
json_match = re.search(r'\{.*\}', input_str)
if json_match:
json_str = json_match.group(0)
try:
json_obj = json.loads(json_str) # This will parse the JSON to ensure it's valid
return json.dumps(json_obj, indent=4) # Convert back to a formatted JSON string
except json.JSONDecodeError:
return "Invalid JSON detected."
else:
return "No JSON found in the input."
66 changes: 66 additions & 0 deletions web/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
apply plugin: 'com.android.application'

buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.google.gms:google-services:4.4.2'
}
}

android {
namespace "com.remembear.app"
compileSdk rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.remembear.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
dirs '../../node_modules/@capacitor/background-runner/android/src/main/libs', 'libs'
}
}

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}

apply from: 'capacitor.build.gradle'

try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
15 changes: 12 additions & 3 deletions web/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:allowBackup="true"
Expand Down Expand Up @@ -31,12 +32,20 @@
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
android:resource="@xml/file_paths" />
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@mipmap/notification_icon" />
</provider>
</application>

<!-- Permissions -->

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.INTERNET"
tools:ignore="ManifestOrder" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
</manifest>
Binary file added web/android/app/src/main/res/raw/nyah.wav
Binary file not shown.
Loading