163 lines
6.2 KiB
Bash
163 lines
6.2 KiB
Bash
#!/bin/sh
|
|||
|
|
# shee.sh: a shell-based OpenAI-compatible local chat
|
||
|
|
# Depends on curl, jq, file and mktemp, otherwise fully POSIX-compatible
|
||
|
|
# (for history/line editing, run it via rlwrap)
|
||
|
|
# Configurable in-chat and with environment variables
|
||
|
|
# Created by Luxferre in 2025, released into public domain
|
||
|
|
|
||
|
|
# all configuration variables are here
|
||
|
|
|
||
|
|
[ -z "$SHEESH_API_ROOT" ] && SHEESH_API_ROOT='https://text.pollinations.ai/openai'
|
||
|
|
[ -z "$SHEESH_MODEL" ] && SHEESH_MODEL='openai-fast'
|
||
|
|
[ -z "$SHEESH_TEMP" ] && SHEESH_TEMP='0.6'
|
||
|
|
[ -z "$SHEESH_SYSPROMPT" ] && SHEESH_SYSPROMPT='You are a helpful assistant.'
|
||
|
|
|
||
|
|
histfile="$(mktemp -t furrfu.XXXXXX)"
|
||
|
|
datafile="$(mktemp -t furrfu.XXXXXX)"
|
||
|
|
addhistory() {
|
||
|
|
role="$1"; shift
|
||
|
|
jq -nc --slurpfile h $histfile --arg r "$role" --arg a "$*" '$h[0]+[{"role":$r,"content":$a}]' > ${histfile}.new
|
||
|
|
mv ${histfile}.new $histfile
|
||
|
|
}
|
||
|
|
|
||
|
|
prompt() { printf "\e[1;32m>> \e[0m"; }
|
||
|
|
clearhist() {
|
||
|
|
printf '[]' > $histfile
|
||
|
|
addhistory system "$SHEESH_SYSPROMPT"
|
||
|
|
}
|
||
|
|
startswith() { case $1 in "$2"*) true;; *) false;; esac; }
|
||
|
|
setsys() {
|
||
|
|
SHEESH_SYSPROMPT="$*"
|
||
|
|
clearhist
|
||
|
|
}
|
||
|
|
setsysfile() { setsys "$(<"$1")"; }
|
||
|
|
addfile() {
|
||
|
|
tmp="$(mktemp -t furrfufile.XXXXXXX)"
|
||
|
|
fname="$(basename $1)"
|
||
|
|
if startswith "$1" "http*://"; then
|
||
|
|
[ -z "$fname" || "$fname" == "/" ] && $fname="${1//\//_}"
|
||
|
|
curl -sSLN $1 > $tmp
|
||
|
|
else
|
||
|
|
cp "$1" $tmp
|
||
|
|
fi
|
||
|
|
if file -b --mime-encoding $tmp | grep binary; then
|
||
|
|
uuencode -m $tmp $fname > ${tmp}.new
|
||
|
|
mv ${tmp}.new $tmp
|
||
|
|
else
|
||
|
|
echo "Contents of ${fname}:" >> ${tmp}.new
|
||
|
|
cat $tmp >> ${tmp}.new
|
||
|
|
mv ${tmp}.new $tmp
|
||
|
|
fi
|
||
|
|
jq -nc --slurpfile h $histfile --rawfile a "$tmp" '$h[0]+[{"role":"user","content":$a}]' > ${histfile}.new
|
||
|
|
mv ${histfile}.new $histfile
|
||
|
|
rm $tmp
|
||
|
|
}
|
||
|
|
|
||
|
|
listmodels() {
|
||
|
|
echo 'Available models:'
|
||
|
|
curl -sSL ${SHEESH_API_ROOT}/models \
|
||
|
|
${SHEESH_UA:+-H "User-Agent: $SHEESH_UA"} \
|
||
|
|
${SHEESH_REFERER:+-H "Referer: $SHEESH_REFERER"} \
|
||
|
|
${SHEESH_ORIGIN:+-H "Origin: $SHEESH_ORIGIN"} \
|
||
|
|
${OPENAI_API_KEY:+-H "Authorization: Bearer $OPENAI_API_KEY"} \
|
||
|
|
${SHEESH_COOKIES:+-H "Cookie: $SHEESH_COOKIES"} \
|
||
|
|
| jq -r '.data[].id' | while read -r modelname; do
|
||
|
|
printf '\e[0;36m* %s\e[0m\n' "$modelname"
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
help() {
|
||
|
|
printf 'Current settings:\n\n\e[2;37mEndpoint: %s\n' "$SHEESH_API_ROOT"
|
||
|
|
printf 'Model: %s\n' "$SHEESH_MODEL"
|
||
|
|
printf 'Temperature: %.2f\n' "$SHEESH_TEMP"
|
||
|
|
printf 'System prompt: "%s"\e[0m\n\n' "$SHEESH_SYSPROMPT"
|
||
|
|
echo 'Available commands:'
|
||
|
|
echo
|
||
|
|
echo '* /models: list available models'
|
||
|
|
echo '* /model: change the current model'
|
||
|
|
echo '* /add: add a file to the context'
|
||
|
|
echo '* /temp: change the current temperature'
|
||
|
|
echo '* /endpoint: change the current API endpoint'
|
||
|
|
echo '* /clear: clear the session context'
|
||
|
|
echo '* /sys [prompt]: set a system prompt and clear the context'
|
||
|
|
echo '* /sysload [file]: load the system prompt from a file and clear the context'
|
||
|
|
echo '* /bye: exit the chat immediately'
|
||
|
|
echo
|
||
|
|
echo 'Available environment variables for configuration:'
|
||
|
|
echo
|
||
|
|
echo '* OPENAI_API_KEY: API key string (default empty)'
|
||
|
|
echo '* SHEESH_API_ROOT: API endpoint (default https://text.pollinations.ai/openai)'
|
||
|
|
echo '* SHEESH_MODEL: model ID (default openai-fast)'
|
||
|
|
echo '* SHEESH_TEMP: model temperature (default 0.6)'
|
||
|
|
echo '* SHEESH_SYSPROMPT: system prompt (default "You are a helpful assistant.") '
|
||
|
|
echo '* SHEESH_UA: user agent override (default empty)'
|
||
|
|
echo '* SHEESH_REFERER: Referer header value (default empty)'
|
||
|
|
echo '* SHEESH_ORIGIN: Origin header value (default empty)'
|
||
|
|
echo '* SHEESH_COOKIES: Cookie header value (default empty)'
|
||
|
|
echo
|
||
|
|
}
|
||
|
|
|
||
|
|
trap cleanup SIGINT SIGABRT SIGHUP SIGQUIT
|
||
|
|
cleanup() {
|
||
|
|
rm $histfile $datafile
|
||
|
|
printf '\nBye!\n'
|
||
|
|
exit 0
|
||
|
|
}
|
||
|
|
|
||
|
|
hdrline='----------------------'
|
||
|
|
title='SHEE.SH v2 by Luxferre'
|
||
|
|
printf '\e[1;36m%s\n%s\n%s\e[0m\n' "$hdrline" "$title" "$hdrline"
|
||
|
|
printf '\e[2;37mEndpoint: %s\n' "$SHEESH_API_ROOT"
|
||
|
|
printf 'Model: %s\n' "$SHEESH_MODEL"
|
||
|
|
printf 'Temperature: %.2f\n' "$SHEESH_TEMP"
|
||
|
|
printf '\e[1;36m%s\e[0m\n' "$hdrline"
|
||
|
|
|
||
|
|
setsys "$SHEESH_SYSPROMPT"
|
||
|
|
while prompt && read -r line; do
|
||
|
|
[ -z "$line" ] && continue
|
||
|
|
[ "$line" == "/bye" ] && break
|
||
|
|
[ "$line" == "/clear" ] && clearhist && echo "Session cleared" && continue
|
||
|
|
[ "$line" == "/help" ] && help && continue
|
||
|
|
[ "$line" == "/models" ] && listmodels && continue
|
||
|
|
startswith "$line" '/sys ' && setsys "${line#\/sys }" && echo "System prompt set and session cleared" && continue
|
||
|
|
startswith "$line" '/sysload ' && setsysfile "${line#\/sysload }" && echo "System prompt loaded and session cleared" && continue
|
||
|
|
startswith "$line" '/temp ' && SHEESH_TEMP="${line#\/temp }" && echo "Temperature set to $SHEESH_TEMP" && continue
|
||
|
|
startswith "$line" '/model ' && SHEESH_MODEL="${line#\/model }" && echo "Model set to $SHEESH_MODEL" && continue
|
||
|
|
startswith "$line" '/endpoint ' && SHEESH_API_ROOT="${line#\/endpoint }" && echo "API endpoint set to $SHEESH_API_ROOT" && continue
|
||
|
|
if startswith "$line" '/add '; then
|
||
|
|
filelist="${line#\/add }"
|
||
|
|
for f in $filelist; do
|
||
|
|
addfile "$f"
|
||
|
|
printf "File %s added to the context\n" "$f"
|
||
|
|
done
|
||
|
|
continue
|
||
|
|
fi
|
||
|
|
# commands end, normal message processing start
|
||
|
|
addhistory user "$line"
|
||
|
|
jq -nc --arg m "$SHEESH_MODEL" --arg t "$SHEESH_TEMP" --slurpfile msgs $histfile \
|
||
|
|
'{model:$m,stream:true,messages:$msgs[0],temperature:($t|tonumber)}' > $datafile
|
||
|
|
curl -sSLN "${SHEESH_API_ROOT}/chat/completions" \
|
||
|
|
-H 'Content-Type: application/json' \
|
||
|
|
${SHEESH_UA:+-H "User-Agent: $SHEESH_UA"} \
|
||
|
|
${SHEESH_REFERER:+-H "Referer: $SHEESH_REFERER"} \
|
||
|
|
${SHEESH_ORIGIN:+-H "Origin: $SHEESH_ORIGIN"} \
|
||
|
|
${OPENAI_API_KEY:+-H "Authorization: Bearer $OPENAI_API_KEY"} \
|
||
|
|
${SHEESH_COOKIES:+-H "Cookie: $SHEESH_COOKIES"} \
|
||
|
|
-H 'Accept: text/event-stream' --data-binary "@$datafile" 2>/dev/null | \
|
||
|
|
while IFS= read -r chunk; do
|
||
|
|
[ -z "$chunk" ] && continue
|
||
|
|
data="${chunk#data: }"
|
||
|
|
[ "$data" == "[DONE]" ] && break
|
||
|
|
delta=''
|
||
|
|
startswith "$data" '{' && delta="$(printf '%s' "$data" | jq -rj '.choices[0].delta.content // empty' 2>/dev/null;printf x)"
|
||
|
|
delta="${delta%?}"
|
||
|
|
[ -n "$delta" ] && printf "\e[0;33m%s\e[0m" "$delta"
|
||
|
|
tailbuf="$tailbuf$delta"
|
||
|
|
done
|
||
|
|
echo
|
||
|
|
addhistory assistant "$tailbuf"
|
||
|
|
tailbuf=""
|
||
|
|
line=''
|
||
|
|
done
|
||
|
|
cleanup
|