Compare commits
38 Commits
637bdeb6c0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1340ff2a0e | ||
|
|
e2398a8a39 | ||
|
|
6a996f1c09 | ||
|
|
68ffcf4956 | ||
|
|
a38c21b1a9 | ||
|
|
54bf5ca168 | ||
|
|
df2c11ae67 | ||
|
|
c501581f39 | ||
|
|
dc9bda2b37 | ||
|
|
2f3f75d3f2 | ||
| aebff3d595 | |||
|
|
83fb593dd6 | ||
|
|
764bb967f6 | ||
| d558d5834e | |||
|
|
21e87884c6 | ||
|
|
c968a3ae00 | ||
|
|
6726a156b3 | ||
| a10a010701 | |||
|
|
f4af875bb3 | ||
|
|
4b57659747 | ||
|
|
c7f48c6fe9 | ||
| 594aa545da | |||
| 286a3d2f32 | |||
| df94c4721b | |||
|
|
2fa41ea756 | ||
|
|
4eb7cd640b | ||
|
|
5f52c8b518 | ||
|
|
0e9f89d331 | ||
|
|
44447ec5b8 | ||
|
|
df21d69df4 | ||
|
|
47358d25a8 | ||
|
|
fc70b1776c | ||
|
|
e1e1e305bc | ||
|
|
323c90005b | ||
|
|
ccb5641919 | ||
|
|
2a881236cc | ||
|
|
218dcad925 | ||
|
|
02cce87a93 |
3
.gitignore
vendored
@@ -2,4 +2,5 @@
|
||||
public/
|
||||
resources/_gen/
|
||||
isableFastRander/
|
||||
.hugo_build.lock
|
||||
.hugo_build.lock
|
||||
*Zone.Identifier
|
||||
3
.gitmodules
vendored
@@ -5,3 +5,6 @@
|
||||
[submodule "themes/component-projects"]
|
||||
path = themes/component-projects
|
||||
url = https://github.com/hugo-fixit/component-projects.git
|
||||
[submodule "themes/hugo-embed-pdf-shortcode"]
|
||||
path = themes/hugo-embed-pdf-shortcode
|
||||
url = https://github.com/anvithks/hugo-embed-pdf-shortcode.git
|
||||
|
||||
@@ -7,15 +7,15 @@ This is a git repository for the FlareBlog. The Blog is based on [Hugo](https://
|
||||
Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/JamesFlare1212/FlareBlog.git
|
||||
git clone --recurse-submodules https://github.com/JamesFlare1212/FlareBlog.git
|
||||
```
|
||||
|
||||
Then, install Hugo.
|
||||
|
||||
For Linux:
|
||||
```bash
|
||||
wget https://github.com/gohugoio/hugo/releases/download/v0.136.5/hugo_extended_0.136.5_linux-amd64.deb
|
||||
dpkg -i hugo_extended_0.136.5_linux-amd64.deb
|
||||
wget https://github.com/gohugoio/hugo/releases/download/v0.146.0/hugo_extended_0.146.0_linux-amd64.deb
|
||||
sudo dpkg -i hugo_extended_0.146.0_linux-amd64.deb
|
||||
```
|
||||
|
||||
For MacOS:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
title: {{ replace .TranslationBaseName "-" " " | title }}
|
||||
subtitle:
|
||||
date: {{ .Date }}
|
||||
lastmod: {{ .Date }}
|
||||
slug: {{ substr .File.UniqueID 0 7 }}
|
||||
description:
|
||||
keywords:
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
title: {{ replace .TranslationBaseName "-" " " | title }}
|
||||
subtitle:
|
||||
date: {{ .Date }}
|
||||
lastmod: {{ .Date }}
|
||||
slug: {{ substr .File.UniqueID 0 7 }}
|
||||
draft: true
|
||||
author:
|
||||
name:
|
||||
link:
|
||||
name: James
|
||||
link: https://www.jamesflare.com
|
||||
email:
|
||||
avatar:
|
||||
avatar: /site-logo.avif
|
||||
description:
|
||||
keywords:
|
||||
license:
|
||||
|
||||
@@ -46,4 +46,8 @@ details summary strong {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.katex-display {
|
||||
overflow-y: clip;
|
||||
}
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
# Hugo Configuration
|
||||
# See: https://gohugo.io/getting-started/configuration/
|
||||
# -------------------------------------------------------------------------------------
|
||||
|
||||
#ignoreLogs = ['error-get-gh-repo', 'error-get-remote-json']
|
||||
# website title
|
||||
title = "FlareBlog"
|
||||
# Hostname (and path) to the root
|
||||
baseURL = "https://www.jamesflare.com/"
|
||||
# theme list
|
||||
theme = ["FixIt", "component-projects"]
|
||||
theme = ["FixIt", "component-projects", "hugo-embed-pdf-shortcode"]
|
||||
enableInlineShortcodes = true
|
||||
defaultContentLanguage = "en"
|
||||
# language code ["en", "zh-CN", "fr", "pl", ...]
|
||||
languageCode = "en"
|
||||
@@ -24,7 +25,8 @@ languageName = "English"
|
||||
# whether to include Chinese/Japanese/Korean
|
||||
hasCJKLanguage = true
|
||||
# default amount of posts in each pages
|
||||
paginate = 12
|
||||
[pagination]
|
||||
pagerSize = 12
|
||||
# copyright description used only for seo schema
|
||||
copyright = ""
|
||||
# whether to use robots.txt
|
||||
@@ -112,6 +114,11 @@ enableEmoji = true
|
||||
table = true
|
||||
taskList = true
|
||||
typographer = true
|
||||
[markup.goldmark.extensions.passthrough]
|
||||
enable = true
|
||||
[markup.goldmark.extensions.passthrough.delimiters]
|
||||
block = [['\[', '\]'], ['$$', '$$']]
|
||||
inline = [['\(', '\)'], ['$', '$']]
|
||||
# https://gohugo.io/getting-started/configuration-markup/#extras
|
||||
[markup.goldmark.extensions.extras]
|
||||
[markup.goldmark.extensions.extras.delete]
|
||||
@@ -166,7 +173,7 @@ enableEmoji = true
|
||||
# -------------------------------------------------------------------------------------
|
||||
|
||||
[privacy]
|
||||
[privacy.twitter]
|
||||
[privacy.x]
|
||||
enableDNT = true
|
||||
[privacy.youtube]
|
||||
privacyEnhanced = true
|
||||
@@ -284,6 +291,8 @@ enableEmoji = true
|
||||
enablePWA = false
|
||||
# FixIt 0.2.14 | NEW whether to add external Icon for external links automatically
|
||||
externalIcon = false
|
||||
# FixIt 0.3.13 | NEW whether to capitalize titles
|
||||
capitalizeTitles = true
|
||||
# FixIt 0.3.0 | NEW whether to add site title to the title of every page
|
||||
# remember to set up your site title in `hugo.toml` (e.g. title = "title")
|
||||
withSiteTitle = true
|
||||
@@ -292,6 +301,8 @@ enableEmoji = true
|
||||
# FixIt 0.3.0 | NEW whether to add site subtitle to the title of index page
|
||||
# remember to set up your site subtitle by `params.header.subtitle.name`
|
||||
indexWithSubtitle = false
|
||||
# FixIt 0.3.13 | NEW whether to show summary in plain text
|
||||
summaryPlainify = false
|
||||
# FixIt 0.2.14 | NEW FixIt will, by default, inject a theme meta tag in the HTML head on the home page only.
|
||||
# You can turn it off, but we would really appreciate if you don’t, as this is a good way to watch FixIt's popularity on the rise.
|
||||
disableThemeInject = false
|
||||
@@ -394,6 +405,9 @@ enableEmoji = true
|
||||
enable = false
|
||||
sticky = false
|
||||
showHome = false
|
||||
# FixIt 0.3.13 | NEW
|
||||
separator = "/"
|
||||
capitalize = true
|
||||
|
||||
# FixIt 0.3.10 | NEW Post navigation config
|
||||
[params.navigation]
|
||||
@@ -477,6 +491,14 @@ enableEmoji = true
|
||||
# whether to show the full text content in feed.
|
||||
fullText = false
|
||||
|
||||
# FixIt 0.3.13 | NEW recently updated pages config for archives, section and term list
|
||||
[params.recentlyUpdated]
|
||||
archives = true
|
||||
section = true
|
||||
list = true
|
||||
days = 30
|
||||
maxCount = 10
|
||||
|
||||
# FixIt 0.2.17 | NEW TagCloud config for tags page
|
||||
[params.tagcloud]
|
||||
enable = false
|
||||
@@ -519,7 +541,7 @@ enableEmoji = true
|
||||
Twitter = ""
|
||||
Instagram = ""
|
||||
Facebook = ""
|
||||
Telegram = "ossOpration"
|
||||
Telegram = ""
|
||||
Medium = ""
|
||||
Gitlab = ""
|
||||
Youtubelegacy = ""
|
||||
@@ -838,8 +860,8 @@ enableEmoji = true
|
||||
appKey = ""
|
||||
placeholder = ""
|
||||
avatar = "mp"
|
||||
meta = ""
|
||||
requiredFields = ""
|
||||
meta = ['nick','mail','link']
|
||||
requiredFields = []
|
||||
pageSize = 10
|
||||
lang = ""
|
||||
visitor = true
|
||||
@@ -969,6 +991,25 @@ enableEmoji = true
|
||||
# For values, see https://mermaid.js.org/config/theming.html#available-themes
|
||||
themes = ["default", "dark"]
|
||||
|
||||
# FixIt 0.3.13 | NEW Admonitions custom config
|
||||
# See https://fixit.lruihao.cn/documentation/content-management/shortcodes/extended/admonition/#custom-admonitions
|
||||
# syntax: <type> = <icon>
|
||||
[params.admonition]
|
||||
# ban = "fa-solid fa-ban"
|
||||
|
||||
# FixIt 0.3.14 | NEW Task lists custom config
|
||||
# See https://fixit.lruihao.cn/documentation/content-management/advanced/#custom-task-lists
|
||||
# syntax: <sign> = <icon>
|
||||
[params.taskList]
|
||||
# tip = "fa-regular fa-lightbulb"
|
||||
|
||||
# FixIt 0.3.15 | NEW version shortcode config
|
||||
[params.repoVersion]
|
||||
# url prefix for the release tag
|
||||
url = "https://github.com/hugo-fixit/FixIt/releases/tag/v"
|
||||
# project name
|
||||
name = "FixIt"
|
||||
|
||||
# FixIt 0.2.12 | NEW PanguJS config
|
||||
[params.pangu]
|
||||
# For Chinese writing
|
||||
@@ -1028,7 +1069,7 @@ enableEmoji = true
|
||||
|
||||
# Analytics config
|
||||
[params.analytics]
|
||||
enable = false
|
||||
enable = true
|
||||
# Google Analytics
|
||||
[params.analytics.google]
|
||||
id = ""
|
||||
@@ -1039,6 +1080,31 @@ enableEmoji = true
|
||||
id = ""
|
||||
# server url for your tracker if you're self hosting
|
||||
server = ""
|
||||
# FixIt 0.3.16 | NEW Baidu Analytics
|
||||
[params.analytics.baidu]
|
||||
id = ""
|
||||
# FixIt 0.3.16 | NEW Umami Analytics
|
||||
[params.analytics.umami]
|
||||
data_website_id = "c687e659-a8de-4d17-a794-0fb82dd085f5"
|
||||
src = "https://track.jamesflare.com/script.js"
|
||||
data_host_url = "https://track.jamesflare.com"
|
||||
data_domains = ""
|
||||
# FixIt 0.3.16 | NEW Plausible Analytics
|
||||
[params.analytics.plausible]
|
||||
data_domain = ""
|
||||
src = ""
|
||||
# FixIt 0.3.16 | NEW Cloudflare Analytics
|
||||
[params.analytics.cloudflare]
|
||||
token = ""
|
||||
# FixIt 0.3.16 | NEW Splitbee Analytics
|
||||
[params.analytics.splitbee]
|
||||
enable = false
|
||||
# no cookie mode
|
||||
no_cookie = true
|
||||
# respect the do not track setting of the browser
|
||||
do_not_track = true
|
||||
# token(optional), more info on https://splitbee.io/docs/embed-the-script
|
||||
data_token = ""
|
||||
|
||||
# Cookie consent config
|
||||
[params.cookieconsent]
|
||||
@@ -1115,6 +1181,22 @@ enableEmoji = true
|
||||
# whether to show the full text content in feed.
|
||||
fullText = true
|
||||
|
||||
# FixIt 0.3.12 | NEW Custom partials config
|
||||
# Custom partials must be stored in the /layouts/partials/ directory.
|
||||
# Depends on open custom blocks https://fixit.lruihao.cn/references/blocks/
|
||||
[params.customPartials]
|
||||
head = []
|
||||
menuDesktop = []
|
||||
menuMobile = []
|
||||
profile = []
|
||||
aside = []
|
||||
comment = []
|
||||
footer = []
|
||||
widgets = []
|
||||
assets = []
|
||||
postFooterBefore = []
|
||||
postFooterAfter = []
|
||||
|
||||
# FixIt 0.2.15 | NEW Developer options
|
||||
# Select the scope named `public_repo` to generate personal access token,
|
||||
# Configure with environment variable `HUGO_PARAMS_GHTOKEN=xxx`, see https://gohugo.io/functions/os/getenv/#examples
|
||||
|
||||
BIN
content/en/posts/csci-1100/crib-sheets/Final Crib Sheet A.pdf
Normal file
BIN
content/en/posts/csci-1100/crib-sheets/Test 2 Crib Sheet A.pdf
Normal file
BIN
content/en/posts/csci-1100/crib-sheets/Test 2 Crib Sheet B.pdf
Normal file
BIN
content/en/posts/csci-1100/crib-sheets/Test 3 Crib Sheet A.pdf
Normal file
BIN
content/en/posts/csci-1100/crib-sheets/Test 3 Crib Sheet B.pdf
Normal file
84
content/en/posts/csci-1100/crib-sheets/index.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: CSCI 1100 - Test Crib Sheets
|
||||
subtitle:
|
||||
date: 2024-12-08T22:17:36-05:00
|
||||
lastmod: 2024-12-08T22:17:36-05:00
|
||||
slug: csci-1100-crib-sheets
|
||||
draft: false
|
||||
author:
|
||||
name: James
|
||||
link: https://www.jamesflare.com
|
||||
email:
|
||||
avatar: /site-logo.avif
|
||||
description: This post shares the crib sheets I have been used in Test 2, Test 3 and Final of CSCI 1100.
|
||||
keywords:
|
||||
license:
|
||||
comment: true
|
||||
weight: 0
|
||||
tags:
|
||||
- CSCI 1100
|
||||
- Exam
|
||||
- RPI
|
||||
- Python
|
||||
- Programming
|
||||
categories:
|
||||
- Programming
|
||||
collections:
|
||||
- CSCI 1100
|
||||
hiddenFromHomePage: false
|
||||
hiddenFromSearch: false
|
||||
hiddenFromRss: false
|
||||
hiddenFromRelated: false
|
||||
summary: This post shares the crib sheets I have been used in Test 2, Test 3 and Final of CSCI 1100.
|
||||
resources:
|
||||
- name: featured-image
|
||||
src: featured-image.jpg
|
||||
- name: featured-image-preview
|
||||
src: featured-image-preview.jpg
|
||||
toc: true
|
||||
math: false
|
||||
lightgallery: false
|
||||
password:
|
||||
message:
|
||||
repost:
|
||||
enable: false
|
||||
url:
|
||||
|
||||
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
|
||||
---
|
||||
|
||||
<!--more-->
|
||||
|
||||
> [!TIP]
|
||||
> You can edit this PDF with Adobe Photoshop. Yes, you are correct! I made this PDF by Photoshop. The font have been used in these crib sheets is [Intel One Mono](https://github.com/intel/intel-one-mono).
|
||||
|
||||
## Test 1 Crib Sheet
|
||||
|
||||
> [!NOTE]
|
||||
> I didn't use a crib sheet in test 1. So, not crib sheets here.
|
||||
|
||||
## Test 2 Crib Sheet
|
||||
|
||||
<div style="width: 100%; max-width: 600px; margin: 0 auto; display: block;">
|
||||
<embed src="Test 2 Crib Sheet A.pdf" type="application/pdf" width="100%" height="500px">
|
||||
</div>
|
||||
|
||||
<div style="width: 100%; max-width: 600px; margin: 0 auto; display: block;">
|
||||
<embed src="Test 2 Crib Sheet B.pdf" type="application/pdf" width="100%" height="500px">
|
||||
</div>
|
||||
|
||||
## Test 3 Crib Sheet
|
||||
|
||||
<div style="width: 100%; max-width: 600px; margin: 0 auto; display: block;">
|
||||
<embed src="Test 3 Crib Sheet A.pdf" type="application/pdf" width="100%" height="500px">
|
||||
</div>
|
||||
|
||||
<div style="width: 100%; max-width: 600px; margin: 0 auto; display: block;">
|
||||
<embed src="Test 3 Crib Sheet B.pdf" type="application/pdf" width="100%" height="500px">
|
||||
</div>
|
||||
|
||||
## Final Crib Sheet
|
||||
|
||||
<div style="width: 100%; max-width: 600px; margin: 0 auto; display: block;">
|
||||
<embed src="Final Crib Sheet A.pdf" type="application/pdf" width="100%" height="500px">
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@
|
||||
title: CSCI 1100 - Test 4 Overview and Practice Questions
|
||||
subtitle:
|
||||
date: 2024-04-26T03:54:07-04:00
|
||||
slug: csci-1100-exam-4overview
|
||||
slug: csci-1100-exam-4-overview
|
||||
draft: true
|
||||
author:
|
||||
name: James
|
||||
@@ -136,4 +136,4 @@ You do not need to know the intricacies of tkinter GUI formatting, but you shoul
|
||||
>
|
||||
> ```
|
||||
> [6.9, 7.4, 7.7, 8.7, 8.8, 9.3, 9.5, 10.1, 11.1, 12.2]
|
||||
> ```
|
||||
> ```
|
||||
|
||||
@@ -10,7 +10,7 @@ author:
|
||||
email:
|
||||
avatar: /site-logo.avif
|
||||
description: This blog post provides a detailed overview of a Python programming homework assignment, which includes creating a Mad Libs game, calculating speed and pace, and generating a framed box with user-specified dimensions.
|
||||
keywords: ["Python", "programming", "homework", "Mad Libs", "speed calculation", "framed box"]
|
||||
keywords: ["Python", "Programming", "Homework", "Mad Libs", "Speed calculation", "Framed box"]
|
||||
license:
|
||||
comment: true
|
||||
weight: 0
|
||||
@@ -120,8 +120,6 @@ We will test your code for the values used in our examples as well as a range of
|
||||
|
||||
{{< link href="HW1.zip" content="HW1.zip" title="Download HW1.zip" download="HW1.zip" card=true >}}
|
||||
|
||||
***
|
||||
|
||||
## Solution
|
||||
|
||||
### hw1_part1.py
|
||||
|
||||
@@ -182,8 +182,6 @@ Test your code well and when you are sure that it works, please submit it as a f
|
||||
|
||||
{{< link href="HW2.zip" content="HW2.zip" title="Download HW2.zip" download="HW2.zip" card=true >}}
|
||||
|
||||
***
|
||||
|
||||
## Solution
|
||||
|
||||
### hw2_part1.py
|
||||
|
||||
@@ -227,4 +227,257 @@ To determine the worst and best movies, the example code used a sort with the ra
|
||||
## Solution
|
||||
|
||||
> [!NOTE]
|
||||
> I didn't get a full mark in this assignment (Only 96%), so I didn't post the solution. I may redo it to get a full mark solution. After that, I will add it here.
|
||||
> I didn't get a full mark in this assignment (Only 96%), so you should not fully trust it. I may redo it to get a full mark solution. After that, I will add it here.
|
||||
|
||||
### hw7_part1.py
|
||||
|
||||
```python
|
||||
"""
|
||||
An implementation of HW7 Part 1
|
||||
"""
|
||||
|
||||
# Global Variables
|
||||
word_path = ""
|
||||
#word_path = "/mnt/c/Users/james/OneDrive/RPI/Spring 2024/CSCI-1100/Homeworks/HW7/hw7_files/"
|
||||
|
||||
# Debugging Variables
|
||||
dictionary_file = "words_10percent.txt"
|
||||
input_file = "input_words.txt"
|
||||
keyboard_file = "keyboard.txt"
|
||||
|
||||
def get_dictionary(file_name):
|
||||
words_dict = dict()
|
||||
data = open(file_name, 'r')
|
||||
for lines in data:
|
||||
lines = lines.strip()
|
||||
the_key = lines.split(",")[0]
|
||||
the_value = float(lines.split(",")[1])
|
||||
words_dict[the_key] = the_value
|
||||
data.close()
|
||||
return words_dict
|
||||
|
||||
def get_keyboard(file_name):
|
||||
keyboard_dict = dict()
|
||||
data = open(file_name, 'r')
|
||||
for lines in data:
|
||||
lines = lines.strip()
|
||||
the_key = lines.split(" ")[0]
|
||||
keyboard_dict[the_key] = []
|
||||
for i in lines.split(" ")[1:]:
|
||||
keyboard_dict[the_key].append(i)
|
||||
data.close()
|
||||
return keyboard_dict
|
||||
|
||||
def check_in_dictionary(word, dictionary):
|
||||
if word in dictionary:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_input_words(file_name):
|
||||
input_words = []
|
||||
file = open(file_name, 'r')
|
||||
for lines in file:
|
||||
lines = lines.strip()
|
||||
input_words.append(lines)
|
||||
file.close()
|
||||
return input_words
|
||||
|
||||
def get_drop_words(word):
|
||||
drop_words = set()
|
||||
for i in range(len(word)):
|
||||
drop_words.add(word[:i] + word[i+1:])
|
||||
return drop_words
|
||||
|
||||
def get_insert_words(word):
|
||||
insert_words = set()
|
||||
alphabet = "abcdefghijklmnopqrstuvwxyz"
|
||||
for i in range(len(word)+1):
|
||||
for j in alphabet:
|
||||
insert_words.add(word[:i] + j + word[i:])
|
||||
#print("Inserting: ", word[:i] + j + word[i:])
|
||||
return insert_words
|
||||
|
||||
def get_swap_words(word):
|
||||
swap_words = set()
|
||||
for i in range(len(word) - 1):
|
||||
swap_words.add(word[:i] + word[i+1] + word[i] + word[i+2:])
|
||||
return swap_words
|
||||
|
||||
def get_replace_words(word, keyboard):
|
||||
replace_words = set()
|
||||
#print(keyboard)
|
||||
for i in range(len(word)):
|
||||
for j in range(len(word[i])):
|
||||
for k in keyboard[word[i][j]]:
|
||||
replace_words.add(word[:i] + k + word[i+1:])
|
||||
return replace_words
|
||||
|
||||
def get_all_possible_words(word, keyboard):
|
||||
all_possible_words = set()
|
||||
all_possible_words.update(get_drop_words(word))
|
||||
all_possible_words.update(get_insert_words(word))
|
||||
all_possible_words.update(get_swap_words(word))
|
||||
all_possible_words.update(get_replace_words(word, keyboard))
|
||||
return all_possible_words
|
||||
|
||||
def get_suggestions(word, dictionary, keyboard):
|
||||
suggestions = dict()
|
||||
all_possible_words = get_all_possible_words(word, keyboard)
|
||||
for i in all_possible_words:
|
||||
if i in dictionary:
|
||||
suggestions[i] = dictionary[i]
|
||||
topx = sorted(suggestions, key=lambda x: (suggestions[x], x), reverse=True)
|
||||
#print(topx)
|
||||
return topx
|
||||
|
||||
def construct_output(input_words, dictionary, keyboard):
|
||||
output = ""
|
||||
max_length = max([len(i) for i in input_words])
|
||||
for i in input_words:
|
||||
output += " " + " " * (max_length - len(i)) + i + " -> "
|
||||
if check_in_dictionary(i, dictionary):
|
||||
output += "FOUND"
|
||||
elif len(get_suggestions(i, dictionary, keyboard)) == 0:
|
||||
output += "NOT FOUND"
|
||||
else:
|
||||
output += "FOUND {:2d}".format(len(get_suggestions(i, dictionary, keyboard))) + ": "
|
||||
suggestions = get_suggestions(i, dictionary, keyboard)[:3]
|
||||
for j in suggestions:
|
||||
output += " " + j
|
||||
output += "\n"
|
||||
return output
|
||||
|
||||
if __name__ == "__main__":
|
||||
dictionary_file = input("Dictionary file => ").strip()
|
||||
print(dictionary_file)
|
||||
input_file = input("Input file => ").strip()
|
||||
print(input_file)
|
||||
keyboard_file = input("Keyboard file => ").strip()
|
||||
print(keyboard_file)
|
||||
|
||||
dictionary = get_dictionary(word_path + dictionary_file)
|
||||
#print(dictionary)
|
||||
keyboard = get_keyboard(word_path + keyboard_file)
|
||||
#print(keyboard)
|
||||
#print(get_input_words(word_path + input_file))
|
||||
#print(get_drop_words("hello"))
|
||||
#print("shut" in get_insert_words("shu"))
|
||||
#print(get_swap_words("hello"))
|
||||
#print("integers" in get_replace_words("inteters", keyboard))
|
||||
#print(get_all_possible_words("hello", keyboard))
|
||||
#print(get_suggestions("doitd", dictionary, keyboard))
|
||||
print(construct_output(get_input_words(word_path + input_file), dictionary, keyboard), end = "")
|
||||
```
|
||||
|
||||
### hw7_part2.py
|
||||
|
||||
```python
|
||||
"""
|
||||
An implementation of HW7 Part 2
|
||||
"""
|
||||
import json
|
||||
|
||||
# Global Variables
|
||||
word_path = ""
|
||||
#word_path = "/mnt/c/Users/james/OneDrive/RPI/Spring 2024/CSCI-1100/Homeworks/HW7/hw7_files/"
|
||||
genre = ""
|
||||
|
||||
# Debugging Variables
|
||||
#min_year = 2000
|
||||
#max_year = 2016
|
||||
#imdb_weight = 0.7
|
||||
#twitter_weight = 0.3
|
||||
#genre = "sci-fi"
|
||||
|
||||
def get_movie_ids(movies, min_year, max_year):
|
||||
ids = set()
|
||||
for i in movies.keys():
|
||||
if movies[i]['movie_year'] >= min_year and movies[i]['movie_year'] <= max_year:
|
||||
ids.add(int(i))
|
||||
return ids
|
||||
|
||||
def get_imdb_rating(movies, movie_id):
|
||||
return float(movies[str(movie_id)]['rating'])
|
||||
|
||||
def get_twitter_rating(ratings, movie_id):
|
||||
if str(movie_id) in ratings.keys():
|
||||
return ratings[str(movie_id)]
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_num_twitter_ratings(ratings, movie_id):
|
||||
return len(get_twitter_rating(ratings, movie_id))
|
||||
|
||||
def get_weighted_rating(movies, ratings, movie_id, imdb_weight, twitter_weight):
|
||||
imdb = get_imdb_rating(movies, movie_id)
|
||||
twitter = 0.0
|
||||
for i in get_twitter_rating(ratings, movie_id):
|
||||
twitter += i
|
||||
twitter /= len(get_twitter_rating(ratings, movie_id))
|
||||
return (imdb * imdb_weight + twitter * twitter_weight) / (imdb_weight + twitter_weight)
|
||||
|
||||
def get_movie_name(movies, movie_id):
|
||||
return movies[str(movie_id)]['name']
|
||||
|
||||
if __name__ == "__main__":
|
||||
movies = json.loads(open(word_path + "movies.json").read())
|
||||
ratings = json.loads(open(word_path + "ratings.json").read())
|
||||
|
||||
"""
|
||||
movies['3520029'] = {'genre': ['Sci-Fi', 'Action', 'Adventure'],
|
||||
'movie_year': 2010, 'name': 'TRON: Legacy',
|
||||
'rating': 6.8, 'numvotes': 254865}
|
||||
"""
|
||||
|
||||
min_year = int(input("Min year => ").strip())
|
||||
print(min_year)
|
||||
max_year = int(input("Max year => ").strip())
|
||||
print(max_year)
|
||||
imdb_weight = float(input("Weight for IMDB => ").strip())
|
||||
print(imdb_weight)
|
||||
twitter_weight = float(input("Weight for Twitter => ").strip())
|
||||
print(twitter_weight)
|
||||
|
||||
ids = get_movie_ids(movies, min_year, max_year)
|
||||
#print(ids)
|
||||
while genre.lower() !="stop":
|
||||
genre = input("\nWhat genre do you want to see? ").strip()
|
||||
print(genre)
|
||||
|
||||
if genre == "stop":
|
||||
break
|
||||
|
||||
min_rating = 10000.0
|
||||
max_rating = 0.0
|
||||
min_name = ""
|
||||
max_name = ""
|
||||
mv_min_year = 10000
|
||||
mv_max_year = 0
|
||||
|
||||
for i in ids:
|
||||
if get_num_twitter_ratings(ratings, i) <= 3:
|
||||
continue
|
||||
genres = movies[str(i)]['genre']
|
||||
genres = [x.lower() for x in genres]
|
||||
#print("Debug", i, genres)
|
||||
if genre.lower() in genres:
|
||||
rating = get_weighted_rating(movies, ratings, i, imdb_weight, twitter_weight)
|
||||
#print("Debug", rating)
|
||||
if rating < min_rating:
|
||||
min_rating = rating
|
||||
min_name = get_movie_name(movies, i)
|
||||
mv_min_year = movies[str(i)]['movie_year']
|
||||
if rating > max_rating:
|
||||
max_rating = rating
|
||||
max_name = get_movie_name(movies, i)
|
||||
mv_max_year = movies[str(i)]['movie_year']
|
||||
|
||||
if min_name == "" or max_name == "":
|
||||
print("\nNo {} movie found in {} through {}".format(genre, mv_min_year, mv_max_year))
|
||||
else:
|
||||
print("\nBest:\n Released in {}, {} has a rating of {:.2f}".format(mv_max_year, max_name, max_rating))
|
||||
print("\nWorst:\n Released in {}, {} has a rating of {:.2f}".format(mv_min_year, min_name, min_rating))
|
||||
|
||||
genre = genre
|
||||
#genre = "stop" # Debugging Only
|
||||
```
|
||||
|
||||
504
content/en/posts/csci-1200/hw-1/index.md
Normal file
@@ -0,0 +1,504 @@
|
||||
---
|
||||
title: CSCI 1200 - Homework 1 - Spotify Playlists
|
||||
subtitle:
|
||||
date: 2025-02-15T13:38:46-05:00
|
||||
lastmod: 2025-02-15T13:38:46-05:00
|
||||
slug: csci-1200-hw-1
|
||||
draft: false
|
||||
author:
|
||||
name: James
|
||||
link: https://www.jamesflare.com
|
||||
email:
|
||||
avatar: /site-logo.avif
|
||||
description: This blog post provides a detailed guide on developing a music playlist management program similar to Spotify using C++. It covers command-line parameter handling, file I/O operations, and the use of STL string and vector classes.
|
||||
keywords: ["C++", "Programming", "Homework", "STL Vector","Playlist Management"]
|
||||
license:
|
||||
comment: true
|
||||
weight: 0
|
||||
tags:
|
||||
- CSCI 1200
|
||||
- Homework
|
||||
- RPI
|
||||
- C++
|
||||
- Programming
|
||||
categories:
|
||||
- Programming
|
||||
collections:
|
||||
- CSCI 1200
|
||||
hiddenFromHomePage: false
|
||||
hiddenFromSearch: false
|
||||
hiddenFromRss: false
|
||||
hiddenFromRelated: false
|
||||
summary: This blog post provides a detailed guide on developing a music playlist management program similar to Spotify using C++. It covers command-line parameter handling, file I/O operations, and the use of STL string and vector classes.
|
||||
resources:
|
||||
- name: featured-image
|
||||
src: featured-image.jpg
|
||||
- name: featured-image-preview
|
||||
src: featured-image-preview.jpg
|
||||
toc: true
|
||||
math: false
|
||||
lightgallery: false
|
||||
password:
|
||||
message:
|
||||
repost:
|
||||
enable: false
|
||||
url:
|
||||
|
||||
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
|
||||
---
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Assignment Requirements
|
||||
|
||||
{{< details >}}
|
||||
Before starting this homework, make sure you have read and understood the Academic Integrity Policy.
|
||||
|
||||
In this assignment you will develop a program to manage music playlists like Spotify does, let's call this program New York Playlists. Please read the entire handout before starting to code the assignment.
|
||||
|
||||
## Learning Objectives
|
||||
|
||||
- Practice handling command line arguments.
|
||||
- Practice handling file input and output.
|
||||
- Practice the C++ Standard Template Library string and vector classes.
|
||||
|
||||
## Command Line Arguments
|
||||
|
||||
Your program will be run like this:
|
||||
|
||||
```console
|
||||
./nyplaylists.exe playlist.txt actions.txt output.txt
|
||||
```
|
||||
|
||||
Here:
|
||||
|
||||
- nyplaylists.exe is the executable file name.
|
||||
- playlist.txt is the name of an input file which contains a playlist - in this README, we will refer to this file as the **playlist file**.
|
||||
- actions.txt is an input file which defines a sequence of actions - in this README, we will refer to this file as the **actions file**.
|
||||
- output.txt where to print your output to.
|
||||
|
||||
## Playlist File Format and Output File Format
|
||||
|
||||
The playlist file and the output file have the same format. Take the playlist_tiny1.txt as an example, this file has the following 4 lines:
|
||||
|
||||
```console
|
||||
"Perfect Duet" Ed Sheeran, Beyonce
|
||||
"Always Remember Us This Way" Lady Gaga current
|
||||
"Million Reasons" Lady Gaga
|
||||
"I Will Never Love Again - Film Version" Lady Gaga, Bradley Cooper
|
||||
```
|
||||
|
||||
Except the second line, each line has two fields, the music title, and the artist(s). There is one single space separating these two fields.
|
||||
|
||||
The second line is special, it ends with the word **current**, meaning that the song "Always Remember Us This Way" is the currently playing song. This word **current** appears in the **playlist file** once and should also appear in the output file once.
|
||||
|
||||
## Actions File Format
|
||||
|
||||
The actions file defines actions. Take actions1.txt as an example, this file has the following lines:
|
||||
|
||||
```console
|
||||
add "Umbrella" Rihanna
|
||||
add "We Are Young" Fun
|
||||
add "You Are Still the One" Shania Twain
|
||||
remove "Million Reasons" Lady Gaga
|
||||
add "Viva La Vida" Coldplay
|
||||
move "I Will Never Love Again - Film Version" Lady Gaga, Bradley Cooper 1
|
||||
next
|
||||
next
|
||||
next
|
||||
previous
|
||||
move "You Are Still the One" Shania Twain 4
|
||||
```
|
||||
|
||||
The **actions file** may include 5 different types of actions:
|
||||
|
||||
- add, which adds a song to the end of the playlist.
|
||||
- remove, which removes a song from the playlist.
|
||||
- move, which moves a song to a new position - the new position is always included at the end of the line. The line *move "I Will Never Love Again - Film Version" Lady Gaga, Bradley Cooper 1*, moves the song "I Will Never Love Again - Film Version" to position 1, and the line *move "You Are Still the One" Shania Twain 4*, moves the song "You Are Still the One" to position 4. Note that, unliked array indexing in C/C++, positioning in Spotify starts at 1, as opposed to 0. This can be seen in the above Spotify screenshot: the first position is position 1.
|
||||
- next, which skips the currently playing song and starts playing the song that is listed directly after it. Note that if the currently playing song is already at the bottom of the playlist, the action *next* will make the first song (i.e., the song at the very top of the playlist) as the currently playing song.
|
||||
- previous, which skips the currently playing song and goes to the song listed directly before the currently playing song. Note that if the currently playing song is already at the top of the playlist, the action *previous* will make the last song (i.e., the song at the bottom of the playlist) as the currently playing song.
|
||||
|
||||
According to this sample **actions file**, 4 songs will be added to the playlist, 1 song will be removed, 2 songs will be moved. And the currently playing song will be a different song, instead of the song "Always Remember Us This Way".
|
||||
|
||||
When playlist_tiny1.txt and actions1.txt are supplied to your program as the two input files, your program should produce the following output file:
|
||||
|
||||
```console
|
||||
"I Will Never Love Again - Film Version" Lady Gaga, Bradley Cooper
|
||||
"Perfect Duet" Ed Sheeran, Beyonce
|
||||
"Always Remember Us This Way" Lady Gaga
|
||||
"You Are Still the One" Shania Twain
|
||||
"Umbrella" Rihanna
|
||||
"We Are Young" Fun current
|
||||
"Viva La Vida" Coldplay
|
||||
```
|
||||
|
||||
## Non-existent Songs
|
||||
|
||||
If a move action or a remove action as defined in the **actions file** attempts to move or remove a song which does not exist in the playlist, your program should ignore such an action.
|
||||
|
||||
## Duplicated Songs
|
||||
|
||||
In cases where the same song appears more than once on the playlist, choose the first song (to move or remove) - i.e., search the playlist, starting from the top to the bottom, identify the first occurrence of this song, and use it (to move or remove).
|
||||
|
||||
## Instructor's Code
|
||||
|
||||
You can test (but not view) the instructor's code here: [instructor code](http://ds.cs.rpi.edu/hws/playlists/). Note that this site is hosted on RPI's network and you can visit this site only if you are on RPI's network: either on campus or using a VPN service. Also note that, it is not your job in this assignment to play musics, the instructor's C++ code here is just used as the backend to manage the playlist.
|
||||
|
||||
## Program Requirements & Submission Details
|
||||
|
||||
In this assignment, you are required to use both std::string and std::vector. You are NOT allowed to use any data structures we have not learned so far.
|
||||
|
||||
Use good coding style when you design and implement your program. Organize your program into functions: don’t put all the code in main! Be sure to read the [Homework Policies](https://www.cs.rpi.edu/academics/courses/spring25/csci1200/homework_policies.php) as you put the finishing touches on your solution. Be sure to make up new test cases to fully debug your program and don’t forget to comment your code! Complete the provided template [README.txt](./README.txt). You must do this assignment on your own, as described in the [Collaboration Policy & Academic Integrity](https://www.cs.rpi.edu/academics/courses/spring25/csci1200/academic_integrity.php) page. If you did discuss the problem or error messages, etc. with anyone, please list their names in your README.txt file. Prepare and submit your assignment as instructed on the course webpage. Please ask a TA if you need help preparing your assignment for submission.
|
||||
|
||||
**Due Date**: 01/16/2025, 10pm.
|
||||
|
||||
## Rubric
|
||||
|
||||
13 pts
|
||||
- README.txt Completed (3 pts)
|
||||
- One of name, collaborators, or hours not filled in. (-1)
|
||||
- Two or more of name, collaborators, or hours not filled in. (-2)
|
||||
- No reflection. (-1)
|
||||
- STL Vector & String (3 pts)
|
||||
- Uses data structures which have not been covered in this class. (-3)
|
||||
- Did not use STL vector (-2)
|
||||
- Did not use STL string (-2)
|
||||
- Program Structure (7 pts)
|
||||
- No credit (significantly incomplete implementation) (-7)
|
||||
- Putting almost everything in the main function. It's better to create separate functions for different tasks. (-2)
|
||||
- Improper uses or omissions of const and reference. (-1)
|
||||
- Almost total lack of helpful comments. (-4)
|
||||
- Too few comments. (-2)
|
||||
- Contains useless comments like commented-out code, terminal commands, or silly notes. (-1)
|
||||
- Overly cramped, excessive whitespace, or poor indentation. (-1)
|
||||
- Lacks error checking (num of args, invalid file names, invalid command, etc.) (-1)
|
||||
- Poor choice of variable names: non-descriptive names (e.g. 'vec', 'str', 'var'), single-letter variable names (except single loop counter), etc. (-2)
|
||||
- Uses global variables. (-1)
|
||||
- Overly long lines, in excess of 100 or so characters. It's recommended to keep all lines short and put comments on their own lines. (-1)
|
||||
{{< /details >}}
|
||||
|
||||
## Supporting Files
|
||||
|
||||
{{< link href="spotify_playlists.7z" content="spotify_playlists.7z" title="Download spotify_playlists.7z" download="spotify_playlists.7z" card=true >}}
|
||||
|
||||
## Program Design
|
||||
|
||||
Before start, we need to find out what need to do. Let's draw a flowchart to exam what are the steps.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A(("Start")) --> D["Read 'playlist file', 'actions file'"]
|
||||
|
||||
subgraph "Initialize"
|
||||
D --> E["Find the index of current song (if any)"]
|
||||
end
|
||||
|
||||
E --> F{"For each action in 'actions file'"}
|
||||
|
||||
subgraph "Process Actions"
|
||||
F -- next --> G["Find the index of current song (if any)"]
|
||||
G --> H["Remove 'current' from current song"]
|
||||
H --> I{"Is it the last song?"}
|
||||
I -- Yes --> J["Set index to 0"]
|
||||
I -- No --> K["Set index to index+1"]
|
||||
J --> L["Mark new current song"]
|
||||
K --> L["Mark new current song"]
|
||||
|
||||
F -- previous --> M["Find the index of current song (if any)"]
|
||||
M --> N["Remove 'current' from current song"]
|
||||
N --> O{"Is it the first song?"}
|
||||
O -- Yes --> P["Set index to last song"]
|
||||
O -- No --> Q["Set index to index-1"]
|
||||
P --> R["Mark new current song"]
|
||||
Q --> R["Mark new current song"]
|
||||
|
||||
F -- add --> S["'Build' the new song string"]
|
||||
S --> T["Append to playlist"]
|
||||
|
||||
F -- remove --> U["'Build' the song string to remove"]
|
||||
U --> V["Find the first occurrence (if any)"]
|
||||
V --> W["Remove from playlist (ignore if not found)"]
|
||||
|
||||
F -- move --> X["'Build' the song string to move"]
|
||||
X --> Y["Check 'move' destination"]
|
||||
Y --> Z["Find the first occurrence (if any)"]
|
||||
Z --> ZA["Remove from playlist (ignore if not found)"]
|
||||
ZA --> ZB["Insert at new position"]
|
||||
end
|
||||
```
|
||||
|
||||
Then, we can plan what function to use in this program.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph "Main"
|
||||
main["main()"]
|
||||
end
|
||||
|
||||
subgraph "File IO"
|
||||
load_list("load_list()")
|
||||
get_text("get_text()")
|
||||
write_list("write_list()")
|
||||
end
|
||||
|
||||
subgraph "Helpers"
|
||||
is_all_digits("is_all_digits()")
|
||||
tokenizer("tokenizer()")
|
||||
check_in_list("check_in_list()")
|
||||
remove_in_list("remove_in_list()")
|
||||
get_current("get_current()")
|
||||
build_song("build_song()")
|
||||
end
|
||||
|
||||
%% Connections
|
||||
main --> load_list
|
||||
load_list --> get_text
|
||||
main --> write_list
|
||||
|
||||
main --> is_all_digits
|
||||
main --> tokenizer
|
||||
main --> check_in_list
|
||||
main --> remove_in_list
|
||||
main --> get_current
|
||||
main --> build_song
|
||||
|
||||
remove_in_list --> check_in_list
|
||||
```
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. It's hard to load each argument correctly. For example, song can include spaces, the singer can also have space or something else in their names. But luckily, we don't need care too much about the middle part. I mean the first argument is always the command. The rest of it is the song information we need to add / delete. I split the arguments / songs into parts by space. `<action> <song> <location>` and take the each part as needed.
|
||||
2. When I am moving / adding the song. It's possible that the song has a `current` string at the end of line (in the playlist file already). If we only check the song's name, it will not pass some test cases. For example, this is how I handle this case for `move` command.
|
||||
|
||||
```diff
|
||||
if (tokens[0] == "move") {
|
||||
if (is_all_digits(tokens.back())){
|
||||
//set target position
|
||||
int dest = std::stoi(tokens.back());
|
||||
//build song from tokens
|
||||
std::string song;
|
||||
song = build_song(tokens, 1, tokens.size() - 1);
|
||||
+ //fix song name if it has current tag
|
||||
+ if (!check_in_list(song, playlist) &&
|
||||
+ !check_in_list(song + " current", playlist)) {continue;}
|
||||
+ else if (check_in_list(song + " current", playlist)) {
|
||||
+ song += " current";
|
||||
+ }
|
||||
remove_in_list(song, playlist);
|
||||
playlist.insert(playlist.begin() + dest - 1, song);
|
||||
} else {
|
||||
std::cout << "ERROR: Missing move destination" << std::endl;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
I added another check with the song + `current` in the playlist before I actually add it into the playlist.
|
||||
|
||||
## Solution
|
||||
|
||||
### nyplaylists.cpp
|
||||
|
||||
```cpp
|
||||
//An implement of CSCI-1200 HW1 Spotify Playlists
|
||||
//Date: 2025/1/16
|
||||
//Author: JamesFlare
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
std::string get_text(const std::string &fname) {
|
||||
//load a text file into a string
|
||||
std::ifstream inFile(fname);
|
||||
//check if file exists
|
||||
if (!inFile) {
|
||||
std::cout << "Error: File not found" << std::endl;
|
||||
return "";
|
||||
}
|
||||
std::string text;
|
||||
std::string line;
|
||||
while (std::getline(inFile, line)) {
|
||||
text += line;
|
||||
text += "\n";
|
||||
}
|
||||
inFile.close();
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
std::vector<std::string> load_list(const std::string &fname) {
|
||||
//load a text file into a vector of strings
|
||||
std::string text = get_text(fname);
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::size_t start = 0;
|
||||
std::size_t end = 0;
|
||||
while ((end = text.find('\n', start)) != std::string::npos) {
|
||||
lines.push_back(text.substr(start, end - start));
|
||||
start = end + 1;
|
||||
}
|
||||
if (start < text.size()) {
|
||||
lines.push_back(text.substr(start));
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
bool is_all_digits(const std::string& s) {
|
||||
//check if string is int
|
||||
for (char c : s) {
|
||||
if (!std::isdigit(static_cast<unsigned char>(c))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !s.empty();
|
||||
}
|
||||
|
||||
std::vector<std::string> tokenizer(const std::string &s) {
|
||||
//split string into tokens
|
||||
std::vector<std::string> tokens;
|
||||
std::string token;
|
||||
for (char c : s) {
|
||||
if (c == ' ') {
|
||||
tokens.push_back(token);
|
||||
token = "";
|
||||
} else {
|
||||
token += c;
|
||||
}
|
||||
}
|
||||
tokens.push_back(token);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
bool check_in_list (const std::string &s, const std::vector<std::string> &list) {
|
||||
//check if string is in list
|
||||
for (std::string item : list) {
|
||||
if (s == item) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void remove_in_list (const std::string &s, std::vector<std::string> &list) {
|
||||
//remove string from list
|
||||
if (!check_in_list(s, list)) {return;}
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (list[i] == s) {
|
||||
list.erase(list.begin() + i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get_current (std::vector<std::string> &playlist) {
|
||||
//return the index of the string has word current at the end
|
||||
for (int i = 0; i < playlist.size(); i++) {
|
||||
if (playlist[i].find("current") != std::string::npos) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string build_song (const std::vector<std::string> &tokens, const int &start, const int &end) {
|
||||
//build string from tokens w/ start and end positions
|
||||
std::string song;
|
||||
for (int i = start; i < end; i++) {
|
||||
song += tokens[i];
|
||||
if (i != end - 1) {
|
||||
song += " ";
|
||||
}
|
||||
}
|
||||
return song;
|
||||
}
|
||||
|
||||
void write_list(const std::string &fname, const std::vector<std::string> &list) {
|
||||
//write list to file
|
||||
std::ofstream outFile(fname);
|
||||
for (std::string line : list) {
|
||||
outFile << line << std::endl;
|
||||
}
|
||||
outFile.close();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
//take 3 arguments
|
||||
if (argc < 3) {
|
||||
std::cout << "Error: Not enough arguments" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
//load arguments
|
||||
std::string playlist_fname = argv[1];
|
||||
std::string action_list_fname = argv[2];
|
||||
std::string output_fname = argv[3];
|
||||
//load working files
|
||||
std::vector<std::string> playlist = load_list(playlist_fname);
|
||||
std::vector<std::string> action_list = load_list(action_list_fname);
|
||||
//get current playing song id
|
||||
int current_song_id = get_current(playlist);
|
||||
//execute actions
|
||||
for (std::string command : action_list) {
|
||||
//split command into tokens
|
||||
std::vector<std::string> tokens = tokenizer(command);
|
||||
if (tokens[0] == "next") {
|
||||
current_song_id = get_current(playlist);
|
||||
//remove "current" tag
|
||||
playlist[current_song_id].erase(playlist[current_song_id].length() - 8);
|
||||
if (current_song_id == playlist.size() - 1) {
|
||||
current_song_id = 0;
|
||||
} else {
|
||||
current_song_id++;
|
||||
}
|
||||
//update current song
|
||||
playlist[current_song_id] += " current";
|
||||
}
|
||||
if (tokens[0] == "previous") {
|
||||
current_song_id = get_current(playlist);
|
||||
//remove "current" tag
|
||||
playlist[current_song_id].erase(playlist[current_song_id].length() - 8);
|
||||
if (current_song_id == 0) {
|
||||
current_song_id = playlist.size() - 1;
|
||||
} else {
|
||||
current_song_id--;
|
||||
}
|
||||
//update current song
|
||||
playlist[current_song_id] += " current";
|
||||
}
|
||||
if (tokens[0] == "add") {
|
||||
std::string song;
|
||||
song = build_song(tokens, 1, tokens.size());
|
||||
playlist.push_back(song);
|
||||
}
|
||||
if (tokens[0] == "remove") {
|
||||
std::string song;
|
||||
song = build_song(tokens, 1, tokens.size());
|
||||
remove_in_list(song, playlist);
|
||||
}
|
||||
if (tokens[0] == "move") {
|
||||
if (is_all_digits(tokens.back())){
|
||||
//set target position
|
||||
int dest = std::stoi(tokens.back());
|
||||
//build song from tokens
|
||||
std::string song;
|
||||
song = build_song(tokens, 1, tokens.size() - 1);
|
||||
//fix song name if it has current tag
|
||||
if (!check_in_list(song, playlist) &&
|
||||
!check_in_list(song + " current", playlist)) {continue;}
|
||||
else if (check_in_list(song + " current", playlist)) {
|
||||
song += " current";
|
||||
}
|
||||
remove_in_list(song, playlist);
|
||||
playlist.insert(playlist.begin() + dest - 1, song);
|
||||
} else {
|
||||
std::cout << "ERROR: Missing move destination" << std::endl;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
//write back file
|
||||
write_list(output_fname, playlist);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
BIN
content/en/posts/csci-1200/hw-1/spotify_playlists.7z
Normal file
|
After Width: | Height: | Size: 64 KiB |
1297
content/en/posts/csci-1200/hw-2/index.md
Normal file
BIN
content/en/posts/csci-1200/hw-2/ride_sharing.7z
Normal file
1206
content/en/posts/csci-1200/hw-3/index.md
Normal file
BIN
content/en/posts/csci-1200/hw-3/matrix1_array.avif
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
content/en/posts/csci-1200/hw-3/matrix_class.7z
Normal file
@@ -47,320 +47,320 @@ repost:
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Before Start
|
||||
## Introduction
|
||||
|
||||
LobeChat defaults to using IndexedDB to store user data, which means the data is stored locally in the browser. This results in the inability to synchronize data across multiple devices and the risk of data loss. At the same time, LobeChat has a server-side database version that addresses the aforementioned issues and also enables the use of a knowledge base feature.
|
||||
By default, LobeChat uses IndexedDB to store user data, meaning the data is stored locally in the browser. Consequently, it becomes impossible to synchronize across multiple devices and poses a risk of data loss. Meanwhile, there is a server database version of LobeChat that addresses these issues and also allows for knowledge base functionality.
|
||||
|
||||
However, configuring the LobeChat DB version is not straightforward. It involves several parts: configuring the database, setting up the authentication service, and configuring the S3 storage service [^1].
|
||||
However, configuring the LobeChat DB version isn't straightforward. It involves several parts: setting up the database, configuring authentication services, and configuring S3 storage[^1].
|
||||
|
||||
[^1]: Refer to the official documentation https://lobehub.com/en/docs/self-hosting/server-database
|
||||
[^1]: See official documentation https://lobehub.com/en/docs/self-hosting/server-database
|
||||
|
||||
## Configuring Logto
|
||||
|
||||
I recommend deploying the Logto service separately, as it may also be used in other projects and can be managed independently.
|
||||
I recommend deploying the Logto service separately to potentially use it in other projects and manage them independently.
|
||||
|
||||
First, create a new directory and navigate into it:
|
||||
First, create a directory and enter it:
|
||||
|
||||
```bash
|
||||
mkdir logto
|
||||
cd logto
|
||||
mkdir logto
|
||||
cd logto
|
||||
```
|
||||
|
||||
Here is my `docker-compose.yaml` file, which you can refer to and modify the relevant parts to suit your own needs:
|
||||
Here is my `docker-compose.yaml` file for reference. Modify the relevant parts according to your own setup.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
|
||||
postgresql:
|
||||
image: postgres:16
|
||||
container_name: logto-postgres
|
||||
volumes:
|
||||
- './data:/var/lib/postgresql/data'
|
||||
environment:
|
||||
- 'POSTGRES_DB=logto'
|
||||
- 'POSTGRES_PASSWORD=logto'
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: always
|
||||
|
||||
logto:
|
||||
image: svhd/logto:latest
|
||||
container_name: logto
|
||||
ports:
|
||||
- '127.0.0.1:3034:3034'
|
||||
- '127.0.0.1:3035:3035'
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- 'PORT=3034'
|
||||
- 'ADMIN_PORT=3035'
|
||||
- 'TRUST_PROXY_HEADER=1'
|
||||
- 'DB_URL=postgresql://postgres:logto@postgresql:5432/logto'
|
||||
- 'ENDPOINT=https://logto.example.com'
|
||||
- 'ADMIN_ENDPOINT=https://logto-admin.example.com'
|
||||
entrypoint: ['sh', '-c', 'npm run cli db seed -- --swe && npm start']
|
||||
services:
|
||||
|
||||
postgresql:
|
||||
image: postgres:16
|
||||
container_name: logto-postgres
|
||||
volumes:
|
||||
- './data:/var/lib/postgresql/data'
|
||||
environment:
|
||||
- 'POSTGRES_DB=logto'
|
||||
- 'POSTGRES_PASSWORD=logto'
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: always
|
||||
|
||||
logto:
|
||||
image: svhd/logto:latest
|
||||
container_name: logto
|
||||
ports:
|
||||
- '127.0.0.1:3034:3034'
|
||||
- '127.0.0.1:3035:3035'
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- 'PORT=3034'
|
||||
- 'ADMIN_PORT=3035'
|
||||
- 'TRUST_PROXY_HEADER=1'
|
||||
- 'DB_URL=postgresql://postgres:logto@postgresql:5432/logto'
|
||||
- 'ENDPOINT=https://logto.example.com'
|
||||
- 'ADMIN_ENDPOINT=https://logto-admin.example.com'
|
||||
entrypoint: ['sh', '-c', 'npm run cli db seed -- --swe && npm start']
|
||||
```
|
||||
|
||||
After making the modifications, write them into the `docker-compose.yaml` file. Then, run the container:
|
||||
After modifying, write the `docker-compose.yaml` file. Then start the container:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Remember to configure the reverse proxy correctly. This proxy must support HTTPS because Logto's various APIs must run in a secure environment, otherwise, errors will occur [^2]. Moreover, simply having HTTPS is not enough; you must also set the value of the `X-Forwarded-Proto` header to `https` to inform Logto that the user is accessing via HTTPS. I use Nginx as the reverse proxy service, and the following configuration can be referenced. Remember to modify the content according to your situation (e.g., `proxy_pass`).
|
||||
> [!WARNING]
|
||||
> Don't forget to set `X-Forwarded-Proto` header in Nginx!
|
||||
|
||||
this proxy must support HTTPS because all of Logto's APIs must be run in a secure environment, otherwise errors will occur[^2]. Additionally, just having HTTPS isn't enough; you also need to set the `X-Forwarded-Proto` header value to `https` to inform Logto that users are accessing it via HTTPS. I use Nginx as my reverse proxy service and provide a reference configuration below (modify according to your situation).
|
||||
|
||||
[^2]: Discussion on errors https://github.com/logto-io/logto/issues/4279
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
|
||||
proxy_pass http://127.0.0.1:3034;
|
||||
proxy_redirect off;
|
||||
}
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
|
||||
proxy_pass http://127.0.0.1:3034;
|
||||
proxy_redirect off;
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, this is just a small part of the configuration. If you are manually crafting the Nginx configuration file rather than managing it through a graphical tool like Nginx Proxy Manager, you will need to complete the other parts yourself. Do not simply copy and paste. In other words, if you are using Nginx Proxy Manager, you can directly copy the modified `proxy_pass` into the corresponding reverse proxy's Advanced configuration box.
|
||||
If you are manually configuring your Nginx configuration file rather than using a graphical tool like Nginx Proxy Manager, you need to complete other parts on your own (do not directly copy). In other words, if you use Nginx Proxy Manager, you can modify the `proxy_pass` and then directly input it into the Advanced settings of the corresponding reverse proxy.
|
||||
|
||||
Afterward, you can visit the ADMIN_ENDPOINT to complete registration and configuration (the first registered account will automatically become an administrator). Remember to add an Application (in preparation for the installation of the LobeChat DB version), and the type can be selected as Next.js (App Router). There are several key parameters that should not be written incorrectly (replace the domain name with your own LobeChat DB version instance):
|
||||
Afterwards, you can access the ADMIN_ENDPOINT to complete registration and configuration (the first registered account will automatically become an admin), remember to add an Application (prepare for LobeChat DB version installation), with a type selected as Next.js (App Router). Several key parameters should not be written incorrectly (replace domain names with your own LobeChat DB instance):
|
||||
|
||||
- `Redirect URIs` write `https://lobe.example.com/api/auth/callback/logto`
|
||||
- `Post sign-out redirect URIs` write `https://lobe.example.com/`
|
||||
- `CORS allowed origins` write `https://lobe.example.com`
|
||||
|
||||
There are three parameters that we will use later in the configuration of the LobeChat DB version: Issuer endpoint, App ID, and App secrets (this needs to be added). You can take note of them.
|
||||
There are three parameters that we will use when configuring the LobeChat DB version: Issuer endpoint, App ID, and App secrets (add one). Note them down.
|
||||
|
||||
You can also visit the user ENDPOINT's `/demo-app` path to test login, registration, and other functions to see if they are working properly. If everything is OK, then Logto is fine, and you can proceed with the following tasks.
|
||||
You can also visit the `/demo-app` path of your user ENDPOINT to test login and registration functions. If everything is fine, then Logto should be properly configured, allowing you to proceed with further steps.
|
||||
|
||||
## Configuring MinIO
|
||||
|
||||
I also recommend deploying MinIO separately, as it can be used for other projects.
|
||||
I recommend deploying MinIO separately for potential use in other projects as well.
|
||||
|
||||
Create a directory and navigate into it:
|
||||
Create a directory and enter it:
|
||||
|
||||
```bash
|
||||
mkdir minio
|
||||
cd minio
|
||||
mkdir minio
|
||||
cd minio
|
||||
```
|
||||
|
||||
Here is my `docker-compose.yaml` file, which you can refer to:
|
||||
Here is my `docker-compose.yaml` file for reference:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
|
||||
minio:
|
||||
image: quay.io/minio/minio
|
||||
container_name: minio
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MINIO_DOMAIN=minio.example.com
|
||||
- MINIO_SERVER_URL=https://minio.example.com
|
||||
- MINIO_BROWSER_REDIRECT_URL=https://console.minio.example.com
|
||||
- MINIO_ROOT_USER=xxxx #change it
|
||||
- MINIO_ROOT_PASSWORD=xxxxx #change it
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
command: server /data --console-address ":9090"
|
||||
services:
|
||||
|
||||
minio:
|
||||
image: quay.io/minio/minio
|
||||
container_name: minio
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MINIO_DOMAIN=minio.example.com
|
||||
- MINIO_SERVER_URL=https://minio.example.com
|
||||
- MINIO_BROWSER_REDIRECT_URL=https://console.minio.example.com
|
||||
- MINIO_ROOT_USER=xxxx #change it
|
||||
- MINIO_ROOT_PASSWORD=xxxxx #change it
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
command: server /data --console-address ":9090"
|
||||
```
|
||||
|
||||
After making the modifications, write them into the `docker-compose.yaml` file. Then, run the container:
|
||||
After modifying, write the `docker-compose.yaml` file. Then start the container:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Next, log in to your MinIO instance from your MINIO_BROWSER_REDIRECT_URL, create a Bucket, and name it `lobe` as an example. If you change it to something else, remember to modify the corresponding configuration files.
|
||||
|
||||
In the Access Policy, choose Custom and fill in a configuration similar to the following (using `lobe` as the Bucket name):
|
||||
Subsequently, log into your MinIO instance from your MINIO_BROWSER_REDIRECT_URL, create a Bucket (e.g., name it as `lobe`; if you change this, remember to modify corresponding configuration files), and configure an Access Policy similar to the following JSON file:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"Action": [
|
||||
"s3:GetBucketLocation"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::lobe"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"Action": [
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::lobe"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"s3:prefix": [
|
||||
"files/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"Action": [
|
||||
"s3:DeleteObject",
|
||||
"s3:GetObject",
|
||||
"s3:PutObject"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::lobe/files/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"Action": [
|
||||
"s3:GetBucketLocation"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::lobe"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"Action": [
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::lobe"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"s3:prefix": [
|
||||
"files/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"Action": [
|
||||
"s3:DeleteObject",
|
||||
"s3:GetObject",
|
||||
"s3:PutObject"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::lobe/files/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Then, go to Access Keys and create a token. Please save the values here, as they will be used in the subsequent configuration of the LobeChat DB version.
|
||||
Then go to Access Keys and create a token, save these values as they will be used in the LobeChat DB version configuration.
|
||||
|
||||
## Configuring the LobeChat DB Version
|
||||
## Configuring LobeChat DB Version
|
||||
|
||||
Next, we will start configuring the LobeChat DB version. First, create a directory and navigate into it:
|
||||
Now we start configuring the LobeChat DB version. First, create a directory and enter it:
|
||||
|
||||
```bash
|
||||
mkdir lobe-db
|
||||
cd lobe-db
|
||||
mkdir lobe-db
|
||||
cd lobe-db
|
||||
```
|
||||
|
||||
Here is the `docker-compose.yaml` configuration file I use. Remember to modify it to your values:
|
||||
Here is my `docker-compose.yaml` file for reference; remember to modify according to your setup:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
|
||||
postgresql:
|
||||
image: pgvector/pgvector:pg16
|
||||
container_name: lobe-postgres
|
||||
volumes:
|
||||
- './data:/var/lib/postgresql/data'
|
||||
environment:
|
||||
- 'POSTGRES_DB=lobe-db'
|
||||
- 'POSTGRES_PASSWORD=lobe-db'
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: always
|
||||
|
||||
lobe:
|
||||
image: lobehub/lobe-chat-database
|
||||
container_name: lobe-database
|
||||
ports:
|
||||
- 127.0.0.1:3033:3210
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
|
||||
environment:
|
||||
- 'APP_URL=https://lobe-db.example.com'
|
||||
- 'NEXT_AUTH_SSO_PROVIDERS=logto'
|
||||
- 'KEY_VAULTS_SECRET=NIdSgLKmeFhWmTuQKQYzn99oYk64aY0JTSssZuiWR8A=' #generate using `openssl rand -base64 32`
|
||||
- 'NEXT_AUTH_SECRET=+IHNVxT2qZpA8J+vnvuwA5Daqz4UFFJOahK6z/GsNIo=' #generate using `openssl rand -base64 32`
|
||||
- 'NEXTAUTH_URL=https://lobe.example.com/api/auth'
|
||||
- 'LOGTO_ISSUER=https://logto.example.com/oidc' #Issuer endpoint
|
||||
- 'LOGTO_CLIENT_ID=xxxx' #App ID
|
||||
- 'LOGTO_CLIENT_SECRET=xxxx' #App secrets
|
||||
- 'DATABASE_URL=postgresql://postgres:lobe-db@postgresql:5432/lobe-db'
|
||||
- 'POSTGRES_PASSWORD=lobe-db'
|
||||
- 'LOBE_DB_NAME=lobe-db'
|
||||
- 'S3_ENDPOINT=https://minio.example.com'
|
||||
- 'S3_BUCKET=lobe'
|
||||
- 'S3_PUBLIC_DOMAIN=https://minio.example.com'
|
||||
- 'S3_ACCESS_KEY_ID=xxxxx'
|
||||
- 'S3_SECRET_ACCESS_KEY=xxxxxx'
|
||||
- 'S3_ENABLE_PATH_STYLE=1'
|
||||
- 'OPENAI_API_KEY=sk-xxxxxx' #your OpenAI API Key
|
||||
- 'OPENAI_PROXY_URL=https://api.openai.com/v1'
|
||||
- 'OPENAI_MODEL_LIST=-all,+gpt-4o,+gpt-4o-mini,+claude-3-5-sonnet-20240620,+deepseek-chat,+o1-preview,+o1-mini' #change on your own needs, see https://lobehub.com/zh/docs/self-hosting/environment-variables/model-provider#openai-model-list
|
||||
restart: always
|
||||
services:
|
||||
|
||||
postgresql:
|
||||
image: pgvector/pgvector:pg16
|
||||
container_name: lobe-postgres
|
||||
volumes:
|
||||
- './data:/var/lib/postgresql/data'
|
||||
environment:
|
||||
- 'POSTGRES_DB=lobe-db'
|
||||
- 'POSTGRES_PASSWORD=lobe-db'
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: always
|
||||
|
||||
lobe:
|
||||
image: lobehub/lobe-chat-database
|
||||
container_name: lobe-database
|
||||
ports:
|
||||
- 127.0.0.1:3033:3210
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
|
||||
environment:
|
||||
- 'APP_URL=https://lobe-db.example.com'
|
||||
- 'NEXT_AUTH_SSO_PROVIDERS=logto'
|
||||
- 'KEY_VAULTS_SECRET=NIdSgLKmeFhWmTuQKQYzn99oYk64aY0JTSssZuiWR8A=' #generate using `openssl rand -base64 32`
|
||||
- 'NEXT_AUTH_SECRET=+IHNVxT2qZpA8J+vnvuwA5Daqz4UFFJOahK6z/GsNIo=' #generate using `openssl rand -base64 32`
|
||||
- 'NEXTAUTH_URL=https://lobe.example.com/api/auth'
|
||||
- 'LOGTO_ISSUER=https://logto.example.com/oidc' #Issuer endpoint
|
||||
- 'LOGTO_CLIENT_ID=xxxx' #App ID
|
||||
- 'LOGTO_CLIENT_SECRET=xxxx' #App secrets
|
||||
- 'DATABASE_URL=postgresql://postgres:lobe-db@postgresql:5432/lobe-db'
|
||||
- 'POSTGRES_PASSWORD=lobe-db'
|
||||
- 'LOBE_DB_NAME=lobe-db'
|
||||
- 'S3_ENDPOINT=https://minio.example.com'
|
||||
- 'S3_BUCKET=lobe'
|
||||
- 'S3_PUBLIC_DOMAIN=https://minio.example.com'
|
||||
- 'S3_ACCESS_KEY_ID=xxxxx'
|
||||
- 'S3_SECRET_ACCESS_KEY=xxxxxx'
|
||||
- 'S3_ENABLE_PATH_STYLE=1'
|
||||
- 'OPENAI_API_KEY=sk-xxxxxx' #your OpenAI API Key
|
||||
- 'OPENAI_PROXY_URL=https://api.openai.com/v1'
|
||||
- 'OPENAI_MODEL_LIST=-all,+gpt-4o,+gpt-4o-mini,+claude-3-5-sonnet-20240620,+deepseek-chat,+o1-preview,+o1-mini' #change on your own needs, see https://lobehub.com/zh/docs/self-hosting/environment-variables/model-provider#openai-model-list
|
||||
restart: always
|
||||
```
|
||||
|
||||
For security reasons, `KEY_VAULTS_SECRET` and `NEXT_AUTH_SECRET` need to be a random 32-character string, which can be generated by running the command `openssl rand -base64 32`.
|
||||
For security reasons, `KEY_VAULTS_SECRET` and `NEXT_AUTH_SECRET` should be a random 32-character string. You can generate it using the command `openssl rand -base64 32`.
|
||||
|
||||
Then, change the domain names in the environment variables to your own. In addition, there are several Logto values, which are related as follows:
|
||||
Then modify domain names in environment variables to your own setup. Additionally, several Logto values need to be set:
|
||||
|
||||
- `Issuer endpoint` corresponds to `LOGTO_ISSUER`
|
||||
- `App ID` corresponds to `LOGTO_CLIENT_ID`
|
||||
- `App secrets` corresponds to `LOGTO_CLIENT_SECRET`
|
||||
|
||||
These can all be found on the created Application page.
|
||||
These can all be found on the Application page you created.
|
||||
|
||||
Regarding the S3 configuration, remember to modify it, such as `S3_ENDPOINT`, `S3_BUCKET`, `S3_PUBLIC_DOMAIN`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`. As for `S3_ENABLE_PATH_STYLE`, it is generally `1`. If your S3 provider uses virtual-host, change it to `0`.
|
||||
For S3 configuration, also modify accordingly (e.g., `S3_ENDPOINT`, `S3_BUCKET`, `S3_PUBLIC_DOMAIN`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`). As for `S3_ENABLE_PATH_STYLE`, it is usually set to `1`. If your S3 provider uses virtual-host style, change this value to `0`.
|
||||
|
||||
{{< admonition type=question title="What is the difference between path-style and virtual-host?" open=true >}}
|
||||
path-style and virtual-host are different ways to access buckets and objects in S3, with different URL structures and domain name resolutions.
|
||||
{{< admonition type=question title="What are the differences between path-style and virtual-host?" open=true >}}
|
||||
Path-style and virtual-host are different ways of accessing buckets and objects in S3. The URL structure and domain name resolution differ:
|
||||
|
||||
Assuming the S3 provider's domain is s3.example.net, the bucket is mybucket, and the object is config.env, the specific differences are as follows:
|
||||
|
||||
- path-style: s3.example.net/mybucket/config.env
|
||||
- virtual-host: mybucket.s3.example.net/config.env
|
||||
Assuming your S3 provider's domain is s3.example.net, bucket is mybucket, object is config.env, the specific differences are as follows:
|
||||
- Path-style: `s3.example.net/mybucket/config.env`
|
||||
- Virtual-host: `mybucket.s3.example.net/config.env`
|
||||
{{< /admonition >}}
|
||||
|
||||
Finally, configure your API-related content (optional), which is exemplified in my configuration for using OpenAI. If you do not configure it on the server side, users will need to fill it in themselves on the frontend.
|
||||
Finally, configure your API-related content (optional). My configuration example uses OpenAI. If you do not set it up on the server side, users will need to enter their own keys in the frontend.
|
||||
|
||||
After making the modifications, write them into the `docker-compose.yaml` file. Then, run the container:
|
||||
After modifying, write the `docker-compose.yaml` file and start the container:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
In theory, you should now be able to access and use the LobeChat DB version. If you need to deploy it in a production environment, please carefully check to ensure there are no security issues. If you have any questions, feel free to comment.
|
||||
In theory, you can now access LobeChat DB version. Before deploying to production, carefully check for any security issues. If you have questions, feel free to comment.
|
||||
|
||||
## Extended Content
|
||||
## Additional Content
|
||||
|
||||
### Customizing Logto Login/Registration Options
|
||||
|
||||
In the Logto management page, you can see a Sign-in experience, where there are various customization options, such as enabling registration, disabling registration, and using social media SSO. The default Sign-up identifier is Username. I recommend changing it to Email address after configuring SMTP in Connectors, otherwise, users will not be able to retrieve their passwords via email, and forgetting the password will be problematic.
|
||||
On the Logto management page, there is a Sign-in experience section with various customization options such as enabling or disabling registration and using social media SSO. By default, the Sign-up identifier is Username; I recommend configuring SMTP in Connectors to change it to Email address so users can recover their passwords via email.
|
||||
|
||||
### Enabling Dark Mode for Logto Login/Registration Pages
|
||||
|
||||
In the Logto management page, you can see a Sign-in experience, where checking Enable dark mode will enable dark mode.
|
||||
On the Logto management page, under Sign-in experience, check Enable dark mode to turn on dark mode.
|
||||
|
||||
### Enabling GitHub Login/Registration Options for Logto
|
||||
### Adding GitHub Login/Registration Options in Logto
|
||||
|
||||
In the Logto management page, you can see a Connectors, where you can add GitHub in the Social connectors, and the same applies to others.
|
||||
In the Logto management page, go to Connectors and add GitHub under Social connectors. Other options are similar.
|
||||
|
||||
### Configuring More Models
|
||||
### Configuring Additional Models
|
||||
|
||||
LobeChat supports many models, and you can set different environment variables to start them. You can refer to the official documentation [LobeChat Model Service Providers - Environment Variables and Configuration](https://lobehub.com/en/docs/self-hosting/environment-variables/model-provider) for the configuration options and explanations of `OPENAI_MODEL_LIST`. Of course, there are also options for other model providers, such as DeepSeek.
|
||||
LobeChat supports many models; you can set different environment variables to enable them. See the official documentation for `OPENAI_MODEL_LIST` configuration options and explanations [here](https://lobehub.com/en/docs/self-hosting/environment-variables/model-provider). There are also other model provider options like DeepSeek.
|
||||
|
||||
Of course, you can also obtain the Model List through the API on the frontend and then select the required models.
|
||||
You can retrieve Model List via API on the frontend to select needed models.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
BIN
content/en/posts/ecse-1010/lab01/Lab01.pdf
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 504 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 541 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 876 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 674 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 36 KiB |
1159
content/en/posts/ecse-1010/lab01/index.md
Normal file
BIN
content/en/posts/ecse-1010/lab02/Lab02.pdf
Normal file
BIN
content/en/posts/ecse-1010/lab02/P1-1-a.avif
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-1-b-2.avif
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-1-b.avif
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-1-c-2.avif
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-1-c.avif
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-2-a.avif
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-2-b.avif
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-3-a.avif
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-a-2.avif
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-a.avif
Normal file
|
After Width: | Height: | Size: 368 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-b.avif
Normal file
|
After Width: | Height: | Size: 287 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-c-2.avif
Normal file
|
After Width: | Height: | Size: 400 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-c.avif
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-d-2.avif
Normal file
|
After Width: | Height: | Size: 478 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-d.avif
Normal file
|
After Width: | Height: | Size: 532 KiB |
270
content/en/posts/ecse-1010/lab02/P1-4-d.svg
Normal file
|
After Width: | Height: | Size: 114 KiB |
272
content/en/posts/ecse-1010/lab02/P1-4-e.svg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
content/en/posts/ecse-1010/lab02/P2-2-a.avif
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
220
content/en/posts/ecse-1010/lab02/P2-4-c-2.svg
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
content/en/posts/ecse-1010/lab02/P3-1-a.avif
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
content/en/posts/ecse-1010/lab02/P3-3-a.avif
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
content/en/posts/ecse-1010/lab02/P3-4-a.avif
Normal file
|
After Width: | Height: | Size: 542 KiB |
BIN
content/en/posts/ecse-1010/lab02/P3-4-b-2.avif
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
content/en/posts/ecse-1010/lab02/P3-4-b-3.avif
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
content/en/posts/ecse-1010/lab02/P3-4-b.avif
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
content/en/posts/ecse-1010/lab02/P4-1-a.avif
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
content/en/posts/ecse-1010/lab02/P4-2-a.avif
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
content/en/posts/ecse-1010/lab02/P4-3-a.avif
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
content/en/posts/ecse-1010/lab02/P4-4-a.avif
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
content/en/posts/ecse-1010/lab02/P4-4-b-1.avif
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
content/en/posts/ecse-1010/lab02/P4-4-b-2.avif
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
content/en/posts/ecse-1010/lab02/P5-2-a.avif
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
content/en/posts/ecse-1010/lab02/P5-3-a.avif
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
content/en/posts/ecse-1010/lab02/P5-3-b.avif
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
content/en/posts/ecse-1010/lab02/P5-4-a-1.avif
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
content/en/posts/ecse-1010/lab02/P5-4-a.avif
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
content/en/posts/ecse-1010/lab02/P6-1-a.avif
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
content/en/posts/ecse-1010/lab02/P6-3-a.avif
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
content/en/posts/ecse-1010/lab02/P6-3-b.avif
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
content/en/posts/ecse-1010/lab02/P6-4-a-b.avif
Normal file
|
After Width: | Height: | Size: 279 KiB |
BIN
content/en/posts/ecse-1010/lab02/P6-4-a.avif
Normal file
|
After Width: | Height: | Size: 260 KiB |
BIN
content/en/posts/ecse-1010/lab02/P8-1-a.avif
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
content/en/posts/ecse-1010/lab02/P8-2-a.avif
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
content/en/posts/ecse-1010/lab02/P8-3-a.avif
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
content/en/posts/ecse-1010/lab02/P8-4-a-b.avif
Normal file
|
After Width: | Height: | Size: 1009 KiB |