Skip to content

Commit

Permalink
Release v1.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
wb14123 committed Nov 10, 2024
1 parent 6e16da1 commit e049d2a
Show file tree
Hide file tree
Showing 27 changed files with 304 additions and 94 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ libraryDependencies ++= Seq(
"org.tpolecat" %% "doobie-specs2" % doobieVersion,
"org.tpolecat" %% "doobie-hikari" % doobieVersion,
"io.getquill" %% "quill-doobie" % "4.8.4",
"org.postgresql" % "postgresql" % "42.7.3",
"org.postgresql" % "postgresql" % "42.7.4",
"io.getquill" %% "quill-cassandra-monix" % "4.8.4",

// search
Expand Down
20 changes: 20 additions & 0 deletions js/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,9 @@ popover-content a:hover {
.search-options {
width: 100%;
font-size: 12px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}

.search-options * {
Expand All @@ -952,6 +955,12 @@ popover-content a:hover {

.search-options select {
margin-right: 20px;
padding-right: 60px !important;
}

.search-option {
display: flex;
flex-direction: row;
}

/* audio player */
Expand Down Expand Up @@ -1009,3 +1018,14 @@ popover-content a:hover {
.nsfw-show .nsfw .article-desc-nsfw {
display: none;
}

.text-with-icon {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
}

.show-comment-btn {
font-size: 18px;
}
3 changes: 3 additions & 0 deletions js/src/match-id.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ window.getPositionBefore = getPositionBefore;

function getNextFolderPosition() {
const folders = getFoldersFromDom();
if (folders.length == 0) {
return POS_STEP;
}
const lastFolder = folders[folders.length - 1];
return lastFolder.position + POS_STEP;
}
Expand Down
29 changes: 29 additions & 0 deletions src/main/protobuf/grpc-api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ message User {
bool subscribed = 16;
NSFWSetting nsfwSetting = 17;
SearchEngine searchEngine = 18;
optional LLMEngine llmEngine = 19;
optional string llmApiKey = 20;
}

// Define UserInfo
Expand All @@ -328,6 +330,7 @@ message UserInfo {
bool subscribed = 10;
NSFWSetting nsfwSetting = 11;
SearchEngine searchEngine = 12;
optional LLMEngine llmEngine = 13;
}

// Define UserUpdater
Expand Down Expand Up @@ -368,6 +371,16 @@ message UserUpdater {
optional string username = 13;
optional NSFWSetting nsfwSetting = 14;
optional SearchEngine searchEngine = 15;

message LlmEngineOption {
optional LLMEngine llmEngineOption = 1;
}
optional LlmEngineOption llmEngine = 16;

message LlmApiKeyOption {
optional string llmApiKeyOption = 1;
}
optional LlmApiKeyOption llmApiKey = 17;
}

// Define UserSession
Expand Down Expand Up @@ -572,6 +585,12 @@ message ImportFailedSource {
string error = 2;
}

// Define LLMEngine

enum LLMEngine {
OpenAI = 0;
}

// Define me.binwang.rss.service.ArticleService

message GetArticlesBySourceRequest {
Expand Down Expand Up @@ -1275,6 +1294,16 @@ message UpdateUserSettingsRequest {
string token = 1;
optional NSFWSetting nsfwSetting = 2;
optional SearchEngine searchEngine = 3;

message LlmEngineOption {
optional LLMEngine llmEngineOption = 1;
}
optional LlmEngineOption llmEngine = 4;

message LlmApiKeyOption {
optional string llmApiKeyOption = 1;
}
optional LlmApiKeyOption llmApiKey = 5;
}

message RemoveCurrentFolderAndSourceRequest {
Expand Down
3 changes: 1 addition & 2 deletions src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ search {
}

open-ai {
apiKey = ""
model = "gpt-3.5-turbo"
model = "gpt-4o-mini"
}

image-proxy {
Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/me/binwang/rss/cmd/Services.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package me.binwang.rss.cmd
import cats.effect.{IO, Resource}
import cats.implicits._
import com.typesafe.config.ConfigFactory
import me.binwang.rss.llm.OpenAILLM
import me.binwang.rss.llm.{LLMModels, OpenAILLM}
import me.binwang.rss.model.ImportLimit
import me.binwang.rss.service._
import me.binwang.rss.sourcefinder.{HtmlSourceFinder, MultiSourceFinder, RegexSourceFinder}
Expand Down Expand Up @@ -37,7 +37,7 @@ object Services {
def apply(baseServer: BaseServer): Resource[IO, Services] = {

val authorizer: Authorizer = new Authorizer(throttler, baseServer.userSessionDao, baseServer.folderDao)
val llm = new OpenAILLM(baseServer.sttpBackend)
val llmModels = LLMModels(openAI = new OpenAILLM(baseServer.sttpBackend))

val importLimit = ImportLimit(
paidFolderCount = Try(config.getInt("import.limit.paid-user-folders")).toOption,
Expand All @@ -54,7 +54,7 @@ object Services {

new Services(
new ArticleService(baseServer.articleDao, baseServer.articleContentDao, baseServer.articleUserMarkingDao,
baseServer.articleSearchDao, llm, authorizer),
baseServer.articleSearchDao, baseServer.userDao, llmModels, authorizer),
new FolderService(baseServer.folderDao, baseServer.folderSourceDao,
baseServer.sourceDao, baseServer.importSourcesTaskDao, authorizer, importLimit),
new SourceService(baseServer.sourceDao, baseServer.folderSourceDao, baseServer.folderDao, baseServer.fetcher,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class ArticleEmbeddingTaskSqlDao(implicit val connectionPool: ConnectionPool) ex
.filter(_.status == lift(EmbeddingUpdateStatus.PENDING))
.sortBy(_.scheduledAt)(Ord.asc)
.take(lift(size))
.forUpdate()
}

val q = for {
Expand Down
8 changes: 8 additions & 0 deletions src/main/scala/me/binwang/rss/dao/sql/BaseSqlDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import me.binwang.rss.model.ArticleOrder.ArticleOrder
import me.binwang.rss.model.EmbeddingUpdateStatus.EmbeddingUpdateStatus
import me.binwang.rss.model.FetchStatus.FetchStatus
import me.binwang.rss.model.ID.ID
import me.binwang.rss.model.LLMEngine.LLMEngine
import me.binwang.rss.model.MoreLikeThisType.MoreLikeThisType
import me.binwang.rss.model.NSFWSetting.NSFWSetting
import me.binwang.rss.model._
Expand Down Expand Up @@ -95,6 +96,13 @@ trait BaseSqlDao {
protected implicit val encodeNsfwSetting: MappedEncoding[NSFWSetting, String] =
MappedEncoding[NSFWSetting, String](_.toString)

protected implicit val decodeLLMEngine: MappedEncoding[String, LLMEngine] =
MappedEncoding[String, LLMEngine](LLMEngine.withName)

protected implicit val encodeLLMSetting: MappedEncoding[LLMEngine, String] =
MappedEncoding[LLMEngine, String](_.toString)


protected implicit val mediaGroupsEncoder: Encoder[MediaGroups] = encoder(java.sql.Types.OTHER, (index, mediaGroups, row) => {
val value = io.circe.syntax.EncoderOps(mediaGroups).asJson.toString()
val pgObj = new PGobject()
Expand Down
1 change: 1 addition & 0 deletions src/main/scala/me/binwang/rss/dao/sql/SourceSqlDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class SourceSqlDao(implicit val connectionPool: ConnectionPool) extends SourceDa
.filter(_.fetchStatus == lift(FetchStatus.SCHEDULED))
.sortBy(source => source.fetchScheduledAt) (Ord.asc)
.take(lift(size))
.forUpdate()
}
/*
This doesn't work because it returns String instead of List[String]
Expand Down
6 changes: 5 additions & 1 deletion src/main/scala/me/binwang/rss/dao/sql/UserSqlDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class UserSqlDao(implicit val connectionPool: ConnectionPool) extends UserDao wi
subscribeEndAt timestamp not null,
subscribed boolean not null default false,
nsfwSetting varchar not null default 'BLUR',
searchEngine jsonb not null
searchEngine jsonb not null,
llmEngine varchar default null,
llmApiKey varchar default null
)
""")
.update
Expand Down Expand Up @@ -94,6 +96,8 @@ class UserSqlDao(implicit val connectionPool: ConnectionPool) extends UserDao wi
setOpt(_.username, updater.username),
setOpt(_.nsfwSetting, updater.nsfwSetting),
setOpt(_.searchEngine, updater.searchEngine),
setOpt(_.llmEngine, updater.llmEngine),
setOpt(_.llmApiKey, updater.llmApiKey),
)
run(q).transact(xa).map(_ > 0)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import me.binwang.rss.model.ArticleListLayout.ArticleListLayout
import me.binwang.rss.model.ArticleOrder.ArticleOrder
import me.binwang.rss.model.FetchStatus.FetchStatus
import me.binwang.rss.model.ID.ID
import me.binwang.rss.model.LLMEngine.LLMEngine
import me.binwang.rss.model.MoreLikeThisType.MoreLikeThisType
import me.binwang.rss.model.NSFWSetting.NSFWSetting
import me.binwang.rss.model._
Expand Down Expand Up @@ -88,6 +89,7 @@ object GenerateGRPC extends GRPCGenerator {
typeOf[SearchTerms],
typeOf[ImportSourcesTask],
typeOf[ImportFailedSource],
typeOf[LLMEngine],
)

override val serviceClasses: Seq[Type] = Seq(
Expand Down
12 changes: 12 additions & 0 deletions src/main/scala/me/binwang/rss/llm/LLMModels.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package me.binwang.rss.llm

import me.binwang.rss.model.LLMEngine
import me.binwang.rss.model.LLMEngine.LLMEngine

case class LLMModels(
openAI: OpenAILLM,
) {
def getModel(llmEngine: LLMEngine): LargeLanguageModel = llmEngine match {
case LLMEngine.OpenAI => openAI
}
}
20 changes: 10 additions & 10 deletions src/main/scala/me/binwang/rss/llm/LargeLanguageModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package me.binwang.rss.llm

import cats.effect.IO
import me.binwang.rss.llm.LargeLanguageModel.initMessage
import me.binwang.rss.model.Article
import org.typelevel.log4cats.LoggerFactory

case class ChatMessage(
Expand All @@ -22,27 +23,26 @@ trait LargeLanguageModel {
private val logger = LoggerFactory.getLoggerFromClass[IO](this.getClass)

// return multiple choices based on previous message
def chat(messages: Seq[ChatMessage]): IO[Seq[ChatMessage]]
def chat(messages: Seq[ChatMessage], apiKey: String): IO[Seq[ChatMessage]]

def getRecommendSearchQueries(likedArticleTitles: Seq[String], size: Int): IO[Seq[String]] = {
// TODO: replace "videos" in prompt based on the type of articles
def getRecommendSearchQueries(articles: Seq[Article], size: Int, apiKey: String): IO[Seq[String]] = {
val interestQueryStr =
"""Here are some recent videos in a user's subscription feed, what is his interest?
"""Here are some recent posts in a user's subscription feed, what is his interest?
| Which languages the user speak?""".stripMargin + "\n\n" +
likedArticleTitles.map(t => s"* $t").mkString("\n")
articles.map(a => s"* ${a.title}, posted at ${a.postedAt.toLocalDateTime}").mkString("\n")
val interestQuery = ChatMessage(role = "user", content = interestQueryStr)
val initReq = Seq(initMessage, interestQuery)
for {
interestChoices <- chat(initReq)
interestChoices <- chat(initReq, apiKey)
recommendQueryStr = s"""Based on that, could you help me to come with $size search queries so that I can find more
| interesting videos for him/her on the Internet? Better to use all the languages the user speaks.
| Show only the search queries without any explanation. One query per line.""".stripMargin
| interesting articles or videos for him/her on the Internet? Better to use all the languages the user speaks.
| Show only the search queries without any explanation. One query per line. Do not include number or quotes""".stripMargin
recommendQuery = ChatMessage(role = "user", content = recommendQueryStr)
nextQueries = initReq :+ interestChoices.head :+ recommendQuery
recommendChoices <- chat(nextQueries)
recommendChoices <- chat(nextQueries, apiKey)
// TODO: change to debug (or output to a seperate log file) after tuning results
_ <- logger.info(s"LLM prompts and results for recommend search. Query: $nextQueries, result: $recommendChoices")
result = recommendChoices.head.content.split('\n').map(removeLeadingListNumber)
result = recommendChoices.head.content.split('\n').map(removeLeadingListNumber).filter(_.nonEmpty)
} yield result
}

Expand Down
3 changes: 1 addition & 2 deletions src/main/scala/me/binwang/rss/llm/OpenAILLM.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ case class OpenAIChatResponse(

class OpenAILLM(backend: SttpBackend[IO, _])(implicit val loggerFactory: LoggerFactory[IO]) extends LargeLanguageModel {

private val apiKey = ConfigFactory.load().getString("open-ai.apiKey")
private val model = ConfigFactory.load().getString("open-ai.model")

override def chat(messages: Seq[ChatMessage]): IO[Seq[ChatMessage]] = {
override def chat(messages: Seq[ChatMessage], apiKey: String): IO[Seq[ChatMessage]] = {
val req = OpenAIChatRequest(model, messages)
basicRequest
.post(uri"https://api.openai.com/v1/chat/completions")
Expand Down
3 changes: 3 additions & 0 deletions src/main/scala/me/binwang/rss/model/Errors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ final case class UserCannotBeActivated(email: String)
final case class UserDeleteCodeInvalidException(code: String)
extends ServerException(code = 20009, msg = s"User delete verification code $code is not valid")

final case class LLMEngineNotConfigured(userID: String)
extends ServerException(code = 20010, msg = s"LLM engine not configured for user $userID")

// Source service

final case class SourceNotFound(sourceID: String)
Expand Down
18 changes: 16 additions & 2 deletions src/main/scala/me/binwang/rss/model/User.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package me.binwang.rss.model

import me.binwang.rss.model.LLMEngine.LLMEngine

import java.time.ZonedDateTime
import java.util.UUID

import me.binwang.rss.model.NSFWSetting.NSFWSetting

object NSFWSetting extends Enumeration {
Expand All @@ -14,6 +15,13 @@ object NSFWSetting extends Enumeration {
= Value
}

object LLMEngine extends Enumeration {
type LLMEngine = Value
val
OpenAI
= Value
}

case class SearchEngine(name: Option[String], urlPrefix: String)

object SearchEngine {
Expand Down Expand Up @@ -45,6 +53,8 @@ case class User (
subscribed: Boolean = false,
nsfwSetting: NSFWSetting = NSFWSetting.BLUR,
searchEngine: SearchEngine = SearchEngine.DEFAULT,
llmEngine: Option[LLMEngine] = None,
llmApiKey: Option[String] = None,
) {

def toInfo: UserInfo = {
Expand All @@ -61,6 +71,7 @@ case class User (
subscribed = subscribed,
nsfwSetting = nsfwSetting,
searchEngine = searchEngine,
llmEngine = llmEngine,
)
}

Expand All @@ -78,7 +89,8 @@ case class UserInfo (
currentSourceID: Option[String] = None,
subscribed: Boolean = false,
nsfwSetting: NSFWSetting = NSFWSetting.BLUR,
searchEngine: SearchEngine = SearchEngine.DUCKDUCKGO,
searchEngine: SearchEngine = SearchEngine.DEFAULT,
llmEngine: Option[LLMEngine] = None,
)

case class UserUpdater (
Expand All @@ -97,4 +109,6 @@ case class UserUpdater (
username: Option[String] = None,
nsfwSetting: Option[NSFWSetting] = None,
searchEngine: Option[SearchEngine] = None,
llmEngine: Option[Option[LLMEngine]] = None,
llmApiKey: Option[Option[String]] = None,
)
Loading

0 comments on commit e049d2a

Please sign in to comment.