Auto-update: Tue Jun 25 19:28:02 PDT 2024
This commit is contained in:
parent
7c43a6813a
commit
261b2a785b
9 changed files with 92 additions and 231 deletions
3
Extras/webclipper/README.md
Normal file
3
Extras/webclipper/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
This is designed to work with UserScripts and similar browser extensions. Fill in the domain/URL where your sijapi instance is exposed (http://localhost:4444 is fine for the same device, but consider using a reverse proxy to extend to your mobile devices).
|
||||||
|
|
||||||
|
And fill in your GLOBAL_API_KEY that you chose when configuring sijapi.
|
41
Extras/webclipper/archivist.js
Normal file
41
Extras/webclipper/archivist.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// ==UserScript==
|
||||||
|
// @name Archivist
|
||||||
|
// @version 0.1
|
||||||
|
// @description archivist userscript posts to sij.ai/clip
|
||||||
|
// @author sij.ai
|
||||||
|
// @match *://*/*
|
||||||
|
// @grant GM_xmlhttpRequest
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
var data = new URLSearchParams({
|
||||||
|
title: document.title,
|
||||||
|
url: window.location.href,
|
||||||
|
referrer: document.referrer || '',
|
||||||
|
width: window.innerWidth ? window.innerWidth.toString() : '',
|
||||||
|
encoding: document.characterSet,
|
||||||
|
source: document.documentElement.outerHTML
|
||||||
|
});
|
||||||
|
|
||||||
|
GM_xmlhttpRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: 'https://!{!{ YOUR DOMAIN HERE }!}!/clip?api_key=!{!{ YOUR API KEY HERE }!}!',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'Authorization': 'bearer !{!{ GLOBAL_API_KEY HERE }!}!'
|
||||||
|
},
|
||||||
|
data: data.toString(),
|
||||||
|
onload: function(response) {
|
||||||
|
console.log('Data sent to server');
|
||||||
|
},
|
||||||
|
onerror: function(error) {
|
||||||
|
console.error('Error sending data:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
})();
|
|
@ -65,6 +65,7 @@ class AutoResponder(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
style: str
|
style: str
|
||||||
context: str
|
context: str
|
||||||
|
ollama_model: str = "llama3"
|
||||||
whitelist: List[str]
|
whitelist: List[str]
|
||||||
blacklist: List[str]
|
blacklist: List[str]
|
||||||
image_prompt: Optional[str] = None
|
image_prompt: Optional[str] = None
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
import os
|
|
||||||
import yaml
|
|
||||||
from time import sleep
|
|
||||||
from pathlib import Path
|
|
||||||
import ipaddress
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
def __init__(self, yaml_file):
|
|
||||||
with open(yaml_file, 'r') as file:
|
|
||||||
self.data = yaml.safe_load(file)
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if name in self.data:
|
|
||||||
value = self.data[name]
|
|
||||||
if isinstance(value, dict):
|
|
||||||
return ConfigSection(value)
|
|
||||||
return value
|
|
||||||
raise AttributeError(f"Config has no attribute '{name}'")
|
|
||||||
|
|
||||||
class ConfigSection:
|
|
||||||
def __init__(self, data):
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if name in self.data:
|
|
||||||
value = self.data[name]
|
|
||||||
if isinstance(value, dict):
|
|
||||||
return ConfigSection(value)
|
|
||||||
return value
|
|
||||||
raise AttributeError(f"ConfigSection has no attribute '{name}'")
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
if name == 'data':
|
|
||||||
super().__setattr__(name, value)
|
|
||||||
else:
|
|
||||||
self.data[name] = value
|
|
||||||
|
|
||||||
# Load the YAML configuration file
|
|
||||||
CFG = Config('.config.yaml')
|
|
||||||
|
|
||||||
# Access existing attributes
|
|
||||||
print(CFG.API.PORT) # Output: localhost
|
|
||||||
|
|
||||||
def load_config():
|
|
||||||
yaml_file = os.path.join(os.path.dirname(__file__), ".config.yaml")
|
|
||||||
|
|
||||||
HOME_DIR = Path.home()
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
CONFIG_DIR = BASE_DIR / "config"
|
|
||||||
ROUTER_DIR = BASE_DIR / "routers"
|
|
||||||
|
|
||||||
DATA_DIR = BASE_DIR / "data"
|
|
||||||
os.makedirs(DATA_DIR, exist_ok=True)
|
|
||||||
|
|
||||||
ALERTS_DIR = DATA_DIR / "alerts"
|
|
||||||
os.makedirs(ALERTS_DIR, exist_ok=True)
|
|
||||||
|
|
||||||
LOGS_DIR = BASE_DIR / "logs"
|
|
||||||
os.makedirs(LOGS_DIR, exist_ok=True)
|
|
||||||
REQUESTS_DIR = LOGS_DIR / "requests"
|
|
||||||
os.makedirs(REQUESTS_DIR, exist_ok=True)
|
|
||||||
REQUESTS_LOG_PATH = LOGS_DIR / "requests.log"
|
|
||||||
DOC_DIR = DATA_DIR / "docs"
|
|
||||||
os.makedirs(DOC_DIR, exist_ok=True)
|
|
||||||
SD_IMAGE_DIR = DATA_DIR / "sd" / "images"
|
|
||||||
os.makedirs(SD_IMAGE_DIR, exist_ok=True)
|
|
||||||
SD_WORKFLOWS_DIR = DATA_DIR / "sd" / "workflows"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(yaml_file, 'r') as file:
|
|
||||||
config_data = yaml.safe_load(file)
|
|
||||||
|
|
||||||
vars = {
|
|
||||||
|
|
||||||
|
|
||||||
"API": {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
config = Config(config_data)
|
|
||||||
return config
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error while loading configuration: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def reload_config():
|
|
||||||
while True:
|
|
||||||
global config
|
|
||||||
with open('config.yaml', 'r') as file:
|
|
||||||
config_data = yaml.safe_load(file)
|
|
||||||
config = Config(config_data)
|
|
||||||
sleep(300) # reload every 5 minutes
|
|
|
@ -18,6 +18,7 @@ accounts:
|
||||||
- name: work
|
- name: work
|
||||||
style: professional
|
style: professional
|
||||||
context: he is currently on leave and will return in late July
|
context: he is currently on leave and will return in late July
|
||||||
|
ollama_model: llama3
|
||||||
whitelist:
|
whitelist:
|
||||||
- '@work.org'
|
- '@work.org'
|
||||||
blacklist:
|
blacklist:
|
||||||
|
@ -56,6 +57,7 @@ accounts:
|
||||||
autoresponders:
|
autoresponders:
|
||||||
- name: ai
|
- name: ai
|
||||||
style: cryptic
|
style: cryptic
|
||||||
|
ollama_model: llama3
|
||||||
context: respond to any inquiries with cryptic and vaguely menacing riddles, esoteric assertions, or obscure references.
|
context: respond to any inquiries with cryptic and vaguely menacing riddles, esoteric assertions, or obscure references.
|
||||||
image_prompt: using visually evocative words, phrases, and sentence fragments, describe an image inspired by the following prompt
|
image_prompt: using visually evocative words, phrases, and sentence fragments, describe an image inspired by the following prompt
|
||||||
whitelist:
|
whitelist:
|
||||||
|
|
4
sijapi/config/named-locations.yaml-example
Normal file
4
sijapi/config/named-locations.yaml-example
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
- name: Echo Valley Ranch
|
||||||
|
latitude: 42.8098216
|
||||||
|
longitude: -123.049396
|
||||||
|
radius: 1.5
|
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"scenes": [
|
|
||||||
{
|
|
||||||
"scene": "default",
|
|
||||||
"triggers": [""],
|
|
||||||
"API_PPrompt": "(Highly-detailed) image of ",
|
|
||||||
"API_SPrompt": "; ((masterpiece)); ((beautiful lighting)), subdued, fine detail, extremely sharp, 8k, insane detail, dynamic lighting, cinematic, best quality, ultra detailed.",
|
|
||||||
"API_NPrompt": "`oil, paint splash, oil effect, dots, paint, freckles, liquid effect, canvas frame, 3d, bad art, asian, illustrated, deformed, blurry, duplicate, bad art, bad anatomy, worst quality, low quality, watermark, FastNegativeV2, (easynegative:0.5), epiCNegative, easynegative, verybadimagenegative_v1.3, nsfw, explicit, topless`",
|
|
||||||
"llm_sys_msg": "You are a helpful AI who assists in refining prompts that will be used to generate highly realistic images. Upon receiving a prompt, you refine it by simplifying and distilling it to its essence, retaining the most visually evocative and distinct elements from what was provided. You may infer some visual details that were not provided in the prompt, so long as they are consistent with the prompt. Always use the most visually descriptive terms possible, and avoid any vague or abstract concepts. Do not include any words or descriptions based on other senses or emotions. Strive to show rather than tell. Space is limited, so be efficient with your words.",
|
|
||||||
"llm_pre_prompt": "Using the most visually descriptive sentence fragments, phrases, and words, distill this scene description to its essence, staying true to what it describes: ",
|
|
||||||
"workflows": [{"workflow": "turbo.json", "size": "1024x768"}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"scene": "portrait",
|
|
||||||
"triggers": [
|
|
||||||
"portrait",
|
|
||||||
"profile",
|
|
||||||
"headshot"
|
|
||||||
],
|
|
||||||
"API_PPrompt": "Highly-detailed portrait photo of ",
|
|
||||||
"API_SPrompt": "; attractive, cute, (((masterpiece))); ((beautiful lighting)), subdued, fine detail, extremely sharp, 8k, insane detail, dynamic lighting, cinematic, best quality, ultra detailed.",
|
|
||||||
"API_NPrompt": "canvas frame, 3d, ((bad art)), illustrated, deformed, blurry, duplicate, bad anatomy, worst quality, low quality, watermark, FastNegativeV2, (easynegative:0.5), epiCNegative, easynegative, verybadimagenegative_v1.3, nsfw, nude",
|
|
||||||
"llm_sys_msg": "You are a helpful AI who assists in refining prompts that will be used to generate highly realistic portrait photos. Upon receiving a prompt, you refine it by simplifying and distilling it to its essence, retaining the most visually evocative and distinct elements from what was provided, focusing in particular on the pictured individual's eyes, pose, and other distinctive features. You may infer some visual details that were not provided in the prompt, so long as they are consistent with the rest of the prompt. Always use the most visually descriptive terms possible, and avoid any vague or abstract concepts. Do not include any words or descriptions based on other senses or emotions. Strive to show rather than tell. Space is limited, so be efficient with your words. Remember that the final product will be a still image, and action verbs are not as helpful as simple descriptions of position, appearance, background, etc.",
|
|
||||||
"llm_pre_prompt": "Using the most visually descriptive sentence fragments, phrases, and words, distill this portrait photo to its essence: ",
|
|
||||||
"workflows": [
|
|
||||||
{
|
|
||||||
"workflow": "selfie.json",
|
|
||||||
"size": "768x1024"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"scene": "wallpaper",
|
|
||||||
"triggers": ["wallpaper"],
|
|
||||||
"API_PPrompt": "Stunning widescreen image of ",
|
|
||||||
"API_SPrompt": ", masterpiece, (subtle:0.7), (nuanced:0.6), best quality, ultra detailed, ultra high resolution, 8k, (documentary:0.3), cinematic, filmic, moody, dynamic lighting, realistic, wallpaper, landscape photography, professional, earthporn, (eliot porter:0.6), (frans lanting:0.4), (daniel kordan:0.6), landscapephotography, ultra detailed, earth tones, moody",
|
|
||||||
"API_NPrompt": "FastNegativeV2, (easynegative:0.5), canvas frame, 3d, ((bad art)), illustrated, deformed, blurry, duplicate, Photoshop, video game, anime, cartoon, fake, tiling, out of frame, bad art, bad anatomy, 3d render, nsfw, worst quality, low quality, text, watermark, (Thomas Kinkade:0.5), sentimental, kitsch, kitschy, twee, commercial, holiday card, modern, futuristic, urban, comic, cartoon, FastNegativeV2, epiCNegative, easynegative, verybadimagenegative_v1.3",
|
|
||||||
"llm_sys_msg": "You are a helpful AI who assists in generating prompts that will be used to generate highly realistic images. Always use the most visually descriptive terms possible, and avoid any vague or abstract concepts. Do not include any words or descriptions based on other senses or emotions. Strive to show rather than tell. Space is limited, so be efficient with your words.",
|
|
||||||
"llm_pre_prompt": "Using a series of words or sentence fragments separated by commas, describe a professional landscape photograph of a striking scene of nature. You can select any place on Earth that a young model from the Pacific Northwest is likely to travel to. Focus on describing the content and composition of the image. Only use words and phrases that are visually descriptive. This model is especially fond of wild and rugged places, mountains. She favors dark muted earth tones, dramatic lighting, and interesting juxtapositions between foreground and background, or center of frame and outer frame areas. Avoid cliche situations; instread strive for nuance and originality in composition and environment.",
|
|
||||||
"workflows": [{"workflow": "landscape.json", "size": "1160x768"}]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"scenes": [
|
|
||||||
{
|
|
||||||
"scene": "default",
|
|
||||||
"triggers": [""],
|
|
||||||
"API_PPrompt": "Highly-detailed image of ",
|
|
||||||
"API_SPrompt": ", masterpiece, subtle, nuanced, best quality, ultra detailed, ultra high resolution, 8k, documentary, american transcendental, cinematic, filmic, moody, dynamic lighting, realistic, wallpaper, landscape photography, professional, earthporn, eliot porter, frans lanting, daniel kordan, landscape photography, ultra detailed, earth tones, moody",
|
|
||||||
"API_NPrompt": "3d, bad art, illustrated, deformed, blurry, duplicate, video game, render, anime, cartoon, fake, tiling, out of frame, bad art, bad anatomy, 3d render, nsfw, worst quality, low quality, text, watermark, Thomas Kinkade, sentimental, kitsch, kitschy, twee, commercial, holiday card, comic, cartoon",
|
|
||||||
"llm_sys_msg": "You are a helpful AI who assists in refining prompts that will be used to generate highly realistic images. Upon receiving a prompt, you refine it by simplifying and distilling it to its essence, retaining the most visually evocative and distinct elements from what was provided. You may infer some visual details that were not provided in the prompt, so long as they are consistent with the prompt. Always use the most visually descriptive terms possible, and avoid any vague or abstract concepts. Do not include any words or descriptions based on other senses or emotions. Strive to show rather than tell. Space is limited, so be efficient with your words.",
|
|
||||||
"llm_pre_prompt": "Using the most visually descriptive sentence fragments, phrases, and words, distill this scene description to its essence, staying true to what it describes: ",
|
|
||||||
"workflows": [{"workflow": "default.json", "size": "1024x768"}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"scene": "wallpaper",
|
|
||||||
"triggers": ["wallpaper"],
|
|
||||||
"API_PPrompt": "Stunning widescreen image of ",
|
|
||||||
"API_SPrompt": ", masterpiece, subtle, nuanced, best quality, ultra detailed, ultra high resolution, 8k, documentary, american transcendental, cinematic, filmic, moody, dynamic lighting, realistic, wallpaper, landscape photography, professional, earthporn, eliot porter, frans lanting, daniel kordan, landscape photography, ultra detailed, earth tones, moody",
|
|
||||||
"API_NPrompt": "3d, bad art, illustrated, deformed, blurry, duplicate, video game, render, anime, cartoon, fake, tiling, out of frame, bad art, bad anatomy, 3d render, nsfw, worst quality, low quality, text, watermark, Thomas Kinkade, sentimental, kitsch, kitschy, twee, commercial, holiday card, comic, cartoon",
|
|
||||||
"llm_sys_msg": "You are a helpful AI who assists in generating prompts that will be used to generate highly realistic images. Always use the most visually descriptive terms possible, and avoid any vague or abstract concepts. Do not include any words or descriptions based on other senses or emotions. Strive to show rather than tell. Space is limited, so be efficient with your words.",
|
|
||||||
"llm_pre_prompt": "Using a series of words or sentence fragments separated by commas, describe a professional landscape photograph of a striking scene of nature. You can select any place on Earth that a young model from the Pacific Northwest is likely to travel to. Focus on describing the content and composition of the image. Only use words and phrases that are visually descriptive. This model is especially fond of wild and rugged places, mountains. She favors dark muted earth tones, dramatic lighting, and interesting juxtapositions between foreground and background, or center of frame and outer frame areas. Avoid cliche situations; instread strive for nuance and originality in composition and environment.",
|
|
||||||
"workflows": [{"workflow": "wallpaper.json", "size": "1024x640"}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"scene": "portrait",
|
|
||||||
"triggers": [
|
|
||||||
"portrait",
|
|
||||||
"profile",
|
|
||||||
"headshot"
|
|
||||||
],
|
|
||||||
"API_PPrompt": "Highly-detailed portrait photo of ",
|
|
||||||
"API_SPrompt": "; attractive, cute, (((masterpiece))); ((beautiful lighting)), subdued, fine detail, extremely sharp, 8k, insane detail, dynamic lighting, cinematic, best quality, ultra detailed.",
|
|
||||||
"API_NPrompt": "canvas frame, 3d, bad art, illustrated, deformed, blurry, duplicate, bad anatomy, worst quality, low quality, watermark, FastNegativeV2, easynegative, epiCNegative, easynegative, verybadimagenegative_v1.3, nsfw, nude",
|
|
||||||
"llm_sys_msg": "You are a helpful AI who assists in refining prompts that will be used to generate highly realistic portrait photos. Upon receiving a prompt, you refine it by simplifying and distilling it to its essence, retaining the most visually evocative and distinct elements from what was provided, focusing in particular on the pictured individual's eyes, pose, and other distinctive features. You may infer some visual details that were not provided in the prompt, so long as they are consistent with the rest of the prompt. Always use the most visually descriptive terms possible, and avoid any vague or abstract concepts. Do not include any words or descriptions based on other senses or emotions. Strive to show rather than tell. Space is limited, so be efficient with your words. Remember that the final product will be a still image, and action verbs are not as helpful as simple descriptions of position, appearance, background, etc.",
|
|
||||||
"llm_pre_prompt": "Using the most visually descriptive sentence fragments, phrases, and words, distill this portrait photo to its essence: ",
|
|
||||||
"workflows": [
|
|
||||||
{
|
|
||||||
"workflow": "selfie.json",
|
|
||||||
"size": "768x1024"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -28,7 +28,7 @@ from sijapi import DEBUG, INFO, WARN, ERR, CRITICAL
|
||||||
from sijapi import PODCAST_DIR, DEFAULT_VOICE, EMAIL_CONFIG
|
from sijapi import PODCAST_DIR, DEFAULT_VOICE, EMAIL_CONFIG
|
||||||
from sijapi.routers import tts, llm, sd, locate
|
from sijapi.routers import tts, llm, sd, locate
|
||||||
from sijapi.utilities import clean_text, assemble_journal_path, extract_text, prefix_lines
|
from sijapi.utilities import clean_text, assemble_journal_path, extract_text, prefix_lines
|
||||||
from sijapi.classes import EmailAccount, IMAPConfig, SMTPConfig, IncomingEmail, EmailContact
|
from sijapi.classes import EmailAccount, IMAPConfig, SMTPConfig, IncomingEmail, EmailContact, AutoResponder
|
||||||
|
|
||||||
|
|
||||||
email = APIRouter(tags=["private"])
|
email = APIRouter(tags=["private"])
|
||||||
|
@ -39,19 +39,19 @@ def load_email_accounts(yaml_path: str) -> List[EmailAccount]:
|
||||||
return [EmailAccount(**account) for account in config['accounts']]
|
return [EmailAccount(**account) for account in config['accounts']]
|
||||||
|
|
||||||
|
|
||||||
def get_account_by_email(email: str) -> Optional[EmailAccount]:
|
def get_account_by_email(this_email: str) -> Optional[EmailAccount]:
|
||||||
email_accounts = load_email_accounts(EMAIL_CONFIG)
|
email_accounts = load_email_accounts(EMAIL_CONFIG)
|
||||||
for account in email_accounts:
|
for account in email_accounts:
|
||||||
if account.imap.username.lower() == email.lower():
|
if account.imap.username.lower() == this_email.lower():
|
||||||
return account
|
return account
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_imap_details(email: str) -> Optional[IMAPConfig]:
|
def get_imap_details(this_email: str) -> Optional[IMAPConfig]:
|
||||||
account = get_account_by_email(email)
|
account = get_account_by_email(this_email)
|
||||||
return account.imap if account else None
|
return account.imap if account else None
|
||||||
|
|
||||||
def get_smtp_details(email: str) -> Optional[SMTPConfig]:
|
def get_smtp_details(this_email: str) -> Optional[SMTPConfig]:
|
||||||
account = get_account_by_email(email)
|
account = get_account_by_email(this_email)
|
||||||
return account.smtp if account else None
|
return account.smtp if account else None
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,55 +75,49 @@ def get_smtp_connection(account: EmailAccount):
|
||||||
else:
|
else:
|
||||||
return SMTP(account.smtp.host, account.smtp.port)
|
return SMTP(account.smtp.host, account.smtp.port)
|
||||||
|
|
||||||
def get_matching_autoresponders(email: IncomingEmail, account: EmailAccount) -> List[Dict]:
|
|
||||||
matching_profiles = []
|
|
||||||
|
|
||||||
def matches_list(item: str, email: IncomingEmail) -> bool:
|
|
||||||
|
|
||||||
|
def get_matching_autoresponders(this_email: IncomingEmail, account: EmailAccount) -> List[AutoResponder]:
|
||||||
|
def matches_list(item: str, this_email: IncomingEmail) -> bool:
|
||||||
if '@' in item:
|
if '@' in item:
|
||||||
return item in email.sender
|
return item in this_email.sender
|
||||||
else:
|
else:
|
||||||
return item.lower() in email.subject.lower() or item.lower() in email.body.lower()
|
return item.lower() in this_email.subject.lower() or item.lower() in this_email.body.lower()
|
||||||
|
matching_profiles = []
|
||||||
for profile in account.autoresponders:
|
for profile in account.autoresponders:
|
||||||
whitelist_match = not profile.whitelist or any(matches_list(item, email) for item in profile.whitelist)
|
whitelist_match = not profile.whitelist or any(matches_list(item, this_email) for item in profile.whitelist)
|
||||||
blacklist_match = any(matches_list(item, email) for item in profile.blacklist)
|
blacklist_match = any(matches_list(item, this_email) for item in profile.blacklist)
|
||||||
|
|
||||||
if whitelist_match and not blacklist_match:
|
if whitelist_match and not blacklist_match:
|
||||||
matching_profiles.append({
|
matching_profiles.append(profile)
|
||||||
'USER_FULLNAME': account.fullname,
|
|
||||||
'RESPONSE_STYLE': profile.style,
|
|
||||||
'AUTORESPONSE_CONTEXT': profile.context,
|
|
||||||
'IMG_GEN_PROMPT': profile.image_prompt,
|
|
||||||
'USER_BIO': account.bio
|
|
||||||
})
|
|
||||||
|
|
||||||
return matching_profiles
|
return matching_profiles
|
||||||
|
|
||||||
|
|
||||||
async def generate_auto_response_body(email: IncomingEmail, profile: Dict) -> str:
|
|
||||||
|
async def generate_auto_response_body(this_email: IncomingEmail, profile: AutoResponder, account: EmailAccount) -> str:
|
||||||
now = await locate.localize_datetime(dt_datetime.now())
|
now = await locate.localize_datetime(dt_datetime.now())
|
||||||
then = await locate.localize_datetime(email.datetime_received)
|
then = await locate.localize_datetime(this_email.datetime_received)
|
||||||
age = now - then
|
age = now - then
|
||||||
usr_prompt = f'''
|
usr_prompt = f'''
|
||||||
Generate a personalized auto-response to the following email:
|
Generate a personalized auto-response to the following email:
|
||||||
From: {email.sender}
|
From: {this_email.sender}
|
||||||
Sent: {age} ago
|
Sent: {age} ago
|
||||||
Subject: "{email.subject}"
|
Subject: "{this_email.subject}"
|
||||||
Body:
|
Body:
|
||||||
{email.body}
|
{this_email.body}
|
||||||
|
Respond on behalf of {account.fullname}, who is unable to respond personally because {profile.context}.
|
||||||
Respond on behalf of {profile['USER_FULLNAME']}, who is unable to respond personally because {profile['AUTORESPONSE_CONTEXT']}.
|
Keep the response {profile.style} and to the point, but responsive to the sender's inquiry.
|
||||||
Keep the response {profile['RESPONSE_STYLE']} and to the point, but responsive to the sender's inquiry.
|
Do not mention or recite this context information in your response.
|
||||||
Do not mention or recite this context information in your response.
|
'''
|
||||||
'''
|
sys_prompt = f"You are an AI assistant helping {account.fullname} with email responses. {account.fullname} is described as: {account.bio}"
|
||||||
|
|
||||||
sys_prompt = f"You are an AI assistant helping {profile['USER_FULLNAME']} with email responses. {profile['USER_FULLNAME']} is described as: {profile['USER_BIO']}"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await llm.query_ollama(usr_prompt, sys_prompt, 400)
|
# async def query_ollama(usr: str, sys: str = LLM_SYS_MSG, model: str = DEFAULT_LLM, max_tokens: int = 200):
|
||||||
|
response = await llm.query_ollama(usr_prompt, sys_prompt, profile.ollama_model, 400)
|
||||||
|
|
||||||
DEBUG(f"query_ollama response: {response}")
|
DEBUG(f"query_ollama response: {response}")
|
||||||
|
|
||||||
if isinstance(response, str):
|
if isinstance(response, str):
|
||||||
|
response += "\n\n"
|
||||||
return response
|
return response
|
||||||
elif isinstance(response, dict):
|
elif isinstance(response, dict):
|
||||||
if "message" in response and "content" in response["message"]:
|
if "message" in response and "content" in response["message"]:
|
||||||
|
@ -138,7 +132,7 @@ Do not mention or recite this context information in your response.
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ERR(f"Error generating auto-response: {str(e)}")
|
ERR(f"Error generating auto-response: {str(e)}")
|
||||||
return f"Thank you for your email regarding '{email.subject}'. We are currently experiencing technical difficulties with our auto-response system. We will review your email and respond as soon as possible. We apologize for any inconvenience."
|
return f"Thank you for your email regarding '{this_email.subject}'. We are currently experiencing technical difficulties with our auto-response system. We will review your email and respond as soon as possible. We apologize for any inconvenience."
|
||||||
|
|
||||||
|
|
||||||
def clean_email_content(html_content):
|
def clean_email_content(html_content):
|
||||||
|
@ -252,7 +246,7 @@ tags:
|
||||||
|
|
||||||
markdown_content += f'''
|
markdown_content += f'''
|
||||||
---
|
---
|
||||||
{email.body}
|
{this_email.body}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
with open(md_path, 'w', encoding='utf-8') as md_file:
|
with open(md_path, 'w', encoding='utf-8') as md_file:
|
||||||
|
@ -269,9 +263,9 @@ tags:
|
||||||
async def autorespond(this_email: IncomingEmail, account: EmailAccount):
|
async def autorespond(this_email: IncomingEmail, account: EmailAccount):
|
||||||
matching_profiles = get_matching_autoresponders(this_email, account)
|
matching_profiles = get_matching_autoresponders(this_email, account)
|
||||||
for profile in matching_profiles:
|
for profile in matching_profiles:
|
||||||
DEBUG(f"Auto-responding to {this_email.subject} with profile: {profile['USER_FULLNAME']}")
|
DEBUG(f"Auto-responding to {this_email.subject} with profile: {profile.name}")
|
||||||
auto_response_subject = f"Auto-Response Re: {this_email.subject}"
|
auto_response_subject = f"Auto-Response Re: {this_email.subject}"
|
||||||
auto_response_body = await generate_auto_response_body(this_email, profile)
|
auto_response_body = await generate_auto_response_body(this_email, profile, account)
|
||||||
DEBUG(f"Auto-response: {auto_response_body}")
|
DEBUG(f"Auto-response: {auto_response_body}")
|
||||||
await send_auto_response(this_email.sender, auto_response_subject, auto_response_body, profile, account)
|
await send_auto_response(this_email.sender, auto_response_subject, auto_response_body, profile, account)
|
||||||
|
|
||||||
|
@ -284,8 +278,8 @@ async def send_auto_response(to_email, subject, body, profile, account):
|
||||||
message['Subject'] = subject
|
message['Subject'] = subject
|
||||||
message.attach(MIMEText(body, 'plain'))
|
message.attach(MIMEText(body, 'plain'))
|
||||||
|
|
||||||
if profile['IMG_GEN_PROMPT']:
|
if profile.image_prompt:
|
||||||
jpg_path = await sd.workflow(profile['IMG_GEN_PROMPT'], earlyout=False, downscale_to_fit=True)
|
jpg_path = await sd.workflow(profile.image_prompt, earlyout=False, downscale_to_fit=True)
|
||||||
if jpg_path and os.path.exists(jpg_path):
|
if jpg_path and os.path.exists(jpg_path):
|
||||||
with open(jpg_path, 'rb') as img_file:
|
with open(jpg_path, 'rb') as img_file:
|
||||||
img = MIMEImage(img_file.read(), name=os.path.basename(jpg_path))
|
img = MIMEImage(img_file.read(), name=os.path.basename(jpg_path))
|
||||||
|
|
Loading…
Reference in a new issue