4ndr0666

chatgpt.sh

Nov 25th, 2024
24
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 230.52 KB | Source Code | 0 0
  1. #!/usr/bin/env bash
  2. # File: chatgpt.sh v0.87.1
  3. # Edited: 11-25-24
  4. # Author: 4ndr0666
  5. # Desc: -- Shell Wrapper for ChatGPT/DALL-E/Whisper/TTS
  6.  
  7. # ======================================================================== // CHATGPT.SH //
  8. set -o pipefail; shopt -s extglob checkwinsize cmdhist lithist histappend;
  9. export COLUMNS LINES; ((COLUMNS>2)) || COLUMNS=80; ((LINES>2)) || LINES=24;
  10.  
  11. # API keys
  12. #OPENAI_API_KEY=
  13. #GOOGLE_API_KEY=
  14. #MISTRAL_API_KEY=
  15. #GROQ_API_KEY=
  16. #ANTHROPIC_API_KEY=
  17. #GITHUB_TOKEN=
  18. #NOVITA_API_KEY=
  19.  
  20. # DEFAULTS
  21. # Text cmpls model
  22. MOD="gpt-3.5-turbo-instruct"
  23. # Chat cmpls model
  24. MOD_CHAT="${MOD_CHAT:-gpt-4o}"  #"chatgpt-4o-latest"
  25. # Image model (generations)
  26. MOD_IMAGE="${MOD_IMAGE:-dall-e-3}"
  27. # Whisper model (STT)
  28. MOD_AUDIO="${MOD_AUDIO:-whisper-1}"
  29. MOD_AUDIO_GROQ="${MOD_AUDIO_GROQ:-whisper-large-v3}"
  30. # Speech model (TTS)
  31. MOD_SPEECH="${MOD_SPEECH:-tts-1}"
  32. # LocalAI model
  33. MOD_LOCALAI="${MOD_LOCALAI:-phi-2}"
  34. # Ollama model
  35. MOD_OLLAMA="${MOD_OLLAMA:-llama3.2}"
  36. # Google AI model
  37. MOD_GOOGLE="${MOD_GOOGLE:-gemini-1.5-pro-latest}"
  38. # Mistral AI model
  39. MOD_MISTRAL="${MOD_MISTRAL:-mistral-large-latest}"
  40. # Groq model
  41. MOD_GROQ="${MOD_GROQ:-llama-3.1-70b-versatile}"
  42. # Enable Groq Whisper only
  43. #WHISPER_GROQ=
  44. # Anthropic model
  45. MOD_ANTHROPIC="${MOD_ANTHROPIC:-claude-3-5-sonnet-latest}"
  46. # Github Azure model
  47. MOD_GITHUB="${MOD_GITHUB:-Phi-3-medium-128k-instruct}"
  48. # Novita AI model
  49. MOD_NOVITA="${MOD_NOVITA:-sao10k/l3-70b-euryale-v2.1}"
  50. # Bash readline mode
  51. READLINEOPT="emacs"  #"vi"
  52. # Stream response
  53. STREAM=1
  54. # Prompter flush with <CTRL-D>
  55. #OPTCTRD=
  56. # Temperature
  57. #OPTT=
  58. # Whisper temperature
  59. OPTTW=0
  60. # Top_p probability mass (nucleus sampling)
  61. #OPTP=1
  62. # Maximum response tokens
  63. OPTMAX=1024
  64. # Model capacity (auto)
  65. #MODMAX=
  66. # Presence penalty
  67. #OPTA=
  68. # Frequency penalty
  69. #OPTAA=
  70. # N responses of Best_of
  71. #OPTB=
  72. # Number of responses
  73. OPTN=1
  74. # Keep Alive (seconds, Ollama)
  75. #OPT_KEEPALIVE=
  76. # Seed (integer)
  77. #OPTSEED=
  78. # Set python tiktoken
  79. #OPTTIK=
  80. # Image size
  81. #OPTS=1024x1024  #hd
  82. #Image style
  83. #OPTI_STYLE=natural  #vivid
  84. # Image out format
  85. OPTI_FMT=b64_json  #url
  86. # TTS voice
  87. OPTZ_VOICE=echo  #alloy, echo, fable, onyx, nova, and shimmer
  88. # TTS voice speed
  89. #OPTZ_SPEED=   #0.25 - 4.0
  90. # TTS out file format
  91. #OPTZ_FMT=opus   #mp3, opus, aac, flac, wav, pcm16
  92. # Recorder command, e.g. "sox -d"
  93. #REC_CMD=""
  94. # Media player command, e.g. "cvlc"
  95. #PLAY_CMD=""
  96. # Clipboard set command, e.g. "xsel -b", "pbcopy"
  97. #CLIP_CMD=""
  98. # Markdown renderer, e.g. "pygmentize -s -lmd", "glow", "mdless", "mdcat"
  99. #MD_CMD="bat"
  100. # Fold response (wrap at white spaces)
  101. OPTFOLD=1
  102. # Avoid using dialog
  103. #NO_DIALOG=
  104. # Inject restart text
  105. #RESTART=""
  106. # Inject   start text
  107. #START=""
  108. #  Chat mode of text cmpls sets "\nQ: " and "\nA:"
  109. # Input and output prices (dollars per million tokens)
  110. #MOD_PRICE="0 0"
  111. # Currency rate against USD
  112. # e.g. BRL is 5.66 USD, JPY is 0.006665 USD
  113. #CURRENCY_RATE="1"
  114.  
  115. # INSTRUCTION
  116. # Chat completions, chat mode only
  117. # INSTRUCTION=""
  118. INSTRUCTION_CHAT_DEF="The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly."
  119. INSTRUCTION_CHAT="${INSTRUCTION_CHAT-$INSTRUCTION_CHAT_DEF}"
  120.  
  121. # Awesome-chatgpt-prompts URL
  122. AWEURL="https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv"
  123. AWEURLZH="https://raw.githubusercontent.com/PlexPt/awesome-chatgpt-prompts-zh/main/prompts-zh.json"  #prompts-zh-TW.json
  124.  
  125. # CACHE AND OUTPUT DIRECTORIES
  126. CACHEDIR="${CACHEDIR:-${XDG_CACHE_HOME:-$HOME/.cache}}/chatgptsh"
  127. OUTDIR="${OUTDIR:-${XDG_DOWNLOAD_DIR:-$HOME/Downloads}}"
  128.  
  129. # Colour palette
  130. # Normal Colours       # Bold                  # Background
  131. Black='\u001b[0;30m'   BBlack='\u001b[1;30m'   On_Black='\u001b[40m'  \
  132. Red='\u001b[0;31m'     BRed='\u001b[1;31m'     On_Red='\u001b[41m'    \
  133. Green='\u001b[0;32m'   BGreen='\u001b[1;32m'   On_Green='\u001b[42m'  \
  134. Yellow='\u001b[0;33m'  BYellow='\u001b[1;33m'  On_Yellow='\u001b[43m' \
  135. Blue='\u001b[0;34m'    BBlue='\u001b[1;34m'    On_Blue='\u001b[44m'   \
  136. Purple='\u001b[0;35m'  BPurple='\u001b[1;35m'  On_Purple='\u001b[45m' \
  137. Cyan='\u001b[0;36m'    BCyan='\u001b[1;36m'    On_Cyan='\u001b[46m'   \
  138. White='\u001b[0;37m'   BWhite='\u001b[1;37m'   On_White='\u001b[47m'  \
  139. Inv='\u001b[0;7m'      Nc='\u001b[m'           Alert=$BWhite$On_Red   \
  140. Bold='\u001b[0;1m';
  141. HISTSIZE=256;
  142.  
  143. # Load user defaults
  144. ((${#CHATGPTRC})) || CHATGPTRC="$HOME/.chatgpt.conf"
  145. [[ -f "${OPTF}${CHATGPTRC}" ]] && . "$CHATGPTRC";
  146.  
  147. # Set file paths
  148. FILE="${CACHEDIR%/}/chatgpt.json"
  149. FILESTREAM="${CACHEDIR%/}/chatgpt_stream.json"
  150. FILECHAT="${FILECHAT:-${CACHEDIR%/}/chatgpt.tsv}"
  151. FILEWHISPER="${FILECHAT%/*}/whisper.json"
  152. FILEWHISPERLOG="${OUTDIR%/*}/whisper_log.txt"
  153. FILETXT="${CACHEDIR%/}/chatgpt.txt"
  154. FILEOUT="${OUTDIR%/}/dalle_out.png"
  155. FILEOUT_TTS="${OUTDIR%/}/tts.${OPTZ_FMT:=opus}"
  156. FILEIN="${CACHEDIR%/}/dalle_in.png"
  157. FILEINW="${CACHEDIR%/}/whisper_in.${REC_FMT:=mp3}"
  158. FILEAWE="${CACHEDIR%/}/awesome-prompts.csv"
  159. FILEFIFO="${CACHEDIR%/}/fifo.buff"
  160. FILEMODEL="${CACHEDIR%/}/models.txt"
  161. USRLOG="${OUTDIR%/}/${FILETXT##*/}"
  162. HISTFILE="${CACHEDIR%/}/history_bash"
  163. HISTCONTROL=erasedups:ignoredups
  164. SAVEHIST=$HISTSIZE HISTTIMEFORMAT='%F %T '
  165.  
  166. # API URL / endpoint
  167. OPENAI_BASE_URL_DEF="https://api.openai.com/v1";
  168. OLLAMA_BASE_URL_DEF="http://localhost:11434";
  169. LOCALAI_BASE_URL_DEF="http://127.0.0.1:8080/v1";
  170. MISTRAL_BASE_URL_DEF="https://api.mistral.ai/v1";
  171. GOOGLE_BASE_URL_DEF="https://generativelanguage.googleapis.com/v1beta";
  172. GROQ_BASE_URL_DEF="https://api.groq.com/openai/v1";
  173. ANTHROPIC_BASE_URL_DEF="https://api.anthropic.com/v1";
  174. GITHUB_BASE_URL_DEF="https://models.inference.ai.azure.com";
  175. NOVITA_BASE_URL_DEF="https://api.novita.ai/v3/openai";
  176. OPENAI_API_KEY_DEF=$OPENAI_API_KEY;
  177. BASE_URL=$OPENAI_BASE_URL_DEF;
  178.  
  179. # Def hist, txt chat types
  180. Q_TYPE="\\nQ: "
  181. A_TYPE="\\nA:"
  182. S_TYPE="\\n\\nSYSTEM: "
  183. I_TYPE_STR="[insert]"
  184. I_TYPE="\\[[Ii][Nn][Ss][Ee][Rr][Tt]\\]"
  185.  
  186. # Globs
  187. SPC="*([$IFS])"
  188. SPC1="*(\\\\[ntrvf]|[$IFS])"
  189. NL=$'\n' BS=$'\b'
  190.  
  191. UAG='user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36'  #chrome on win10
  192. PLACEHOLDER='sk-CbCCb0CC0bbbCbb0CCCbC0CbbbCC00bC00bbCbbCbbbCbb0C'
  193.  
  194. HELP="Name
  195.     ${0##*/} -- Wrapper for ChatGPT / DALL-E / Whisper / TTS
  196.  
  197.  
  198. Synopsis
  199.     ${0##*/} [-cc|-d|-qq] [opt..] [PROMPT|TEXT_FILE|PDF_FILE]
  200.     ${0##*/} -i [opt..] [X|L|P][hd] [PROMPT]  #dall-e-3
  201.     ${0##*/} -i [opt..] [S|M|L] [PROMPT]
  202.     ${0##*/} -i [opt..] [S|M|L] [PNG_FILE]
  203.     ${0##*/} -i [opt..] [S|M|L] [PNG_FILE] [MASK_FILE] [PROMPT]
  204.     ${0##*/} -w [opt..] [AUDIO_FILE|.] [LANG] [PROMPT]
  205.     ${0##*/} -W [opt..] [AUDIO_FILE|.] [PROMPT-EN]
  206.     ${0##*/} -z [OUTFILE|FORMAT|-] [VOICE] [SPEED] [PROMPT]
  207.     ${0##*/} -ccWwz [opt..] -- [PROMPT] -- [whisper_arg..] -- [tts_arg..]
  208.     ${0##*/} -l [MODEL]
  209.     ${0##*/} -TTT [-v] [-m[MODEL|ENCODING]] [INPUT|TEXT_FILE|PDF_FILE]
  210.     ${0##*/} -HPP [/HIST_FILE|.]
  211.     ${0##*/} -HPw
  212.  
  213.  
  214. Description
  215.     Wraps ChatGPT, DALL-E, Whisper, and TTS from various providers.
  216.    
  217.     Defaults to single-turn native chat completions. Handles single
  218.     and multi-turn chat, text completions, image generation/editing,
  219.     speech-to-text, and text-to-speech.
  220.  
  221.     Accepts prompts, files (text, PDF, image), and options for model
  222.     selection, parameters, and output.
  223.  
  224.  
  225.     Chat Completion Modes
  226.    
  227.     Set option -c to start multi-turn chat mode via text completions
  228.     (instruct models) or -cc for native chat completions (gpt-3.5+
  229.     models) with command line history support.
  230.  
  231.     In chat mode, some options are automatically set to un-lobotomise
  232.     the bot.
  233.  
  234.     Option -C resumes (continues from) last history session. Set option
  235.     -E to exit on response.
  236.  
  237.  
  238.     Text Completion Modes
  239.  
  240.     Option -d initiates a multi-turn text completion session with history.
  241.     Further options (e.g., instructions, temperature) must be specified
  242.     explicitly.
  243.  
  244.     For single-turn text completion, provide the model (e.g.,
  245.     gpt-3.5-turbo-instruct) and other parameters (e.g., temperature,
  246.     stop sequences) on the command line.
  247.  
  248.  
  249.     Insert Modes
  250.  
  251.     Set option -qq for multi turn insert mode, and add tag \`[insert]'
  252.     to the prompt at the location to be filled in (instruct models).
  253.  
  254.  
  255.     Instruction Prompts
  256.  
  257.     Positional arguments are read as a single PROMPT. Optionally set
  258.     INTRUCTION with option -S.
  259.  
  260.     If a plain text or PDF file path is set as the last positional
  261.     parameter or as an argument to \`option -S\`, the file is loaded
  262.     as text PROMPT.
  263.  
  264.     To create and reuse a custom prompt, set the prompt name as a command
  265.     line option, such as \`-S .[prompt_name]' or \`-S ,[prompt_name]'.
  266.  
  267.     Alternatively, set the first positional argument with the operator
  268.     dot \`.' and the prompt name, such as \`.[prompt]'.
  269.  
  270.  
  271.     Commands
  272.  
  273.     If the first positional argument of the script starts with the
  274.     command operator \`/', the command \`/session [HIST_NAME]' to change
  275.     to or create a new history file is assumed (with options -ccCdPP).
  276.  
  277.     In multi-turn interactions, prompts starting with a colon \`:' are
  278.     appended as user messages to the request block, while double colons
  279.     \`::' append the prompt as instruction / system without initiating
  280.     a new API request.
  281.  
  282.     With vision and reasoning models, append the image file paths and
  283.     possibly URLs at the end of the prompt. Make sure file paths
  284.     containing spaces are backslash-escaped.
  285.  
  286.  
  287.     Image Generations and Edits (Dall-E)
  288.  
  289.     Option -i generates or edits images. A text prompt is required for
  290.     generations. An image file is required for variations. Edits need
  291.     an image file, a mask (or the image must have a transparent layer),
  292.     and a text prompt to direct the editing.
  293.  
  294.     Size of output image may be set as the first positional parameter,
  295.     options are: \`256x256' (S), \`512x512' (M), \`1024x1024' (L),
  296.     \`1792x1024' (X), and \`1024x1792' (P). The parameter \`hd' may also
  297.     be set for quality (Dall-E-3), such as \`Xhd', or \`1792x1024hd'.
  298.    
  299.     For Dalle-3, optionally set the generation style as either \"natural\"
  300.     or \"vivid\" as a positional parameter.
  301.  
  302.  
  303.     Speech-To-Text (Whisper)
  304.  
  305.     Option -w transcribes audio to any language, and option -W translates
  306.     audio to English text. Set these options twice to have phrasal-
  307.     level timestamps, options -ww, and -WW. Set thrice for word-level
  308.     timestamps.
  309.  
  310.  
  311.     TTS (Text-To-Voice)
  312.  
  313.     Option -z synthesises voice from text (TTS models). Set a voice as
  314.     the first positional parameter (\`alloy', \`echo', \`fable', \`onyx',
  315.     \`nova', or \`shimmer'). Set the second positional parameter as the
  316.     speed (0.25 - 4.0), and, finally the output file name or the format,
  317.     such as \`./new_audio.mp3' (\`mp3', \`opus', \`aac', and \`flac'),
  318.     or \`-' for stdout. Set options -vz to not play received output.
  319.  
  320.  
  321.     Observations
  322.  
  323.     Input sequences \`\\n' and \`\\t' are only treated specially in
  324.     restart, start and stop sequences!
  325.  
  326.     A personal OpenAI API is required, set environment or command
  327.     line option --api-key.
  328.  
  329.     Check the man page for extended description of interface and
  330.     settings. See the online man page and script usage examples at:
  331.  
  332.     <https://gitlab.com/fenixdragao/shellchatgpt>.
  333.  
  334.  
  335. Environment
  336.     BLOCK_USR
  337.     BLOCK_USR_TTS   Extra options for the request JSON block
  338.             (e.g. \`\"seed\": 33, \"dimensions\": 1024').
  339.  
  340.     CACHEDIR    Script cache directory base.
  341.    
  342.     CHATGPTRC   Path to the user configuration file.
  343.             Defaults=${CHATGPTRC/"$HOME"/"~"}
  344.  
  345.     FILECHAT    Path to a history / session TSV file.
  346.  
  347.     INSTRUCTION     Initial instruction or system message.
  348.  
  349.     INSTRUCTION_CHAT
  350.             Initial instruction or system message (chat mode).
  351.  
  352.     MOD_CHAT        MOD_IMAGE      MOD_AUDIO
  353.     MOD_SPEECH      MOD_LOCALAI    MOD_OLLAMA
  354.     MOD_MISTRAL     MOD_GOOGLE     MOD_GROQ
  355.     MOD_AUDIO_GROQ  MOD_ANTHROPIC  MOD_GITHUB
  356.             Set default model for each endpoint / provider.
  357.    
  358.     OPENAI_BASE_URL
  359.     OPENAI_URL_PATH
  360.             Main Base URL setting. Alternatively, provide the
  361.             URL_PATH parameter to disable endpoint auto-selection.
  362.  
  363.     [PROVIDER]_BASE_URL
  364.             Base URLs for providers: LOCALAI, OLLAMA,
  365.             MISTRAL, GOOGLE, GROQ, ANTHROPIC, and GITHUB.
  366.  
  367.     OPENAI_API_KEY
  368.     [PROVIDER]_API_KEY
  369.             Keys for OpenAI, GoogleAI, MistralAI, Groq,
  370.             Anthropic, and GitHub Models APIs.
  371.  
  372.     OUTDIR      Output directory for received image and audio.
  373.  
  374.     RESTART
  375.     START           Restart and start sequences. May be set to null.
  376.  
  377.     VISUAL
  378.     EDITOR      Text editor for external prompt editing.
  379.             Defaults=\"${VISUAL:-${EDITOR:-vim}}\"
  380.  
  381.     CLIP_CMD    Clipboard set command, e.g. \`xsel -b', \`pbcopy'.
  382.  
  383.     PLAY_CMD    Audio player command, e.g. \`mpv --no-video --vo=null'.
  384.  
  385.     REC_CMD     Audio recorder command, e.g. \`sox -d'.
  386.  
  387.  
  388. Command List
  389.     In chat mode, commands are invoked with either \`!' or \`/' as
  390.     operators. These commands allow users to modify settings and
  391.     manage the session.
  392.  
  393.   -------    ----------    -----------------------------------------
  394.   --- Misc Commands ------------------------------------------------
  395.      -S      :, ::   [PROMPT]  Append user/system prompt to request.
  396.      -S.     -.       [NAME]   Load and edit custom prompt.
  397.      -S/     -S%      [NAME]   Load and edit awesome prompt (zh).
  398.      -Z      !last             Print last response JSON.
  399.      !#      !save   [PROMPT]  Save current prompt to shell history. ‡
  400.       !      !r, !regen        Regenerate last response.
  401.      !!      !rr               Regenerate response, edit prompt first.
  402.      !i      !info             Info on model and session settings.
  403.      !j      !jump             Jump to request, append response primer.
  404.     !!j     !!jump             Jump to request, no response priming.
  405.     !cat     -                 Cat prompter (one-shot, ctrl-d).
  406.     !cat     !cat: [TXT|URL|PDF] Cat text or PDF file, dump URL.
  407.     !dialog  -                 Toggle the \`dialog' interface.
  408.     !img     !media [FILE|URL] Append image, media, or URL to prompt.
  409.     !md      !markdown [SOFTW] Toggle markdown support in response.
  410.    !!md     !!markdown [SOFTW] Render last response in markdown.
  411.     !rep     !replay           Replay last TTS audio response.
  412.     !res     !resubmit         Resubmit last TTS recorded input.
  413.     !p       !pick,  [PROMPT]  File picker, appends filepath to prompt. ‡
  414.     !pdf     !pdf:    [FILE]   Dump PDF text.
  415.    !photo   !!photo   [INDEX]  Take a photo, camera index (Termux). ‡
  416.     !sh      !shell    [CMD]   Run shell or command, and edit output. ‡
  417.     !sh:     !shell:   [CMD]   Same as !sh but apppend output as user.
  418.    !!sh     !!shell    [CMD]   Run interactive shell (w/ cmd) and exit.
  419.     !url     !url:     [URL]   Dump URL text or YouTube transcript.
  420.   --- Script Settings and UX ---------------------------------------
  421.    !fold     !wrap             Toggle response wrapping.
  422.      -g      !stream           Toggle response streaming.
  423.      -h      !help   [REGEX]   Print help, optionally set regex.
  424.      -l      !models  [NAME]   List language models or model details.
  425.      -o      !clip             Copy responses to clipboard.
  426.      -u      !multi            Toggle multiline, ctrl-d flush.
  427.      -uu    !!multi            Multiline, one-shot, ctrl-d flush.
  428.      -U      -UU               Toggle cat prompter or set one-shot.
  429.      -V      !debug            Dump raw request block and confirm.
  430.      -v      !ver              Toggle verbose modes.
  431.      -x      !ed               Toggle text editor interface.
  432.      -xx    !!ed               Single-shot text editor.
  433.      -y      !tik              Toggle python tiktoken use.
  434.      !q      !quit             Exit. Bye.
  435.   --- Model Settings -----------------------------------------------
  436.     -Nill    !Nill             Toggle model max response (chat cmpls).
  437.      -M      !NUM !max [NUM]   Max response tokens.
  438.      -N      !modmax   [NUM]   Model token capacity.
  439.      -a      !pre      [VAL]   Presence penalty.
  440.      -A      !freq     [VAL]   Frequency penalty.
  441.      -b      !best     [NUM]   Best-of n results.
  442.      -j      !seed     [NUM]   Seed number (integer).
  443.      -K      !topk     [NUM]   Top_k.
  444.      -m      !mod      [MOD]   Model by name or pick from list.
  445.      -n      !results  [NUM]   Number of results.
  446.      -p      !topp     [VAL]   Top_p.
  447.      -r      !restart  [SEQ]   Restart sequence.
  448.      -R      !start    [SEQ]   Start sequence.
  449.      -s      !stop     [SEQ]   One stop sequence.
  450.      -t      !temp     [VAL]   Temperature.
  451.      -w      !rec     [ARGS]   Toggle voice chat mode (Whisper).
  452.      -z      !tts     [ARGS]   Toggle TTS chat mode (speech out).
  453.     !blk     !block   [ARGS]   Set and add options to JSON request.
  454.     !ka      !keep-alive [NUM] Set duration of model load in memory
  455.   !vision    !audio            Toggle model multimodality type.
  456.   --- Session Management -------------------------------------------
  457.      -H      !hist             Edit raw history file in editor.
  458.      -P      -HH, !print       Print session history.
  459.      -L      !log  [FILEPATH]  Save to log file (pretty-print).
  460.     !br      !break, !new      Start new session (session break).
  461.     !ls      !list    [GLOB]   List Hist files with glob in name. All: \`.'.
  462.                                Instruction prompts: \`pr'. Awesome: \`awe'.
  463.     !grep    !sub    [REGEX]   Search sessions and copy to tail.
  464.      !c      !copy [SRC_HIST] [DEST_HIST]
  465.                                Copy session from source to destination.
  466.      !f      !fork [DEST_HIST] Fork current session to destination.
  467.      !k      !kill     [NUM]   Comment out n last entries in hist file.
  468.     !!k     !!kill  [[0]NUM]   Dry-run of command !kill.
  469.      !s      !session [HIST_FILE]
  470.                                Change to, search for, or create hist file.
  471.     !!s     !!session [HIST_FILE]
  472.                                 Same as !session, break session.
  473.   -------    ----------    -----------------------------------------
  474.  
  475.      : Commands with a colon have their output appended to the prompt.
  476.  
  477.      ‡ Commands with double dagger may be invoked at the very end of
  478.        the prompt.
  479.  
  480.      E.g. \`/temp 0.7', \`!modgpt-4', \`-p 0.2', \`/session HIST_NAME',
  481.           \`[PROMPT] /pick', and \`[PROMPT] /sh'.
  482.  
  483.  
  484.     To continue from an old session, either \`/copy . .\` or \`/fork.\` it.
  485.     The dot means the current session. The shorthand for this feature
  486.     is \`/.\`. It is also possible to execute \`/grep [regex]\` for a
  487.     session and resume it.
  488.  
  489.     To preview a prompt completion, append a forward slash \`/' to it.
  490.     Regenerate it again or flush / accept the prompt and response.
  491.  
  492.     After a response has been written to the history file, regenerate
  493.     it with command \`!regen' or type in a single exclamation mark or
  494.     forward slash in the new empty prompt (twice for editing the
  495.         prompt before request).
  496.  
  497.     Change chat context at run time with the \`!hist' command to edit
  498.     the raw history file (delete or comment out entries).
  499.  
  500.     Press <CTRL-X CTRL-E> to edit command line in text editor (readline).
  501.     Press <CTRL-J> or <CTRL-V CTRL-J> for newline (readline).
  502.     Press <CTRL-L> to redraw readline buffer (user input) on screen.
  503.     Press <CTRL-C> during cURL requests to interrupt the call.
  504.     Press <CTRL-\\> to terminate the script at any time (QUIT signal),
  505.     or \`Q' in user confirmation prompts.
  506.  
  507.  
  508. Options
  509.     Service Providers
  510.     --anthropic
  511.         Set Anthropic integration (cmpls/chat).
  512.     --github
  513.         Set GitHub Models integration (chat).
  514.     --google
  515.         Set Google Gemini integration (cmpls/chat).
  516.     --groq  Set Groq integration (chat).
  517.     --localai
  518.         Set LocalAI integration (cmpls/chat).
  519.     --mistral
  520.         Set Mistral AI integration (chat).
  521.     --novita
  522.         Set Novita AI integration (cmpls/chat).
  523.     --openai
  524.         Reset service integrations.
  525.     -O, --ollama
  526.         Set and request to Ollama server (cmpls/chat).
  527.  
  528.     Configuration File
  529.     -f, --no-conf
  530.         Ignore user configuration file.
  531.     -F  Edit configuration file, if it exists.
  532.         \$CHATGPTRC=${CHATGPTRC/"$HOME"/"~"}.
  533.     -FF     Dump template configuration file to stdout.
  534.  
  535.     Sessions and History Files
  536.     -H, --hist  [/HIST_FILE]
  537.         Edit history file with text editor or pipe to stdout.
  538.         A hist file name can be optionally set as argument.
  539.     -P, -PP, --print  [/HIST_FILE]    (aliases to -HH and -HHH)
  540.         Print out last history session. Set twice to print
  541.         commented out entries, too. Heeds -ccdrR.
  542.  
  543.     Input Modes
  544.     -u, --multiline
  545.         Toggle multiline prompter, <CTRL-D> flush.
  546.     -U, --cat
  547.         Set cat prompter, <CTRL-D> flush.
  548.     -x, -xx, --editor
  549.         Edit prompt in text editor. Set twice for single-shot.
  550.         Set options -eex to edit last text buffer from cache.
  551.  
  552.     Interface Modes
  553.     -c, --chat
  554.         Chat mode in text completions (used with -wzvv).
  555.     -cc     Chat mode in chat completions (used with -wzvv).
  556.     -C, --continue, --resume
  557.         Continue from (resume) last session (cmpls/chat).
  558.     -d, --text
  559.         Start new multi-turn session in plain text completions.
  560.     -e, --edit
  561.         Edit the first input before request. (cmpls/chat).
  562.         With options -eex, edit the last text editor buffer.
  563.     -E, -EE, --exit
  564.         Exit on first run (even with -cc).
  565.     -g, --stream  (defaults)
  566.         Set response streaming.
  567.     -G, --no-stream
  568.         Unset response streaming.
  569.     -i, --image   [PROMPT]
  570.         Generate images given a prompt.
  571.     -i  [PNG]
  572.         Create variations of a given image.
  573.     -i  [PNG] [MASK] [PROMPT]
  574.         Edit image with mask, and prompt (required).
  575.     -q, -qq, --insert
  576.         Insert text mode. Use \`[insert]' tag within the prompt.
  577.         Set twice for multi-turn (\`instruct', Mistral \`code' models).
  578.     -q, -qq, --insert
  579.         Insert text rather than completing. Use \`[insert]' within the
  580.         user prompt to indicate where the model should insert text.
  581.         Set twice for multi-turn (\`instruct' and Mistral \`code' models).
  582.     -S .[PROMPT_NAME],  -.[PROMPT_NAME]
  583.     -S ,[PROMPT_NAME],  -,[PROMPT_NAME]
  584.         Load, search for, or create custom prompt.
  585.         Set \`.[prompt]' to load prompt silently.
  586.         Set \`,[prompt]' to single-shot edit prompt.
  587.         Set \`,,[prompt]' to edit the prompt template.
  588.         Set \`.?' to list all prompt template files.
  589.     -S /[AWESOME_PROMPT_NAME]
  590.     -S %[AWESOME_PROMPT_NAME_ZH]
  591.         Set or search an awesome-chatgpt-prompt(-zh).
  592.         Set \`//' or \`%%' to refresh cache. Davinci+ models.
  593.     -T, -TT, -TTT, --tiktoken
  594.         Count input tokens with Tiktoken. Set twice to print
  595.         tokens, thrice to available encodings. Set the model
  596.         or encoding with option -m. It heeds options -ccm.
  597.     -w, --transcribe  [AUD] [LANG] [PROMPT]
  598.         Transcribe audio file into text (whisper models).
  599.         LANG is optional. A prompt that matches the audio language
  600.         is optional. Set twice to phrase or thrice for word-level
  601.         timestamps (-www). With -vv, stop voice recorder on silence.
  602.     -W, --translate   [AUD] [PROMPT-EN]
  603.         Translate audio file into English text (whisper models).
  604.         Set twice to phrase or thrice for word-level timestamps (-WWW).
  605.     -z, --tts   [OUTFILE|FORMAT|-] [VOICE] [SPEED] [PROMPT]
  606.         Synthesise speech from text prompt, set -v to not play.
  607.  
  608.     Model Settings
  609.     -@, --alpha  [[VAL%]COLOUR]
  610.         Set transparent colour of image mask. Def=black.
  611.         Fuzz intensity can be set with [VAL%]. Def=0%.
  612.     -Nill
  613.         Unset model max response (chat cmpls only).
  614.     -NUM
  615.     -M, --max  [NUM[-NUM]]
  616.         Set maximum number of \`response tokens'. Def=$OPTMAX.
  617.         A second number in the argument sets model capacity.
  618.     -N, --modmax    [NUM]
  619.         Set \`model capacity' tokens. Def=_auto_, Fallback=4000.
  620.     -a, --presence-penalty   [VAL]
  621.         Set presence penalty  (cmpls/chat, -2.0 - 2.0).
  622.     -A, --frequency-penalty  [VAL]
  623.         Set frequency penalty (cmpls/chat, -2.0 - 2.0).
  624.     -b, --best-of   [NUM]
  625.         Set best of, must be greater than opt -n (cmpls). Def=1.
  626.     -B, --logprobs  [NUM]
  627.         Request log probabilities, see -Z (cmpls, 0 - 5),
  628.     -j, --seed  [NUM]
  629.         Set a seed for deterministic sampling (integer).
  630.     -K, --top-k     [NUM]
  631.         Set Top_k value (local-ai, ollama, google).
  632.     --keep-alive, --ka [NUM]
  633.         Set how long the model will stay loaded into memory (ollama).
  634.     -m, --model     [MOD]
  635.         Set language MODEL name or set it as \`.' to pick
  636.         from the list. Def=$MOD, $MOD_CHAT.
  637.     --multimodal, --vision, --audio
  638.         Set model multimodal mode.
  639.     -n, --results   [NUM]
  640.         Set number of results. Def=$OPTN.
  641.     -p, --top-p     [VAL]
  642.         Set Top_p value, nucleus sampling (cmpls/chat, 0.0 - 1.0).
  643.     -r, --restart   [SEQ]
  644.         Set restart sequence string (cmpls).
  645.     -R, --start     [SEQ]
  646.         Set start sequence string (cmpls).
  647.     -s, --stop      [SEQ]
  648.         Set stop sequences, up to 4. Def=\"<|endoftext|>\".
  649.     -S, --instruction  [INSTRUCTION|FILE]
  650.         Set an instruction prompt. It may be a text file.
  651.     -t, --temperature  [VAL]
  652.         Set temperature value (cmpls/chat/whisper),
  653.         Def=${OPTT:-0} (0.0 - 2.0), Whisper=${OPTTW:-0} (0.0 - 1.0).
  654.  
  655.     Miscellanous Settings
  656.     --api-key  [KEY]
  657.         Set API key to use.
  658.     --fold (defaults), --no-fold
  659.         Set or unset response folding (wrap at white spaces).
  660.     -h, --help
  661.         Print this help page.
  662.     -k, --no-colour
  663.         Disable colour output. Def=auto.
  664.     -l, --list-models  [MOD]
  665.         List models or print details of MODEL.
  666.     -L, --log   [FILEPATH]
  667.         Set log file. FILEPATH is required.
  668.     --md, --markdown, --markdown=[SOFTWARE]
  669.         Enable markdown rendering in response. Software is optional:
  670.         \`bat', \`pygmentize', \`glow', \`mdcat', or \`mdless'.
  671.     --no-md, --no-markdown
  672.         Disable markdown rendering.
  673.     -o, --clipboard
  674.         Copy response to clipboard.
  675.     -v, --verbose
  676.         Less verbose. With -ccwv, sleep after response. With
  677.         -ccwzvv, stop recording voice input on silence and play
  678.         TTS response right away. May be set multiple times.
  679.     -V      Dump raw request block to stderr (debug).
  680.     --version
  681.         Print script version.
  682.     -y, --tik
  683.         Set tiktoken for token count (cmpls/chat).
  684.     -Y, --no-tik  (defaults)
  685.         Unset tiktoken use (cmpls/chat).
  686.     -Z, -ZZ, -ZZZ, --last
  687.         Print data from the last JSON responses."
  688.  
  689. ENDPOINTS=(
  690.     /completions               #0
  691.     /moderations               #1
  692.     /edits                     #2  -> chat/completions
  693.     /images/generations        #3
  694.     /images/variations         #4
  695.     /embeddings                #5
  696.     /chat/completions          #6
  697.     /audio/transcriptions      #7
  698.     /audio/translations        #8
  699.     /images/edits              #9
  700.     /audio/speech              #10
  701.     /models                    #11
  702.     #/realtime                 #12
  703. )
  704. #https://platform.openai.com/docs/{deprecations/,models/,model-index-for-researchers/}
  705. #https://help.openai.com/en/articles/{6779149,6643408}
  706.  
  707. #set model endpoint based on its name
  708. function set_model_epnf
  709. {
  710.     unset OPTEMBED TKN_ADJ EPN6 MULTIMODAL
  711.     typeset -l model; model=${1##*/};
  712.     set -- "${model##ft:}";
  713.  
  714.     if is_amodelf "$1"
  715.     then    set -- "audio";
  716.     elif is_visionf "$1"
  717.     then    set -- "vision";
  718.     fi;
  719.  
  720.     case "${1##ft:}" in
  721.         *dalle-e*|*stable*diffusion*)
  722.                 # 3 generations  4 variations  9 edits  
  723.                 ((OPTII)) && EPN=4 || EPN=3;
  724.                 ((OPTII_EDITS)) && EPN=9;;
  725.         tts-*|*-tts-*)  EPN=10;;
  726.         *whisper*)  ((OPTWW)) && EPN=8 || EPN=7;;
  727.         code-*)     case "$1" in
  728.                     *search*)   EPN=5 OPTEMBED=1;;
  729.                     *)      EPN=0;;
  730.                 esac;;
  731.         text-*|*turbo-instruct*|*davinci*|*babbage*|ada|*moderation*|*embed*|*similarity*|*search*)
  732.                 case "$1" in
  733.                     *embed*|*similarity*|*search*)  EPN=5 OPTEMBED=1;;
  734.                     *moderation*)   EPN=1 OPTEMBED=1;;
  735.                     *)      EPN=0;;
  736.                 esac;;
  737.         o[1-9]*|chatgpt-*|gpt-[4-9]*|gpt-3.5*|gpt-*|*turbo*|*vision*|*audio*)
  738.                 EPN=6 EPN6=6  OPTB= OPTBB=
  739.                 ((OPTC)) && OPTC=2
  740.                 #set token adjustment per message
  741.                 case "$MOD" in
  742.                     gpt-3.5-turbo-0301)     ((TKN_ADJ=4+1));;
  743.                     gpt-3.5-turbo*|gpt-4*|*)    ((TKN_ADJ=3+1));;
  744.                 esac #https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
  745.                 #also: <https://tiktokenizer.vercel.app/>
  746.                 ;;
  747.         *)      #fallback
  748.                 case "$1" in
  749.                     *embed*|*similarity*|*search*)
  750.                         EPN=5 OPTEMBED=1;;
  751.                     *)
  752.                         if ((OPTZ && !(MTURN+CHAT_ENV) ))
  753.                         then    OPTCMPL= OPTC= EPN=10;
  754.                         elif ((OPTW && !(MTURN+CHAT_ENV) ))
  755.                         then    OPTCMPL= OPTC= EPN=7;
  756.                         elif ((OPTI && !(MTURN+CHAT_ENV) ))
  757.                         then    # 3 generations  4 variations  9 edits  
  758.                             ((OPTII)) && EPN=4 || EPN=3;
  759.                             ((OPTII_EDITS)) && EPN=9;
  760.                         elif ((OPTEMBED))
  761.                         then    OPTCMPL= OPTC= EPN=1;
  762.                         elif ((OPTCMPL || OPTSUFFIX))
  763.                         then    OPTC= EPN=0;
  764.                         elif ((OPTC>1 || GROQAI || MISTRALAI || GOOGLEAI || GITHUBAI))
  765.                         then    OPTCMPL= EPN=6;
  766.                         elif ((OPTC))
  767.                         then    OPTCMPL= EPN=0;
  768.                         else    EPN=0;  #defaults
  769.                         fi;;
  770.                 esac
  771.                 return 1;;
  772.     esac
  773. }
  774.  
  775. #set ``model capacity''
  776. function model_capf
  777. {
  778.     typeset -l model; model=${1##*/};
  779.     case "${model##ft:}" in  #ft: fine-tune models
  780.         open-codestral-mamba*|codestral-mamba*|ai21-jamba-1.5*|ai21-jamba-instruct)
  781.             MODMAX=256000;;
  782.         open-mixtral-8x22b)
  783.             MODMAX=64000;;
  784.         claude-[3-9]*|claude-2.1*)
  785.             MODMAX=200000;;
  786.         claude-2.0*|claude-instant*)
  787.             MODMAX=100000;;
  788.         llama3*|gemma-*|text-embedding-ada-002|*embedding*-002|*search*-002)
  789.             MODMAX=8191;;
  790.         davinci|curie|babbage|ada)
  791.             MODMAX=2049;;
  792.         meta-llama-3-70b-instruct|meta-llama-3-8b-instruct|\
  793.         code-davinci-00[2-9]*|mistral-embed*|-8k*)
  794.             MODMAX=8001;;
  795.         gemini*-flash*)
  796.             MODMAX=1048576;;  #std: 128000
  797.         gemini*-1.[5-9]*|gemini*-[2-9].[0-9]*)
  798.             MODMAX=2097152;;  #std: 128000
  799.         *llama-3-8b-instruct|*llama-3-70b-instruct|*gemma-2-9b-it|*hermes-2-pro-llama-3-8b)
  800.             MODMAX=8192;;
  801.         *qwen-2.5-72b-instruct)
  802.             MODMAX=32000;;
  803.         *l3-70b-euryale-v2.1|*l31-70b-euryale-v2.2|*dolphin-mixtral-8x22b)
  804.             MODMAX=16000;;
  805.         *llama-3.1-8b-instruct|davinci-00[2-9]|babbage-00[2-9]|gpt-3.5*16k*|\
  806.         *turbo*16k*|gpt-3.5-turbo-1106|gemini*-vision*|*-16k*)
  807.             MODMAX=16384;;
  808.         *llama-3.1-70b-instruct|*llama-3.1-405b-instruct|*mistral-7b-instruct|*wizardlm-2-7b|*qwen-2-7*b-instruct*|\
  809.         gpt-4*32k*|*32k|*mi[sx]tral*|*codestral*|mistral-small|*mathstral*|*moderation*)
  810.             MODMAX=32768;;
  811.         o[1-9]*|gpt-4[a-z]*|chatgpt-*|gpt-[5-9]*|gpt-4-1106*|\
  812.         gpt-4-*preview*|gpt-4-vision*|gpt-4-turbo|gpt-4-turbo-202[4-9]-*|\
  813.         mistral-3b*|*mistral-nemo*|*mistral-large*|open-mistral-nemo*|phi-3.5-mini-instruct|\
  814.         phi-3.5-moe-instruct|phi-3.5-vision-instruct|\
  815.         cohere-command-r*|*llama-[3-9].[1-9]*|*llama[4-9]-*|\
  816.         *llama[4-9]*|*ministral*|*pixtral*|*-128k*)
  817.             MODMAX=128000;;  #131072
  818.         *openchat-7b|*mythomax-l2-13b|*airoboros-l2-70b|*lzlv_70b|*nous-hermes-llama2-13b|\
  819.         *openhermes-2.5-mistral-7b|*midnight-rose-70b|\
  820.         gpt-4*|*-bison*|*-unicorn|text-davinci-002-render-sha|\
  821.         *wizardlm-2-8x22b)
  822.             MODMAX=65535;;
  823.         gemini*-pro*)   MODMAX=32760;;
  824.         *turbo*|*davinci*)  MODMAX=4096;;
  825.         cohere-embed-v3-*)  MODMAX=1000;;
  826.         *embedding-gecko*)  MODMAX=3072;;
  827.         *embed*|*search*)   MODMAX=2046;;
  828.         aqa)    MODMAX=7168;;
  829.         *-4k*)  MODMAX=4000;;
  830.         *)  MODMAX=4000;;
  831.     esac
  832. }
  833. #novita: model names: [provider]/[model]
  834. #groq: 3.1 models to max_tokens of 8k and 405b to 16k input tokens.
  835. #pixtral: maximum number images per request is 8.
  836. #https://blog.google/technology/ai/google-gemini-next-generation-model-february-2024/
  837.  
  838. #make cmpls request
  839. function __promptf
  840. {
  841.     if curl "$@" ${FAIL} -L "${BASE_URL}${ENDPOINTS[EPN]}" \
  842.         -X POST \
  843.         -H "Content-Type: application/json" \
  844.         -H "Authorization: Bearer ${MISTRAL_API_KEY:-$OPENAI_API_KEY}" \
  845.         -d "$BLOCK"  $CURLTIMEOUT
  846.     then    [[ \ $*\  = *\ -s\ * ]] || _clr_lineupf;
  847.     else    return $?;  #E#
  848.     fi
  849. }
  850.  
  851. function _promptf
  852. {
  853.     typeset chunk_n chunk str n
  854.     chunk= chunk_n= n=;
  855.     json_minif;
  856.    
  857.     if ((STREAM))
  858.     then    set -- -s "$@" -S --no-buffer;
  859.           [[ -s $FILE ]] && mv -f -- "$FILE" "${FILE%.*}.2.${FILE##*.}"; : >"$FILE"  #clear buffer asap
  860.         __promptf "$@" | { tee -- "$FILESTREAM" || cat ;} |
  861.         if ((GOOGLEAI))
  862.         then    cat;
  863.         elif ((ANTHROPICAI))
  864.         then    sed -n -e 's/^[[:space:]]*//' -e 's/^[[:space:]]*[Dd][Aa][Tt][Aa]:[[:space:]]*//p' -e '/^[[:space:]]*\[[A-Za-z_][A-Za-z_]*\]/d' -e '/^[[:space:]]*$/d';
  865.         else    sed -e 's/^[[:space:]]*//' -e 's/^[[:space:]]*[Dd][Aa][Tt][Aa]:[[:space:]]*//' -e '/^[[:space:]]*[A-Za-z_][A-Za-z_]*:/d' -e '/^[[:space:]]*\[[A-Za-z_][A-Za-z_]*\]/d' -e '/^[[:space:]]*$/d';
  866.         fi |
  867.         while IFS=  read -r chunk  #|| [[ -n $chunk ]]
  868.         do
  869.             ## Anthropic sends lots more than only '[DATA]:' fields:
  870.             ### 'event: message_start'  
  871.             ## Google hack does not pass 'DATA:'.
  872.             #((ANTHROPICAI)) && { [[ ${chunk:0:128} = [Dd][Aa][Tt][Aa]:* ]] || continue ;}
  873.             ## 'DATA: {json}', 'data: [DONE]'
  874.             #chunk=${chunk##*(\ )[Dd][Aa][Tt][Aa]:*(\ )}
  875.             #[[ ${chunk:0:256} = *[!$IFS]* ]] || continue
  876.             #[[ ${chunk:0:256} = \[+([A-Z])\] ]] && continue
  877.             if ((!n))  #first pass, del leading spaces
  878.             then    ((OPTC)) && {
  879.                     str='text":'; ((GOOGLEAI)) ||
  880.                     if ((EPN==0)) && ((OLLAMA))
  881.                     then    str='response":';
  882.                     elif ((EPN==6))
  883.                     then    str='content":';
  884.                     fi
  885.                     chunk_n="${chunk/${str}*(\ )\"+(\ |\\[ntr])/$str\"}"
  886.                     [[ $chunk_n = *"${str}"\"\"* ]] && continue
  887.                 }; ((++n));
  888.                 printf '%s\n' "${chunk_n:-$chunk}"; chunk_n= ;
  889.             else    printf '%s\n' "$chunk"
  890.             fi;     printf '%s\n' "$chunk" >>"$FILE"
  891.         done
  892.     else
  893.         { test_cmplsf || ((OPTV>1)) ;} && set -- -s "$@"
  894.         set -- -\# "$@" -o "$FILE"
  895.         __promptf "$@"
  896.     fi
  897. }
  898.  
  899. function promptf
  900. {
  901.     typeset pid ret
  902.  
  903.     if ((OPTVV)) && ((!OPTII))
  904.     then    block_printf || {
  905.           REPLY=${REPLY_CMD:-$REPLY} REGEN= JUMP= PSKIP=;
  906.           REPLY_CMD_DUMP= REPLY_CMD_BLOCK= SKIP_SH_HIST= WSKIP= SKIP=;  #E#
  907.           return 200 ;}
  908.     fi
  909.  
  910.     if ((STREAM))
  911.     then    : >"$FILETXT"; RET_APRF=;
  912.         if ((PREVIEW>1))
  913.         then    cat -- "$FILE"
  914.         else    test_cmplsf || ((OPTV>1)) || printf "${BYELLOW}%s\\b${NC}" "X" >&2;
  915.             _promptf || exit;  #!#
  916.         fi | {  prompt_printf; ret=$?; printf '%s' "${RET_APRF##0}" >"$FILETXT"; exit $ret ;}
  917.     else
  918.         test_cmplsf || ((OPTV>1)) || printf "${BYELLOW}%*s\\r${YELLOW}" "$COLUMNS" "X" >&2;
  919.         ((PREVIEW>1)) || COLUMNS=$((COLUMNS-1)) _promptf || exit;  #!#
  920.         printf "${NC}" >&2;
  921.         if ((OPTI))
  922.         then    prompt_imgprintf
  923.         else    prompt_printf
  924.         fi
  925.     fi & pid=$! PIDS+=($!)  #catch <CTRL-C>
  926.    
  927.     trap "trap 'exit' INT; kill -- $pid 2>/dev/null; echo >&2;" INT;
  928.     wait $pid; echo >&2;
  929.     trap 'exit' INT; RET_APRF=;
  930.     [[ -s $FILETXT ]] && {  RET_APRF=$(<$FILETXT); : >"$FILETXT" ;}
  931.  
  932.     if ((OPTCLIP)) || [[ ! -t 1 ]]
  933.     then    typeset out; out=$(
  934.             ((STREAM)) && set -- -j "$@"
  935.             prompt_pf -r "$@" "$FILE"
  936.         )
  937.         ((!OPTCLIP)) || (${CLIP_CMD:-false} <<<"$out" &)  #clipboard
  938.         [[ -t 1 ]] || printf '%s\n' "$out" >&2  #pipe + stderr
  939.     fi
  940.     wait $pid;  #curl exit code
  941. }
  942.  
  943. #print tokens from response
  944. function response_tknf
  945. {
  946.     jq -r '(.usage.prompt_tokens)//"0",
  947.         (.usage.completion_tokens)//"0",
  948.         (.usage.completion_tokens_details|(.reasoning_tokens//.audio_tokens))//"0",
  949.         (.created//empty|strflocaltime("%Y-%m-%dT%H:%M:%S%Z"))' "$@";
  950. }
  951. #https://community.openai.com/t/usage-stats-now-available-when-using-streaming-with-the-chat-completions-api-or-completions-api/738156
  952.  
  953. #position cursor at end of screen
  954. function _clr_dialogf
  955. {
  956.     printf "${NC}\\n\\n\\033[${LINES};1H" >&2;
  957. }
  958. function __clr_dialogf {    ((DIALOG_CLR)) && _clr_dialogf ;}
  959.  
  960. #clear impending stream (tty)
  961. function _clr_ttystf
  962. {
  963.     typeset REPLY n;
  964.     while IFS= read -r -n 1 -t 0.1;
  965.     do  ((++n)); ((n<16384)) || break;
  966.     done </dev/tty;
  967. }
  968.  
  969. #clear n lines up as needed (assumes one `new line').
  970. function _clr_lineupf
  971. {
  972.     typeset chars n
  973.     chars="${1:-1}" ;((COLUMNS))||COLUMNS=80
  974.     for ((n=0;n<((chars+(COLUMNS-1))/COLUMNS);++n))
  975.     do  printf '\e[A\e[K' >&2
  976.     done
  977. }
  978. #https://www.zsh.org/mla/workers//1999/msg01550.html
  979. #https://superchlorine.com/2013/08/kill-winch-to-fix-bash-prompt-wrapping-to-the-same-line/
  980.  
  981. # spin.bash -- provide a `spinning wheel' to show progress
  982. #  Copyright 1997 Chester Ramey (adapted)
  983. SPIN_CHARS8=(⣟ ⣯ ⣷ ⣾ ⣽ ⣻ ⢿ ⡿)
  984. SPIN_CHARS0=(. o O @ \*)
  985. SPIN_CHARS=(\| \\ - /)
  986. function _spinf
  987. {
  988.     ((++SPIN_INDEX)); ((SPIN_INDEX%=${#SPIN_CHARS[@]}));
  989.     printf "%s\\b" "${SPIN_CHARS[SPIN_INDEX]}" >&2;
  990. }
  991. #avoid animations on pipelines
  992. [[ -t 1 ]] || function _spinf { : ;}
  993.  
  994. #print input and backspaces for all chars
  995. function _printbf {     printf "%s${1//?/\\b}" "${1}" >&2; };
  996.  
  997. #trim leading glob
  998. #usage: trim_leadf [string] [glob]
  999. function trim_leadf
  1000. {
  1001.     typeset var ind sub
  1002.     var="$1" ind=${INDEX:-320}
  1003.     sub="${var:0:$ind}"
  1004.     ((SMALLEST)) && sub="${sub#$2}" || sub="${sub##$2}"
  1005.     var="${sub}${var:$ind}"
  1006.     printf '%s\n' "$var"
  1007. }
  1008. #trim trailing glob
  1009. #usage: trim_trailf [string] [glob]
  1010. function trim_trailf
  1011. {
  1012.     typeset var ind sub
  1013.     var="$1" ind=${INDEX:-320}
  1014.     if ((${#var}>ind))
  1015.     then    sub="${var:$((${#var}-${ind}))}"
  1016.         ((SMALLEST)) && sub="${sub%$2}" || sub="${sub%%$2}"
  1017.         var="${var:0:$((${#var}-${ind}))}${sub}"
  1018.     else    ((SMALLEST)) && var="${var%$2}" || var="${var%%$2}"
  1019.     fi; printf '%s\n' "$var"
  1020. }
  1021. #fast shell glob trimmer
  1022. #usage: trimf [string] [glob]
  1023. function trimf
  1024. {
  1025.     trim_leadf "$(trim_trailf "$1" "$2")" "$2"
  1026. }
  1027.  
  1028. #pretty print request body or dump and exit
  1029. function block_printf
  1030. {
  1031.     typeset REPLY; OPTAWE= SKIP= ;
  1032.     [[ ${BLOCK:0:32} = @* ]] && cat -- "${BLOCK##@}" | less >&2
  1033.     printf '\n%s\n%s\n' "${ENDPOINTS[EPN]}" "$BLOCK" >&2
  1034.     printf '\n%s\n' '<Enter> continue, <Ctrl-D> redo, <Ctrl-C> exit' >&2
  1035.     _clr_ttystf; read </dev/tty || return 200;
  1036. }
  1037.  
  1038. #prompt confirmation prompter
  1039. function new_prompt_confirmf
  1040. {
  1041.     typeset REPLY extra
  1042.     case \ $*\  in  *\ ed\ *) extra=", te[x]t editor, m[u]ltiline";; esac;
  1043.     case \ $*\  in  *\ whisper\ *)  ((OPTW)) && extra="${extra}, [W]hsp_append, [w]hsp_off, w[h]sp_retry";; esac;
  1044.  
  1045.     _sysmsgf 'Confirm?' "[Y]es, [n]o, [e]dit${extra}, [r]edo, or [a]bort " ''
  1046.     REPLY=$(read_charf); _clr_lineupf $((8+1+40+${#extra}))  #!#
  1047.     case "$REPLY" in
  1048.         [Q])    return 202;;  #exit
  1049.         [aq])   return 201;;  #abort
  1050.         [Rr])   return 200;;  #redo
  1051.         [Ee]|$'\e')     return 199;;  #edit
  1052.         [VvXx]) return 198;;  #text editor
  1053.         [UuMm]) return 197;;  #multiline
  1054.         [w])    return 196;;  #whisper off
  1055.         [WAPp]) return 195;;  #whisper append
  1056.         [HhTt]) return 194;;  #whisper retry request
  1057.         [NnOo]) REC_OUT=; return 1;;  #no
  1058.         [-/!])  echo >&2; read_mainf -i "$REPLY" REPLY; echo >&2;
  1059.             BLOCK_USR= BREAK_SET= EDIT= ENDPOINTS= HERR= JUMP= \
  1060.             MAIN_LOOP= PREVIEW= REGEN= REPLAY_FILES= REPLY= \
  1061.             REPLY_CMD_BLOCK= REPLY_CMD_DUMP= REPLY_OLD= RESTART= START= \
  1062.             RESUBW= RET= SKIP= SKIP_SH_HIST=  BCYAN= CYAN= ON_CYAN= \
  1063.             cmd_runf "$REPLY";
  1064.             new_prompt_confirmf "$@";;
  1065.     esac  #yes
  1066. }
  1067.  
  1068. #read one char from user
  1069. function read_charf
  1070. {
  1071.     typeset REPLY ret
  1072.     ((NO_CLR)) || _clr_ttystf;
  1073.     IFS=$'\n' read -r -n 1 "$@" </dev/tty; ret=$?;
  1074.     printf '%.1s\n' "$REPLY";
  1075.     [[ -n $REPLY ]] && echo >&2;
  1076.     return $ret
  1077. }
  1078.  
  1079. #main user input read
  1080. #usage: read_mainf [read_opt].. VARIABLE_NAME
  1081. function read_mainf
  1082. {
  1083.     IFS= read -r -e -d $'\r' ${OPTCTRD:+-d $'\04'} "$@"
  1084. }
  1085. #https://www.reddit.com/r/bash/comments/ppp6a2/is_there_a_way_to_paste_multiple_lines_where_read/
  1086.  
  1087. #audio-model player
  1088. #play from an appending pcm16 audio file
  1089. function splayerf
  1090. (
  1091.     trap 'exit' INT TERM;
  1092.     : > "${1}" || exit;
  1093.    
  1094.     ((${#TERMUX_VERSION})) && sleep 0.8;
  1095.     for ((n=0;n<12;n++))  #wait for buffer
  1096.     do  sleep 0.6; [[ -s "${1}" ]] &&
  1097.         (( $(wc -c <${1}) > 64000)) && break;
  1098.     done
  1099.    
  1100.     if command -v ffplay
  1101.     then    ffplay -autoexit -nodisp -hide_banner -f s16le -ar 24000 -i "${1}";
  1102.     elif command -v sox
  1103.     then    sox -t raw -b 16 -e signed-integer -L -r 24000 -c 1 "${1}" -d;
  1104.     elif command -v mpv
  1105.     then    mpv --demuxer=rawaudio --demuxer-rawaudio-format=s16le --demuxer-rawaudio-rate=24000 --demuxer-rawaudio-channels=1 "${1}";
  1106.     elif command -v cvlc
  1107.     then    cvlc --play-and-exit --no-loop --no-repeat -I dummy --demux rawaud --rawaud-channels 1 --rawaud-samplerate 24000 --rawaud-fourcc s16l "${1}";
  1108.     else    false;
  1109.     fi >/dev/null 2>&1;
  1110. )
  1111.  
  1112. #print response
  1113. function prompt_printf
  1114. {
  1115.     typeset pid ret
  1116.  
  1117.     if ((STREAM))
  1118.     then    typeset OPTC OPTV;
  1119.     else    set -- "$FILE"; unset STREAM;
  1120.         ((OPTBB)) && jq -r '(.choices[].logprobs)?' "$@" >&2
  1121.     fi
  1122.     if ((OPTEMBED))
  1123.     then    jq -r '(.data),
  1124.         (.model//"'"$MOD"'"//"?")+" ("+(.object//"?")+") ["
  1125.         +(.usage.prompt_tokens//"?"|tostring)+" / "
  1126.         +(.usage.total_tokens//"?"|tostring)+" tkns]"' "$@" >&2
  1127.         return
  1128.     fi
  1129.  
  1130.     if ((OPTMD)) && ((MD_CMD_UNBUFF))
  1131.     then
  1132.         JQCOL= JQCOL2= prompt_prettyf "$@" | mdf;
  1133.     else
  1134.         #audio-models
  1135.         if ((MULTIMODAL>1)) && ((OPTZ)) && ((STREAM)) && if [[ -n $TERMUX_VERSION ]]
  1136.             then    OPTV=2 set_termuxpulsef;
  1137.             else    :; fi;
  1138.         then
  1139.             splayerf "$FILEFIFO" & pid=$! PIDS+=($!);
  1140.             tee >(
  1141.                 jq -r '(.choices|.[]?|.delta.audio.data)//empty' |
  1142.                 {   base64 -d || base64 -D ;} >"$FILEFIFO";
  1143.                 ) | prompt_prettyf "$@" | foldf; ret=$?;
  1144.  
  1145.             wait $pid;
  1146.             kill -0 $pid >/dev/null 2>&1 && kill -9 -- $pid >/dev/null 2>&1;
  1147.         else
  1148.             prompt_prettyf "$@" | foldf; ret=$?;
  1149.         fi
  1150.         if ((OPTMD))
  1151.         then    printf "${NC}\\n" >&2;
  1152.             prompt_pf -r ${STREAM:+-j --unbuffered} "$@" "$FILE" 2>/dev/null | mdf >&2 2>/dev/null;
  1153.         fi; ((!ret));
  1154.     fi || prompt_pf -r ${STREAM:+-j --unbuffered} "$@" "$FILE" 2>/dev/null;
  1155.     return $ret;
  1156. }
  1157. function prompt_prettyf
  1158. {
  1159.     ((STREAM)) || unset STREAM;
  1160.  
  1161.     jq -r ${STREAM:+-j --unbuffered} "${JQCOLNULL} ${JQCOL} ${JQCOL2}
  1162.       byellow
  1163.       + (.choices[1].index as \$sep | if .choices? != null then .choices[] else . end |
  1164.       ( ((.delta.content)//(.delta.text)//(.delta.audio.transcript)//.text//.response//.completion//(.content[]?|.text?)//(.message.content${ANTHROPICAI:+skip})//(.message.audio.transcript)//(.candidates[]?|.content.parts[]?|.text?)//\"\" ) |
  1165.       if ( (${OPTC:-0}>0) and (${STREAM:-0}==0) ) then (gsub(\"^[\\\\n\\\\t ]\"; \"\") |  gsub(\"[\\\\n\\\\t ]+$\"; \"\")) else . end)
  1166.       + if any( (.finish_reason//.stop_reason//\"\")?; . != \"stop\" and . != \"stop_sequence\" and . != \"end_turn\" and . != \"\") then
  1167.           red+\"(\"+(.finish_reason//.stop_reason)+\")\"+byellow else null end,
  1168.       if \$sep then \"---\" else empty end) + reset" "$@" && _p_suffixf;
  1169. }  #finish_reason: length, max_tokens
  1170. function prompt_pf
  1171. {
  1172.     typeset var
  1173.     typeset -a opt; opt=();
  1174.     for var
  1175.     do  [[ -f $var ]] || {  opt=("${opt[@]}" "$var"); shift ;}
  1176.     done
  1177.     set -- "(if .choices? != null then (.choices[$INDEX]) else . end |
  1178.         (.delta.content)//(.delta.text)//(.delta.audio.transcript)//.text//.response//.completion//(.content[]?|.text?)//(.message.content${ANTHROPICAI:+skip})//(.message.audio.transcript)//(.candidates[]?|.content.parts[]?|.text?)//(.data?))//empty" "$@"
  1179.     jq "${opt[@]}" "$@" && _p_suffixf || ! _warmsgf 'Err';
  1180. }
  1181. #https://stackoverflow.com/questions/57298373/print-colored-raw-output-with-jq-on-terminal
  1182. #https://stackoverflow.com/questions/40321035/
  1183.  
  1184. #print suffix string
  1185. function _p_suffixf {   ((!${#SUFFIX} )) || printf '%s' "${SUFFIX}" ;}
  1186.  
  1187. #print last line of input that is within $columns range
  1188. #usage: _p_linerf [string]
  1189. function _p_linerf
  1190. {
  1191.     typeset var
  1192.     var=$(sed -n '$p' <<<$1);
  1193.     var=$((${#var} % COLUMNS));
  1194.     ((var)) && printf '\n%s' "${1: ${#1}-${var}}" >&2;
  1195. }
  1196.  
  1197. #open image with sys defaults
  1198. function _openf
  1199. {
  1200.     if command -v xdg-open >/dev/null 2>&1
  1201.     then    xdg-open "$1"
  1202.     elif command -v open >/dev/null 2>&1
  1203.     then    open "$1"
  1204.     elif command -v feh >/dev/null 2>&1
  1205.     then    feh "$1"
  1206.     elif command -v sxiv >/dev/null 2>&1
  1207.     then    sxiv "$1"
  1208.     elif command -v firefox >/dev/null 2>&1
  1209.     then    firefox "$1"
  1210.     elif command -v google-chrome-stable >/dev/null 2>&1
  1211.     then    google-chrome-stable "$1"
  1212.     elif command -v google-chrome >/dev/null 2>&1
  1213.     then    google-chrome "$1"
  1214.     else    false
  1215.     fi
  1216. }
  1217. #https://budts.be/weblog/2011/07/xdf-open-vs-exo-open/
  1218.  
  1219. #print image endpoint response
  1220. function prompt_imgprintf
  1221. {
  1222.     typeset n m fname fout
  1223.     if [[ $OPTI_FMT = b64_json ]]
  1224.     then    [[ -d "${FILEOUT%/*}" ]] || FILEOUT="${FILEIN}"
  1225.         n=0 m=0
  1226.         for fname in "${FILEOUT%.*}"*
  1227.         do  fname="${fname%.*}" fname="${fname##*[!0-9]}"
  1228.             ((m>fname)) || ((m=fname+1))
  1229.         done
  1230.         while jq -e ".data[${n}]" "$FILE" >/dev/null 2>&1
  1231.         do  fout="${FILEOUT%.*}${m}.png"
  1232.             jq -r ".data[${n}].b64_json" "$FILE" | {    base64 -d || base64 -D ;} > "$fout"
  1233.             _sysmsgf 'File Out:' "${fout/"$HOME"/"~"}";
  1234.             ((OPTV)) ||  _openf "$fout" || function _openf { : ;}
  1235.             ((++n, ++m)); ((n<50)) || break;
  1236.         done
  1237.         ((n)) || ! _warmsgf 'Err';
  1238.     else    jq -r '.data[].url' "$FILE" || ! _warmsgf 'Err';
  1239.     fi &&
  1240.     jq -r 'if .data[].revised_prompt then "\nREVISED PROMPT: "+.data[].revised_prompt else empty end' "$FILE" >&2
  1241. }
  1242.  
  1243. function prompt_audiof
  1244. {
  1245.     ((OPTVV)) && _warmsgf "Whisper:" "Model: ${MOD_AUDIO:-unset},  Temperature: ${OPTTW:-${OPTT:-unset}}${*:+,  }${*}" >&2
  1246.  
  1247.     curl -\# ${OPTV:+-Ss} ${FAIL} -L "${BASE_URL}${ENDPOINTS[EPN]}" \
  1248.         -X POST \
  1249.         -H "Authorization: Bearer $OPENAI_API_KEY" \
  1250.         -H 'Content-Type: multipart/form-data' \
  1251.         -F file="@$1" \
  1252.         -F model="${MOD_AUDIO}" \
  1253.         -F temperature="${OPTTW:-$OPTT}" \
  1254.         -o "$FILE" \
  1255.         "${@:2}" && {
  1256.       [[ -d $CACHEDIR ]] && printf '%s\n\n' "$(<"$FILE")" >> "$FILEWHISPER";
  1257.       ((OPTV)) || _clr_lineupf; ((CHAT_ENV)) || echo >&2;
  1258.     }
  1259. }
  1260.  
  1261. function list_modelsf
  1262. {
  1263.     curl -\# ${FAIL} -L "${BASE_URL}${ENDPOINTS[11]}${1:+/}${1}" \
  1264.         -H "Authorization: Bearer $OPENAI_API_KEY" -o "$FILE" &&
  1265.  
  1266.     if [[ -n $1 ]]
  1267.     then    jq . "$FILE" || ! _warmsgf 'Err';
  1268.     else    {   jq -r '.data[].id' "$FILE" | sort &&
  1269.             {    ((LOCALAI+OLLAMA+MISTRALAI+GOOGLEAI+GROQAI+ANTHROPICAI+GITHUBAI+NOVITAAI)) || [[ $BASE_URL != "$OPENAI_BASE_URL_DEF" ]] ||
  1270.             printf '%s\n' text-moderation-latest text-moderation-stable omni-moderation-latest ;}
  1271.         } | tee -- "$FILEMODEL" || ! _warmsgf 'Err';
  1272.     fi || ! _warmsgf 'Err:' 'Model list'
  1273. }
  1274.  
  1275. function pick_modelf
  1276. {
  1277.     typeset REPLY mod options
  1278.     set -- "${1// }"; set -- "${1##*(0)}";
  1279.     ((${#1}<2)) || return  #mind o1 models
  1280.     ((${#MOD_LIST[@]})) || MOD_LIST=($(list_modelsf))
  1281.     if [[ ${REPLY:=$1} = +([0-9]) ]] && ((REPLY && REPLY <= ${#MOD_LIST[@]}))
  1282.     then    mod=${MOD_LIST[REPLY-1]}  #pick model by number from the model list
  1283.     else    _clr_ttystf; REPLY=${REPLY//[!0-9]};
  1284.         while ! ((REPLY && REPLY <= ${#MOD_LIST[@]}))
  1285.         do
  1286.             if test_dialogf
  1287.             then    options=( $(_dialog_optf ${MOD_LIST[@]:-err}) )
  1288.                 REPLY=$(
  1289.                   dialog --backtitle "Model Picker" --title "Selection Menu" \
  1290.                     --menu "Choose a model:" 0 40 0 \
  1291.                     -- "${options[@]}"  2>&1 >/dev/tty;
  1292.                 ) || typeset NO_DIALOG=1;
  1293.                 _clr_dialogf;
  1294.             else
  1295.                 echo $'\nPick model:' >&2;
  1296.                 select mod in ${MOD_LIST[@]:-err}
  1297.                 do  break;
  1298.                 done </dev/tty; REPLY=${REPLY//[$' \t\b\r']};
  1299.             fi;
  1300.             [[ \ ${MOD_LIST[*]:-err}\  = *\ "$REPLY"\ * ]] && mod=$REPLY && break;
  1301.         done;  #pick model by number or name
  1302.     fi; MOD=${mod:-$MOD};
  1303. }
  1304.  
  1305. function lastjsonf
  1306. {
  1307.     if ((OPTZZ>2)) && [[ -s $FILE_PRE ]]  #google response
  1308.     then    jq . "$FILE_PRE" 2>/dev/null || cat -- "$FILE_PRE";
  1309.         [[ -t 1 ]] && printf "${BWHITE}%s${NC}\\n" "$FILE_PRE" >&2;
  1310.     elif ((OPTZZ>1)) && [[ -s ${FILE%.*}.2.${FILE##*.} ]]  #old response
  1311.     then    jq . "${FILE%.*}.2.${FILE##*.}" 2>/dev/null || cat -- "${FILE%.*}.2.${FILE##*.}";
  1312.         [[ -t 1 ]] && printf "${BWHITE}%s${NC}\\n" "${FILE%.*}.2.${FILE##*.}" >&2;
  1313.     elif [[ -s $FILE ]]  #last response
  1314.     then    jq . "$FILE" 2>/dev/null || cat -- "$FILE";
  1315.         [[ -t 1 ]] && printf "${BWHITE}%s${NC}\\n" "$FILE" >&2;
  1316.     elif [[ -s $FILESTREAM ]]
  1317.     then    jq . "$FILESTREAM" 2>/dev/null || cat -- "$FILESTREAM";
  1318.         [[ -t 1 ]] && printf "${BWHITE}%s${NC}\\n" "$FILESTREAM" >&2;
  1319.     fi;
  1320. }
  1321.  
  1322. #set up context from history file ($HIST and $HIST_C)
  1323. function set_histf
  1324. {
  1325.     typeset time token string stringc stringd max_prev q_type a_type role role_last rest com sub ind herr nl x r n;
  1326.     time= token= string= stringc= stringd= max_prev= role= role_last= rest= sub= ind= nl=;
  1327.     typeset -a MEDIA MEDIA_CMD; MEDIA=(); MEDIA_CMD=(); HIST_LOOP=0;
  1328.     [[ -s $FILECHAT ]] || return; HIST= HIST_C=;
  1329.     ((BREAK_SET)) && return;
  1330.     ((OPTTIK)) && HERR_DEF=1 || HERR_DEF=4
  1331.     ((herr = HERR_DEF + HERR))  #context limit error
  1332.     q_type=${Q_TYPE##$SPC1} a_type=${A_TYPE##$SPC1}
  1333.     ((OPTC>1 || EPN==6)) && typeset A_TYPE="${A_TYPE} "  #pretty-print seq "\\nA: " ($rest)
  1334.     ((${#})) && token_prevf "${*}"
  1335.  
  1336.     while _spinf
  1337.         IFS=$'\t' read -r time token string
  1338.     do
  1339.         [[ ${time}${token} = *([$IFS])\#* ]] && { ((OPTHH>2)) && com=1 || continue ;}
  1340.         [[ ${time}${token} = *[Bb][Rr][Ee][Aa][Kk]* ]] && break
  1341.         [[ -z ${time}${token}${string} ]] && continue
  1342.         if [[ -z $string ]]
  1343.         then    [[ -n $token ]] && string=$token token=$time time=
  1344.             [[ -n $time  ]] && string=$time  token=  time=
  1345.         fi
  1346.  
  1347.         ((${#string}>1)) && string=${string:1:${#string}-2}  #del lead and trail ""
  1348.         #improve bash globbing speed with substring manipulation
  1349.         sub="${string:0:32}" sub="${sub##@("${q_type}"|"${a_type}"|":")}"
  1350.         stringc="${sub}${string:32}"  #del lead seqs `\nQ: ' and `\nA:'
  1351.  
  1352.         ((MOD_REASON)) && case "${string}" in :*)   continue;; esac;
  1353.  
  1354.         if ((OPTTIK || token<1))
  1355.         then    ((token<1 && OPTVV)) && _warmsgf "Warning:" "Zero/Neg token in history"
  1356.             start_tiktokenf
  1357.             if ((EPN==6))
  1358.             then    token=$(__tiktokenf "$(INDEX=32 trim_leadf "$stringc" :)" )
  1359.             else    token=$(__tiktokenf "\\n$(INDEX=32 trim_leadf "$stringc" :)" )
  1360.             fi; ((token+=TKN_ADJ))
  1361.         fi # every message follows <|start|>{role/name}\n{content}<|end|>\n (gpt-3.5-turbo-0301)
  1362.         #trail nls are rm in (text) chat modes, so actual request prompt token count may be *less*
  1363.         #we currently ignore (re)start seq tkns, always consider +3 tkns from $[QA]_TYPE
  1364.  
  1365.         if (( ( ( (max_prev+token+TKN_PREV)*(100+herr) )/100 ) < MODMAX-OPTMAX)) || {
  1366.             #truncate input to fit most of the model capacity
  1367.             if  (( x = ( (MODMAX-OPTMAX-max_prev-TKN_PREV)*(100-(herr*2) ) )/100 )); ((x>20));
  1368.             then    (( r = ( ( ( ( ( (x*100)/token) * x) / 100) ) * ${#stringc}) / token ));
  1369.                 (( token = ( ( ( (x*100)/token) * x) / 100) + 1 ));
  1370.                 stringc=${stringc:${#stringc}-r};
  1371.             fi
  1372.            (( ( ( (max_prev+token+TKN_PREV)*(100+herr) )/100 ) < MODMAX-OPTMAX))
  1373.         }
  1374.         then    ((++HIST_LOOP))
  1375.             ((max_prev+=token)); ((MAIN_LOOP)) || ((TOTAL_OLD+=token))
  1376.             MAX_PREV=$((max_prev+TKN_PREV))  HIST_TIME="${time##\#}"
  1377.  
  1378.             if ((OPTC))
  1379.             then    stringc=$(trim_leadf  "$stringc" "*(\\\\[ntr]| )")
  1380.                 stringc=$(trim_trailf "$stringc" "*(\\\\[ntr])")
  1381.             fi
  1382.  
  1383.             role_last=$role role= rest= nl=
  1384.             case "${string}" in
  1385.                 ::*)    role=system rest=  #[DEPRECATED]
  1386.                     stringc=$(INDEX=32 trim_leadf "$stringc" :)  #append (txt cmpls)
  1387.                     ;;
  1388.                 :*)     role=system
  1389.                     ((OPTC)) && rest="$S_TYPE" nl="\\n"  #system message
  1390.                     ;;
  1391.                 "${a_type:-%#}"*|"${START:-%#}"*)
  1392.                     role=assistant
  1393.                     if ((OPTC)) || [[ -n "${START}" ]]
  1394.                     then    rest="${START-${A_TYPE}}"
  1395.                     fi
  1396.                     ;;
  1397.                 *) #q_type, RESTART
  1398.                     role=user
  1399.                     if ((OPTC)) || [[ -n "${RESTART}" ]]
  1400.                     then    rest="${RESTART-${Q_TYPE}}"
  1401.                     fi
  1402.                     ;;
  1403.             esac
  1404.  
  1405.             #vision
  1406.             if ((!OPTHH)) && { is_visionf "$MOD" || is_amodelf "$MOD" ;}
  1407.             then    MEDIA=(); OPTV=100 media_pathf "$stringc"
  1408.             fi
  1409.  
  1410.             #print commented out lines ( $OPTHH > 2 )
  1411.             ((com)) && stringc=$(sed 's/\\n/\\n# /g' <<<"${rest}${stringc}") rest= com=
  1412.            
  1413.             HIST="${rest}${stringc}${nl}${HIST}"
  1414.             stringd=$(fmt_ccf "${stringc}" "${role}") && ((${#HIST_C}&&${#stringc})) && stringd=${stringd},${NL};
  1415.             if ((GOOGLEAI)) && [[ $role = @(system|user) && $role_last = @(system|user) ]] \
  1416.                 #&& [[ $stringd != *\"inline_data\":* ]]
  1417.             then    # must fail with inline image objects?!
  1418.                 #{"role": "%s", "parts": [ {"text": "%s"} ] }
  1419.                 HIST_C=$(SMALLEST=1 trim_leadf "$HIST_C" $'*"text":?( )"')
  1420.                 stringd=$(SMALLEST=1 trim_trailf "$stringd" $'"}*')"\\n\\n"
  1421.             fi
  1422.             ((EPN==6)) && HIST_C="${stringd}${HIST_C}"
  1423.         else    break
  1424.         fi
  1425.     done < <(tac -- "$FILECHAT")
  1426.     _printbf ' ' #_spinf() end
  1427.     ((MAX_PREV+=3)) # chat cmpls, every reply is primed with <|start|>assistant<|message|>
  1428.     # in text chat cmpls, prompt is primed with A_TYPE = 3 tkns
  1429.    
  1430.     #first system/instruction: add extra newlines and delete $S_TYPE  (txt cmpls)
  1431.     [[ $role = system ]] && if ((OLLAMA))
  1432.     then    ((OPTC && EPN==0)) && [[ $rest = \\n* ]] && rest="xx${rest}"  #!#del \n at start of string
  1433.         HIST="${HIST:${#rest}+${#stringc}}"  #delete first system message for ollama
  1434.     else    HIST="${HIST:${#rest}:${#stringc}}\\n${HIST:${#rest}+${#stringc}}"
  1435.     fi
  1436.  
  1437.     ((!OPTC)) || [[ $HIST = "$stringc"*(\\n) ]] ||  #hist contains only one/system prompt?
  1438.     HIST=$(trim_trailf "$HIST" "*(\\\\[ntrvf])")  #del multiple trailing nl
  1439.     HIST=$(trim_leadf "$HIST" "?(\\\\[ntrvf]|$NL)?( )")  #del one leading nl+sp
  1440. }
  1441. #https://thoughtblogger.com/continuing-a-conversation-with-a-chatbot-using-gpt/
  1442.  
  1443. #print the last line of tsv history file
  1444. function hist_lastlinef
  1445. {
  1446.     sed -n -e 's/\t"/\t/; s/"$//;' -e '$s/^[^\t]*\t[^\t]*\t//p' "$@" \
  1447.     | sed -e "s/^://; s/^${Q_TYPE//\\n}//; s/^${A_TYPE//\\n}//;"
  1448. }
  1449.  
  1450. #grep the last line of a history file that contains a REGEX '\t"Q:'
  1451. #little slow with big tsv files
  1452. function grep_usr_lastlinef
  1453. {
  1454.     unescapef "$(grep -F -e $'\t"'${Q_TYPE//$SPC1} "$FILECHAT" | hist_lastlinef)"
  1455. }
  1456.  
  1457. #print to history file
  1458. #usage: push_tohistf [string] [tokens] [time]
  1459. function push_tohistf
  1460. {
  1461.     typeset string token time
  1462.     string=$1; ((${#string})) || ((OPTCMPL)) || return; CKSUM_OLD=;
  1463.     token=$2; ((token>0)) || {
  1464.         start_tiktokenf;    ((OPTTIK)) && _printbf '(tiktoken)';
  1465.         token=$(__tiktokenf "${string}");
  1466.         ((token+=TKN_ADJ)); ((OPTTIK)) && _printbf '          '; };
  1467.     time=${3:-$(datef)}
  1468.     printf '%s%.22s\t%d\t"%s"\n' "$INT_RES" "$time" "$token" "${string:-${Q_TYPE##$SPC1}}" >> "$FILECHAT"
  1469. }
  1470.  
  1471. function datef
  1472. {
  1473.     date -Iseconds 2>/dev/null || date +"%Y-%m-%dT%H:%M:%S%z";
  1474. }
  1475.  
  1476. #record preview query input and response to hist file
  1477. #usage: prev_tohistf [input]
  1478. function prev_tohistf
  1479. {
  1480.     typeset input answer
  1481.     input="$*"
  1482.     ((BREAK_SET)) && { _break_sessionf; BREAK_SET= ;}
  1483.     if ((STREAM))
  1484.     then    answer=$(escapef "$(prompt_pf -r -j "$FILE")")
  1485.     else    answer=$(prompt_pf "$FILE")
  1486.         ((${#answer}>1)) && answer=${answer:1:${#answer}-2}  #del lead and trail ""
  1487.     fi
  1488.     push_tohistf "$input" '' '#1970-01-01'  #(dummy dates)
  1489.     push_tohistf "$answer" '' '#1970-01-01'  #(as comments)
  1490. }
  1491.  
  1492. #calculate token preview
  1493. #usage: token_prevf [string]
  1494. function token_prevf
  1495. {
  1496.     ((OPTTIK)) && _printbf '(tiktoken)'
  1497.     start_tiktokenf
  1498.     TKN_PREV=$(__tiktokenf "${*}")
  1499.     ((TKN_PREV+=TKN_ADJ))
  1500.     ((OPTTIK)) && _printbf '          '
  1501. }
  1502.  
  1503. #send to tiktoken coproc
  1504. function send_tiktokenf
  1505. {
  1506.     kill -0 $COPROC_PID 2>/dev/null || return
  1507.     printf '%s\n' "${1//$NL/\\n}" >&"${COPROC[1]}"
  1508. }
  1509.  
  1510. #get from tiktoken coproc
  1511. function get_tiktokenf
  1512. {
  1513.     typeset REPLY m
  1514.     kill -0 $COPROC_PID 2>/dev/null || return
  1515.     while IFS= read -r
  1516.         ((!${#REPLY}))
  1517.     do  ((++m)); ((m>128)) && break
  1518.         ((m%32)) || sleep 0.1
  1519.     done <&"${COPROC[0]}"
  1520.     if ((!${#REPLY}))
  1521.     then    ! _warmsgf 'Err:' 'get_tiktokenf()'
  1522.     else    printf '%s\n' "$REPLY"
  1523.     fi
  1524. }
  1525.  
  1526. #start tiktoken coproc
  1527. function start_tiktokenf
  1528. {
  1529.     if ((OPTTIK)) && ! kill -0 $COPROC_PID 2>/dev/null
  1530.     then    coproc { trap '' INT; PYTHONUNBUFFERED=1 HOPTTIK=1 tiktokenf ;}
  1531.         PIDS+=($COPROC_PID)
  1532.     fi
  1533. }
  1534.  
  1535. #defaults tiktoken fun
  1536. function __tiktokenf
  1537. {
  1538.     if ((OPTTIK)) && kill -0 $COPROC_PID 2>/dev/null
  1539.     then    send_tiktokenf "${*}" && get_tiktokenf
  1540.     else    false
  1541.     fi; ((!$?)) || _tiktokenf "$@"
  1542. }
  1543.  
  1544. #poor man's tiktoken
  1545. #usage: _tiktokenf [string] [divide_by]
  1546. # divide_by  ^:less tokens  v:more tokens
  1547. function _tiktokenf
  1548. {
  1549.     typeset str tkn var by wc
  1550.     var="$1" by="$2"
  1551.  
  1552.     # 1 TOKEN ~= 4 CHARS IN ENGLISH
  1553.     #str="${1// }" str="${str//[$'\t\n']/xxxx}" str="${str//\\[ntrvf]/xxxx}" tkn=$((${#str}/${by:-4}))
  1554.    
  1555.     # 1 TOKEN ~= ¾ WORDS
  1556.     var=$(sed 's/\\[ntrvf]/ x /g' <<<"$var")  #escaped special chars
  1557.     var=$(sed 's/[^[:alnum:] \t\n]/ x/g' <<<"$var")
  1558.     wc=$(wc -w <<<"$var")
  1559.     tkn=$(( (wc * 4) / ${by:-3}))
  1560.  
  1561.     printf '%d\n' "${tkn:-0}" ;((tkn>0))
  1562. }
  1563.  
  1564. #use openai python tiktoken lib
  1565. #input should be `unescaped'
  1566. #usage: tiktokenf [model|encoding] [text|-]
  1567. function tiktokenf
  1568. {
  1569.     python -c "import sys
  1570.  
  1571. try:
  1572.    import tiktoken
  1573. except ImportError as e:
  1574.    print(\"Err: Python -- \", e)
  1575.    exit()
  1576.  
  1577. opttiktoken, opttik = ${OPTTIKTOKEN:-0}, ${HOPTTIK:-0}
  1578. optv, optl = ${OPTV:-0}, ${OPTL:-0}
  1579. mod, text = sys.argv[1], \"\"
  1580.  
  1581. if opttik <= 0:
  1582.    if opttiktoken+optl > 2:
  1583.        for enc_name in tiktoken.list_encoding_names():
  1584.            print(enc_name)
  1585.        sys.exit()
  1586.    elif (len(sys.argv) > 2) and (sys.argv[2] == \"-\"):
  1587.        text = sys.stdin.read()
  1588.    else:
  1589.        text = sys.argv[2]
  1590.  
  1591. try:
  1592.    enc = tiktoken.encoding_for_model(mod)
  1593. except:
  1594.    try:
  1595.        try:
  1596.            enc = tiktoken.get_encoding(mod)
  1597.        except:
  1598.            enc = tiktoken.get_encoding(\"${MODEL_ENCODING}\")
  1599.    except:
  1600.        enc = tiktoken.get_encoding(\"r50k_base\")  #davinci
  1601.        print(\"Warning: Tiktoken -- unknown model/encoding, fallback \", str(enc), file=sys.stderr)
  1602.  
  1603. if opttik <= 0:
  1604.    encoded_text = enc.encode_ordinary(text)
  1605.    if opttiktoken > 1:
  1606.        print(encoded_text)
  1607.    if optv:
  1608.        print(len(encoded_text))
  1609.    else:
  1610.        print(len(encoded_text),str(enc))
  1611. else:
  1612.    try:
  1613.        while text != \"/END_TIKTOKEN/\":
  1614.            text = sys.stdin.readline().rstrip(\"\\n\")
  1615.            text = text.replace(\"\\\\\\\\\", \"&\\f\\f&\").replace(\"\\\\n\", \"\\n\").replace(\"\\\\t\", \"\\t\").replace(\"\\\\\\\"\", \"\\\"\").replace(\"&\\f\\f&\", \"\\\\\")
  1616.            encoded_text = enc.encode_ordinary(text)
  1617.            print(len(encoded_text), flush=True)
  1618.    except (KeyboardInterrupt, BrokenPipeError, SystemExit):  #BaseException:
  1619.        exit()" "${MOD:-davinci}" "${@:-}"
  1620. }
  1621. #cl100k_base gpt-3.5-turbo
  1622. #json specials \" \\ b f n r t \uHEX
  1623.  
  1624. #convert markdown to plain text
  1625. function unmarkdownf
  1626. {
  1627.     python -c "import sys
  1628. try:
  1629.    import markdown
  1630.    from bs4 import BeautifulSoup
  1631. except:
  1632.    sys.exit(2)
  1633.  
  1634. def remove_markdown(text):
  1635.    html = markdown.markdown(text)
  1636.    soup = BeautifulSoup(html, 'html.parser')
  1637.    return soup.get_text()
  1638.  
  1639. markdown_text = sys.stdin.read()
  1640. stripped_text = remove_markdown(markdown_text)
  1641. print(stripped_text)"
  1642. }
  1643.  
  1644. #set output image size
  1645. function set_imgsizef
  1646. {
  1647.     typeset opts_hd
  1648.     case "$1" in
  1649.         [Hh][Dd] | [Hh][Dd]* | *[Hh][Dd] )
  1650.             OPTS_HD="hd" opts_hd=1;
  1651.             set -- "${1/[Hh][Dd]}";;
  1652.     esac
  1653.     case "$1" in  #width x height, dall-e-3
  1654.         1024*1792 | [Pp] | [Pp][Oo][Rr][Tt][Rr][Aa][Ii][Tt] )  #portrait
  1655.             OPTS=1024x1792;;
  1656.         1792* | [Xx] | [Ll][Aa][Nn][Dd][Ss][Cc][Aa][Pp][Ee] )  #landscape
  1657.             OPTS=1792x1024;;
  1658.         1024* | [Ll] | [Ll][Aa][Rr][Gg][Ee] )  #large
  1659.             OPTS=1024x1024;;
  1660.         512*  |  [Mm]  | [Mm][Ee][Dd][Ii][Uu][Mm] ) OPTS=512x512;;  #medium
  1661.         256*  |  [Ss]  | [Ss][Mm][Aa][Ll][Ll] )     OPTS=256x256;;  #small
  1662.         *)  #fallbacks
  1663.             [[ -z $OPTS ]] || return 1;
  1664.             if [[ $MOD_IMAGE = *dall-e*[3-9] ]] || [[ opts_hd -gt 0 ]]
  1665.             then    OPTS=1024x1024;
  1666.             else    OPTS=512x512;
  1667.             fi; ((opts_hd));;
  1668.     esac;
  1669. }
  1670.  
  1671. # Nill, null, none, inf; Nil, nul; -N---, --- (chat);
  1672. GLOB_NILL='?([Nn])[OoIiUu][NnLl][EeLlFf]' GLOB_NILL2='?([Nn])[IiUu][Ll]' GLOB_NILL3='?([Nn])-*(-)'
  1673. function set_maxtknf
  1674. {
  1675.     typeset buff
  1676.     set -- "${*:-$OPTMAX}"
  1677.     set -- "${*##[+-]}"; set -- "${*%%[+-]}"; set -- "${*// }";
  1678.  
  1679.     case "$*" in
  1680.         $GLOB_NILL|$GLOB_NILL2|$GLOB_NILL3)
  1681.             ((OPTMAX_NILL)) && unset OPTMAX_NILL || OPTMAX_NILL=1;;
  1682.         *[0-9][!0-9][0-9]*)
  1683.             OPTMAX="${*##${*%[!0-9]*}}" MODMAX="${*%%"$OPTMAX"}"
  1684.             OPTMAX="${OPTMAX##[!0-9]}" OPTMAX_NILL= ;;
  1685.         *[0-9]*)
  1686.             OPTMAX="${*//[!0-9]}" OPTMAX_NILL= ;;
  1687.     esac
  1688.     if ((OPTMAX>MODMAX))
  1689.     then    buff="$MODMAX" MODMAX="$OPTMAX" OPTMAX="$buff"
  1690.     fi
  1691. }
  1692.  
  1693. #set the markdown command
  1694. function set_mdcmdf
  1695. {
  1696.     typeset cmd;
  1697.     unset MD_CMD_UNBUFF;
  1698.     ((STREAM)) || unset STREAM;
  1699.     set -- "$(trimf "${*:-$MD_CMD}" "$SPC")";
  1700.  
  1701.     if ! command -v "${1%% *}" &>/dev/null
  1702.     then    for cmd in "bat" "pygmentize" "glow" "mdcat" "mdless"
  1703.         do  command -v $cmd &>/dev/null || continue;
  1704.             set -- $cmd; break;
  1705.         done;
  1706.     fi
  1707.  
  1708.     case "$1" in
  1709.         bat)    MD_CMD_UNBUFF=1  #line-buffer support
  1710.             function mdf {
  1711.               [[ -t 1 || OPTMD -gt 1 ]] && set -- --color always "$@";
  1712.               bat --paging never --language md --style plain "$@" | foldf;
  1713.             }
  1714.             ;;
  1715.         bat*)   eval "function mdf {    $* \"\$@\" ;}"
  1716.             MD_CMD_UNBUFF=1
  1717.             ;;
  1718.         pygmentize)
  1719.             function mdf {  pygmentize ${STREAM:+-s} -l md "$@" | foldf ;}
  1720.             MD_CMD_UNBUFF=1
  1721.             ;;
  1722.         pygmentize*)
  1723.             eval "function mdf {    $* \"\$@\" | foldf ;}"
  1724.             [[ $* = *-s* ]] && MD_CMD_UNBUFF=1
  1725.             ;;
  1726.         glow*|mdcat*|mdless*|*)
  1727.             command -v "${1%% *}" &>/dev/null || return 1;
  1728.             eval "function mdf {    $* \"\$@\" ;}";
  1729.             ;;
  1730.     esac; MD_CMD="${1%% *}";
  1731.     #turn off folding for some software
  1732.     case "$1" in mdless*|less*|bat?*|cat*) OPTFOLD=1; cmd_runf /fold;; esac;
  1733. }
  1734. function mdf {  cat ;}
  1735.  
  1736. #set a terminal web browser
  1737. function set_browsercmdf
  1738. {
  1739.     typeset cmd;
  1740.     if ((${#BROWSER})) && command -v "${BROWSER%% *}" &>/dev/null
  1741.     then    _set_browsercmdf "$BROWSER";
  1742.     else    for cmd in "w3m" "lynx" "elinks" "links" "curl"
  1743.         do  command -v $cmd &>/dev/null || continue;
  1744.             _set_browsercmdf $cmd && return;
  1745.         done; false;
  1746.     fi;
  1747. }
  1748. function _set_browsercmdf
  1749. {
  1750.     case "$1" in
  1751.         w3m*)   printf '%s' "w3m -T text/html";;
  1752.         lynx*)  printf '%s' "lynx -force_html -nolist";;
  1753.         elinks*) printf '%s' "elinks -force-html -no-references";;
  1754.         links*) printf '%s' "links -force-html";;
  1755.         google-chrome*|chromium*) printf '%s' "${1%% *} --disable-gpu --headless --dump-dom";;
  1756.         *)  printf '%s' "curl -L ${FAIL} --progress-bar";;
  1757.     esac;
  1758. }
  1759.  
  1760. #calculate cost of query (dollars per million tokens)
  1761. #usage: costf [input_tokens] [output_tokens] [input_price] [output_price] [scale]
  1762. function costf
  1763. {
  1764.     bc <<<"scale=${5:-8};
  1765. ( ( (${1:-0} / 1000000) * ${3:-0}) + ( (${2:-0} / 1000000) * ${4:-0}) ) * ${CURRENCY_RATE:-1}"
  1766. }
  1767. function _model_costf
  1768. {
  1769.     case "${MOD_PRICE[*]}" in
  1770.     *[0-9]*[$IFS]*[0-9]*)   echo ${MOD_PRICE[@]:0:2}; return;;
  1771.     esac;
  1772.     typeset model; model=${1};
  1773.     case "${model##ft:}" in
  1774.         claude-3-opus*)     echo 15 75;;
  1775.         claude-3-sonnet*|claude-3-5-sonnet*) echo 3 15;;
  1776.         claude-3-haiku*)    echo 0.25 1.25;;
  1777.         claude-3.5-haiku*)  echo 1 5;;
  1778.         claude-2.1*|claude-2*)  echo 8 24;;
  1779.         claude-instant-1.2*)    echo 0.8 2.4;;
  1780.         open-mistral-nemo*)     echo 0.3 0.3;;
  1781.         mistral-large*)     echo 2 6;;
  1782.         codestral*|mistral-small*) echo 0.2 0.6;;
  1783.         open-mistral-7b*)   echo 0.25 0.25;;
  1784.         open-mixtral-8x7b*)     echo 0.7 0.7;;
  1785.         open-mixtral-8x22b*|pixtral-large*)     echo 2 6;;
  1786.         pixtral-12b*)   echo 0.15 0.15;;
  1787.         mistral-medium*)    echo 2.75 8.1;;
  1788.         ministral-8b*)  echo 0.1 0.1;;
  1789.         ministral-3b*)  echo 0.04 0.04;;
  1790.         gpt-4o-audio-preview|gpt-4o-audio-preview-2024-10-01) echo 2.5 10;;  #text only
  1791.         o1-mini*|o1-mini-2024-09-12) echo 3 12;;
  1792.         o1*|o1-preview-2024-09-12) echo 15 60;;
  1793.         gpt-4o-mini*)   echo 0.15 0.6;;
  1794.         gpt-4o-2024-05-13|chatgpt-4o*)  echo 5 15;;
  1795.         gpt-4o-2024-08-06|gpt-4o*) echo 2.5 10;;
  1796.         text-embedding-3-small)     echo 0.02 0;;
  1797.         text-embedding-3-large)     echo 0.13 0;;
  1798.         text-embedding-ada-002|mistral-embed*|mistral-moderation*)  echo 0.1 0;;
  1799.         gpt-4)  echo 30 60;;
  1800.         gpt-4-32k)  echo 60 120;;
  1801.         gpt-4-turbo*|gpt-4-*preview)    echo 10 30;;
  1802.         gpt-3.5-turbo-0125)     echo 0.5 1.5;;
  1803.         gpt-3.5-turbo-0613|gpt-3.5-turbo-0301|gpt-3.5-turbo-instruct) echo 1.5 2;;
  1804.         gpt-3.5-turbo-1106)     echo 1 2;;
  1805.         gpt-3.5-turbo-16k-0613) echo 3 4;;
  1806.         davinci-002)    echo 2 2;;
  1807.         babbage-002)    echo 0.4 0.4;;
  1808.         gemini-1.0-pro*)    echo 0.5 1.5;;
  1809.         gemini-1.5-flash*) ((MAX_PREV>128000||TOTAL_OLD>128000)) && echo 0.15 0.6 || echo 0.075 0.3;;
  1810.         gemini-1.5*) ((MAX_PREV>128000||TOTAL_OLD>128000)) && echo 7 21 || echo 3.5 10.5;;
  1811.         # Novita Models
  1812.         meta-llama/llama-3.1-8b-instruct|qwen/qwen-2-7b-instruct)   echo 0.05 0.05;;
  1813.         meta-llama/llama-3.1-70b-instruct)  echo 0.34 0.39;;
  1814.         meta-llama/llama-3.1-405b-instruct)     echo 2.75 2.75;;
  1815.         mistralai/mistral-7b-instruct|microsoft/wizardlm-2-7b|openchat/openchat-7b)     echo 0.06 0.06;;
  1816.         qwen/qwen-2-72b-instruct)   echo 0.34 0.39;;
  1817.         meta-llama/llama-3-8b-instruct)     echo 0.04 0.04;;
  1818.         meta-llama/llama-3-70b-instruct)    echo 0.51 0.74;;
  1819.         google/gemma-2-9b-it)   echo 0.08 0.08;;
  1820.         nousresearch/hermes-2-pro-llama-3-8b)   echo 0.14 0.14;;
  1821.         mistralai/mistral-nemo)     echo 0.15 0.15;;
  1822.         microsoft/wizardlm-2-8x22b)     echo 0.62 0.62;;
  1823.         gryphe/mythomax-l2-13b)     echo 0.09 0.09;;
  1824.         jondurbin/airoboros-l2-70b)     echo 0.50 0.50;;
  1825.         lzlv_70b)   echo 0.58 0.78;;
  1826.         nousresearch/nous-hermes-llama2-13b|teknium/openhermes-2.5-mistral-7b)  echo 0.17 0.17;;
  1827.         sophosympatheia/midnight-rose-70b)  echo 0.80 0.80;;
  1828.         qwen/qwen-2.5-72b-instruct)     echo 0.38 0.40;;
  1829.         sao10k/l3*-70b-euryale-v2.[1-9])    echo 1.48 1.48;;
  1830.         cognitivecomputations/dolphin-mixtral-8x22b)    echo 0.90 0.90;;
  1831.         *)  echo 0 0; false;;
  1832.     esac;
  1833. }
  1834. #prices updated on aug/24
  1835. #https://openai.com/api/pricing/
  1836. #https://cloud.google.com/vertex-ai/generative-ai/pricing
  1837. #https://ai.google.dev/pricing
  1838.  
  1839. #check input and run a chat command  #tags: cmdrunf, runcmdf, run_cmdf
  1840. function cmd_runf
  1841. {
  1842.     typeset opt_append filein fileinq outdir out var wc xskip pid n
  1843.     typeset -a args arr
  1844.     [[ ${1:0:128}${2:0:128} = *([$IFS:])[/!-]* ]] || return;
  1845.     ((${#1}+${#2}<1024)) || return;
  1846.     printf "${NC}" >&2;
  1847.  
  1848.     set -- "${1##*([$IFS:])?([/!])}" "${@:2}";
  1849.     args=("$@"); set -- "$*";
  1850.  
  1851.     case "$*" in
  1852.         $GLOB_NILL|$GLOB_NILL2|$GLOB_NILL3)
  1853.             set_maxtknf nill
  1854.             cmdmsgf 'Max Response' "$OPTMAX${OPTMAX_NILL:+${EPN6:+ - inf.}} tkns"
  1855.             ;;
  1856.         -[0-9]*|[0-9]*|-M*|[Mm]ax*|\
  1857.         -N*|[Mm]odmax*)
  1858.             if [[ $* = -N* ]] || [[ $* = -[Mm]odmax* ]]
  1859.             then  #model capacity
  1860.                 set -- "${*##@([Mm]odmax|-N)*([$IFS])}";
  1861.                 [[ $* = *[!0-9]* ]] && set_maxtknf "$*" || MODMAX="$*"
  1862.             else  #response max
  1863.                 set_maxtknf "${*##?([Mm]ax|-M)*([$IFS])}";
  1864.             fi
  1865.             if ((HERR))
  1866.             then    unset HERR
  1867.                 _sysmsgf 'Context Length:' 'error reset'
  1868.             fi ;cmdmsgf 'Max Response / Capacity' "$OPTMAX${OPTMAX_NILL:+${EPN6:+ - inf.}} / $MODMAX tkns"
  1869.             ;;
  1870.         -a*|presence*|pre*)
  1871.             set -- "${*//[!0-9.]}"
  1872.             OPTA="${*:-$OPTA}"
  1873.             fix_dotf OPTA
  1874.             cmdmsgf 'Presence Penalty' "$OPTA"
  1875.             ;;
  1876.         -A*|frequency*|freq*)
  1877.             set -- "${*//[!0-9.]}"
  1878.             OPTAA="${*:-$OPTAA}"
  1879.             fix_dotf OPTAA
  1880.             cmdmsgf 'Frequency Penalty' "$OPTAA"
  1881.             ;;
  1882.         -b*|best[_-]of*)
  1883.             set -- "${*//[!0-9.]}" ;set -- "${*%%.*}"
  1884.             OPTB="${*:-$OPTB}"
  1885.             cmdmsgf 'Best_Of' "$OPTB"
  1886.             ;;
  1887.         -[cC])
  1888.             ((OPTC)) && {   cmd_runf -cc; return ;}
  1889.             OPTC=1 EPN=0 OPTCMPL= ;
  1890.             cmdmsgf "Endpoint[$EPN]:" "Text Chat Completions$(printf "${NC}") [${ENDPOINTS[EPN]:-$BASE_URL}]";
  1891.             ;;
  1892.         -[cC][cC])
  1893.             ((OPTC>1)) && {     cmd_runf -d; return ;}
  1894.             OPTC=2 EPN=6 OPTCMPL= ;
  1895.             cmdmsgf "Endpoint[$EPN]:" "Chat Completions$(printf "${NC}") [${ENDPOINTS[EPN]:-$BASE_URL}]";
  1896.             ;;
  1897.         -[dD]|-[dD][dD])
  1898.             ((!OPTC)) && {  cmd_runf -c; return ;}
  1899.             OPTC= EPN=0 OPTCMPL=1 ;
  1900.             cmdmsgf "Endpoint[$EPN]:" "Text Completions$(printf "${NC}") [${ENDPOINTS[EPN]:-$BASE_URL}]";
  1901.             ;;
  1902.         break|br|new)
  1903.             break_sessionf;
  1904.             [[ -n ${INSTRUCTION_OLD:-$INSTRUCTION} ]] && {
  1905.               _sysmsgf 'INSTRUCTION:' "${INSTRUCTION_OLD:-$INSTRUCTION}" 2>&1 | foldf >&2
  1906.               ((GOOGLEAI)) && GINSTRUCTION=${INSTRUCTION_OLD:-$INSTRUCTION} INSTRUCTION= ||
  1907.               INSTRUCTION=${INSTRUCTION_OLD:-$INSTRUCTION};
  1908.             }; CKSUM_OLD= MAX_PREV= WCHAT_C= MAIN_LOOP= HIST_LOOP= TOTAL_OLD= xskip=1;
  1909.             ;;
  1910.         currency[_-]rate*|currency*)  #currency rate
  1911.             set -- "${*##@(currency[_-]rate|currency)$SPC}"
  1912.             set -- "${*//[!0-9.,]}"
  1913.             CURRENCY_RATE=${*:-$CURRENCY_RATE}
  1914.             cmdmsgf 'Currency Rate:' "${*:-$CURRENCY_RATE} (vs Dollar)"
  1915.             ;;
  1916.         prices*|price*)
  1917.             set -- "${*##@(prices|price)$SPC}"
  1918.             set -- "${*//[!0-9.,\ ]}"
  1919.             MOD_PRICE=( ${*//,/.} )
  1920.             cmdmsgf 'Price / 1M tkns:' "input: \$ ${MOD_PRICE[0]}  output: \$ ${MOD_PRICE[1]}"
  1921.             ;;
  1922.         block*|blk*)
  1923.             set -- "${*##@(block|blk)$SPC}"
  1924.             _printbf '>';
  1925.             read_mainf -i "${BLOCK_USR}${1:+ }${1}" BLOCK_USR;
  1926.             cmdmsgf $'\nUser Block:' "${BLOCK_USR:-unset}";
  1927.             ;;
  1928.         fold|wrap|no-fold|no-wrap)
  1929.             ((++OPTFOLD)) ;((OPTFOLD%=2))
  1930.             cmdmsgf 'Folding' $(_onoff $OPTFOLD)
  1931.             ;;
  1932.         -g|-G|stream|no-stream)
  1933.             ((++STREAM)) ;((STREAM%=2))
  1934.             ((STREAM)) || unset STREAM;
  1935.             cmdmsgf 'Streaming' $(_onoff ${STREAM:-0})
  1936.             ;;
  1937.         -h|h|help|-\?|\?)
  1938.             var=$(sed -n -e 's/^   //' -e '/^[[:space:]]*-----* /,/^[[:space:]]*E\.g\./p' <<<"$HELP");
  1939.             less -S <<<"${var}"; xskip=1;
  1940.             ;;
  1941.         -h\ *|h\ *|[/!]h*|help?*|-\?*|\?*)
  1942.             set -- "${*##@(-h|h|[/!]h|help|\?)$SPC}";
  1943.             if ((${#1}<2)) ||
  1944.                 ! grep --color=always -i -e "${1%%${NL}*}" <<<"$(cmd_runf -h)" >&2;
  1945.             then    cmd_runf -h; return;
  1946.             fi; xskip=1
  1947.             ;;
  1948.         -H|H|history|hist)
  1949.             _edf "$FILECHAT"
  1950.             CKSUM_OLD= xskip=1
  1951.             ;;
  1952.         -HH|-HHH*|HH|HHH*|request|print)
  1953.             [[ $* = ?(-)HHH* ]] && typeset OPTHH=3
  1954.             Q_TYPE="\\n${Q_TYPE}" A_TYPE="\\n${A_TYPE}" \
  1955.               MOD= OLLAMA= HERR= TKN_PREV= MAX_PREV= set_histf
  1956.             var=$( usr_logf "$(unescapef "$HIST")" )
  1957.             if ((OPTMD))
  1958.             then    set_mdcmdf "$MD_CMD";
  1959.                 mdf <<<"$var" >&2 2>/dev/null;
  1960.             else    printf "\\n---\\n%s\\n---\\n" "$var" >&2;
  1961.             fi
  1962.             ((OPTCLIP)) && ${CLIP_CMD:-false} <<<"$var" && echo 'Clipboard set!' >&2
  1963.             ;;
  1964.         -P|P)   cmd_runf -HH; return;; -PP*|PP*)    cmd_runf -HHH; return;;
  1965.         -j|seed)
  1966.             OPTSEED="${*##@(-j|seed)*([$IFS])}"
  1967.             cmdmsgf 'Seed:' "$OPTSEED"
  1968.             ;;
  1969.         j|jump)
  1970.             cmdmsgf 'Jump:' 'append response primer'
  1971.             JUMP=1 REPLY=
  1972.             return 179
  1973.             ;;
  1974.         [/!]j|[/!]jump|J|Jump)
  1975.             cmdmsgf 'Jump:' 'no response primer'
  1976.             JUMP=2 REPLY=
  1977.             return 180
  1978.             ;;
  1979.         -K*|top[Kk]*|top[_-][Kk]*)
  1980.             set -- "${*//[!0-9.]}"
  1981.             OPTKK="${*:-$OPTKK}"
  1982.             cmdmsgf 'Top_K' "$OPTKK"
  1983.             ;;
  1984.         keep-alive*|ka*)
  1985.             set -- "${*//[!0-9.]}"
  1986.             OPT_KEEPALIVE="${*:-$OPT_KEEPALIVE}"
  1987.             cmdmsgf 'Keep_alive' "$OPT_KEEPALIVE"
  1988.             ;;
  1989.         -L*|log*)
  1990.             ((OPTLOG)) && [[ -n $* ]] && OPTLOG= ;
  1991.             ((++OPTLOG)); ((OPTLOG%=2));
  1992.             cmdmsgf 'Logging' $(_onoff $OPTLOG);
  1993.             ((OPTLOG)) && {
  1994.               set -- "${*##@(-L|log)$SPC}"
  1995.               if [[ -d "$*" ]]
  1996.               then  USRLOG="${*%%/}/${USRLOG##*/}"
  1997.               else  USRLOG="${*:-${USRLOG}}"
  1998.               fi
  1999.               [[ "$USRLOG" = '~'* ]] && USRLOG="${HOME}${USRLOG##\~}"
  2000.               _cmdmsgf 'Log file' "<${USRLOG/"$HOME"/"~"}>";
  2001.             };
  2002.             ;;
  2003.         -l*|models)
  2004.             set -- "${*##@(-l|models)*([$IFS])}";
  2005.             list_modelsf "$*" | less >&2;
  2006.             ;;
  2007.         -m*|model*|mod*)
  2008.             set -- "${*##@(-m|model|mod)}"; set -- "${1//[$IFS]}"
  2009.             if ((${#1}<3))
  2010.             then    pick_modelf "$1"
  2011.             else    MOD=${1:-$MOD};
  2012.             fi; MULTIMODAL=;
  2013.             case "${MOD}" in o[1-9]*)   :;; *)  ((MOD_REASON));; esac && set_optsf
  2014.             set_model_epnf "$MOD"; model_capf "$MOD";
  2015.             is_amodelf "$MOD" && MULTIMODAL=2;
  2016.             is_visionf "$MOD" && MULTIMODAL=1;
  2017.             send_tiktokenf '/END_TIKTOKEN/'
  2018.             cmdmsgf 'Model Name' "$MOD$( ((MULTIMODAL)) && printf ' / %s' 'multimodal')"
  2019.             cmdmsgf 'Max Response / Capacity:' "$OPTMAX${OPTMAX_NILL:+${EPN6:+ - inf.}} / $MODMAX tkns"
  2020.             ;;
  2021.         markdown*|md*)
  2022.             ((OPTMD)) || (OPTMD=1 cmd_runf //"${args[@]}");
  2023.             set -- "${*##@(markdown|md)$SPC}"
  2024.             ((OPTMD)) && [[ -n $1 ]] && OPTMD= ;
  2025.             if ((++OPTMD)); ((OPTMD%=2))
  2026.             then    MD_CMD=${1:-$MD_CMD} xskip=1;
  2027.                 set_mdcmdf "$MD_CMD";
  2028.                 sysmsgf 'MD Cmd:' "$MD_CMD"
  2029.             fi;
  2030.             cmdmsgf 'Markdown' $(_onoff $OPTMD);
  2031.             ((OPTMD)) || unset OPTMD; NO_OPTMD_AUTO=1;
  2032.             ;;
  2033.         [/!]markdown*|[/!]md*)
  2034.             set -- "${*##[/!]@(markdown|md)$SPC}"
  2035.             set_mdcmdf "${1:-$MD_CMD}"; xskip=1;
  2036.             printf "${NC}\\n" >&2;
  2037.             ((BREAK_SET)) ||
  2038.             prompt_pf -r ${STREAM:+-j --unbuffered} "$FILE" 2>/dev/null | mdf >&2 2>/dev/null;
  2039.             printf "${NC}\\n\\n" >&2;
  2040.             ;;
  2041.         url*|[/!]url*)
  2042.             xskip=1;
  2043.             [[ $* = ?([/!])url:* ]] && opt_append=1;  #append as user
  2044.             set -- "$(trimf "$(trim_leadf "$*" '@(url|[/!]url)*(:)')" "$SPC")";
  2045.  
  2046.             if case "$*" in *youtube.com/watch*|*youtu.be/*)    :;; *)  ! :;; esac
  2047.             then
  2048.                 { yt_descf "$*" || _warmsgf 'YouTube:' 'Description unavailable';
  2049.                   yt_transf "$*" || _warmsgf 'YouTube:' 'Transcript dump fail / unavailable';
  2050.                 } >"$FILEFIFO";
  2051.                 [[ -s "$FILEFIFO" ]] && cmd_runf /cat "$FILEFIFO";
  2052.             elif var=$(set_browsercmdf)
  2053.             then    ((OPTV)) || _printbf "${var%% *}";
  2054.                 case "$var" in
  2055.                 curl*|google-chrome*|chromium*)
  2056.                     cmd_runf /sh${opt_append:+:} "${var} ${1// /%20} | sed '/</{ :loop ;s/<[^<]*>//g ;/</{ N ;b loop } }'";  #curl+sed
  2057.                     ;;
  2058.                 *)  cmd_runf /sh${opt_append:+:} "${var} -dump" "${1// /%20}"
  2059.                     ;;
  2060.                 esac; return;
  2061.             fi
  2062.             ;;
  2063.         media*|img*|audio*|aud*)
  2064.             set -- "${*##@(media|img|audio|aud)*([$IFS])}";
  2065.             set -- "$(trim_trailf "$*" $'*([ \t\n])')";
  2066.             CMD_CHAT=1 media_pathf "$1" && {
  2067.               [[ -f $1 ]] && set -- "$(duf "$1")";
  2068.               var=$((MEDIA_IND_LAST+${#MEDIA_IND[@]}+${#MEDIA_CMD_IND[@]}))
  2069.               out=$(is_audiof "$1" && echo aud || echo img)
  2070.               _sysmsgf "$out ?$var" "${1:0: COLUMNS-6-${#var}}$([[ -n ${1: COLUMNS-6-${#var}} ]] && printf '\b\b\b%s' ...)";
  2071.             };
  2072.             ;;
  2073.         multimodal|[/!-]multimodal|--multimodal|\
  2074.         vision|[/!-]vision|--vision|\
  2075.         audio|[/!-]audio|--audio)
  2076.             ((++MULTIMODAL)); ((MULTIMODAL%=3))
  2077.             case "${MULTIMODAL}" in 2)  var=audio;; 1)  var=vision;; *)     var=text;; esac;
  2078.             cmdmsgf "Multimodal Model [${var}]" $(_onoff $MULTIMODAL)
  2079.             ;;
  2080.         -n*|results*)
  2081.             [[ $* = -n*[!0-9\ ]* ]] && {    cmd_runf "-N${*##-n}"; return ;}  #compat with -Nill option
  2082.             set -- "${*//[!0-9.]}" ;set -- "${*%%.*}"
  2083.             OPTN="${*:-$OPTN}"
  2084.             cmdmsgf 'Results' "$OPTN"
  2085.             ;;
  2086.         -p*|top[Pp]*|top[_-][Pp]*)
  2087.             set -- "${*//[!0-9.]}"
  2088.             OPTP="${*:-$OPTP}"
  2089.             fix_dotf OPTP
  2090.             cmdmsgf 'Top_P' "$OPTP"
  2091.             ;;
  2092.         -r*|restart*)
  2093.             set -- "${*##@(-r|restart)?( )}"
  2094.             restart_compf "$*"
  2095.             cmdmsgf 'Restart Sequence' "\"${RESTART-unset}\""
  2096.             ;;
  2097.         -R*|start*)
  2098.             set -- "${*##@(-R|start)?( )}"
  2099.             start_compf "$*"
  2100.             cmdmsgf 'Start Sequence' "\"${START-unset}\""
  2101.             ;;
  2102.         -s*|stop*)
  2103.             set -- "${*##@(-s|stop)?( )}"
  2104.             ((${#1})) && STOPS=("$(unescapef "${*}")" "${STOPS[@]}")
  2105.             cmdmsgf 'Stop Sequences' "[$(unset s v; for s in "${STOPS[@]}"; do v=${v}\"$(escapef "$s")\",; done; printf '%s' "${v%%,}")]"
  2106.             ;;
  2107.         -?(S)*([$' \t'])[.,]*)
  2108.             set -- "${*##-?(S)*([$' \t.,'])}"; PSKIP= SKIP=1 EDIT=1
  2109.             var=$(INSTRUCTION=$* OPTRESUME=1 CMD_CHAT=1; custom_prf "$@" && echo "$INSTRUCTION")
  2110.             case $? in [1-9]*|201|[!0]*)    REPLY=${args[*]};;  *) REPLY="-S $var";; esac
  2111.             ;;
  2112.         -?(S)*([$' \t'])[/%]*)
  2113.             set -- "${*##-?(S)*([$' \t'])}"; PSKIP= SKIP=1 EDIT=1
  2114.             var=$(INSTRUCTION=$* CMD_CHAT=1; awesomef && echo "$INSTRUCTION") && REPLY="-S $var"
  2115.             ;;
  2116.         -S*|-:*)
  2117.             set -- "${*##-[S:]*([$': \t'])}"
  2118.             SKIP=1 PSKIP=1 REPLY="::${*}"
  2119.             unset INSTRUCTION GINSTRUCTION
  2120.             ;;
  2121.         -t*|temperature*|temp*)
  2122.             set -- "${*//[!0-9.]}"
  2123.             OPTT="${*:-$OPTT}"
  2124.             fix_dotf OPTT
  2125.             cmdmsgf 'Temperature' "$OPTT"
  2126.             ;;
  2127.         -o|clipboard|clip)
  2128.             ((++OPTCLIP)); ((OPTCLIP%=2))
  2129.             cmdmsgf 'Clipboard' $(_onoff $OPTCLIP)
  2130.             if ((OPTCLIP))  #set clipboard
  2131.             then    set_clipcmdf;
  2132.                 set -- "$(hist_lastlinef "$FILECHAT")";
  2133.                 [[ $* = *[!$IFS]* ]] &&
  2134.                   unescapef "$*" | ${CLIP_CMD:-false} &&
  2135.                   printf "${NC}Clipboard Set -- %.*s..${CYAN}\\n" $((COLUMNS-20>20?COLUMNS-20:20)) "$*" >&2;
  2136.             fi
  2137.             ;;
  2138.         -q|insert)
  2139.             ((++OPTSUFFIX)) ;((OPTSUFFIX%=2))
  2140.             cmdmsgf 'Insert Mode' $(_onoff $OPTSUFFIX)
  2141.             ;;
  2142.         -v|-vv|-vv*|verbose)
  2143.             set --  ${*//[!v]}; set -- ${*//v/ v};
  2144.             for var
  2145.             do  ((++OPTV)); ((OPTV%=3));
  2146.             done;
  2147.             case "${OPTV:-0}" in
  2148.                 1) var='Less';;  2) var='Much less';;
  2149.                 0) var='ON'; unset OPTV;;
  2150.             esac ;_cmdmsgf 'Verbose' "$var"
  2151.             ;;
  2152.         -V|-VV|debug)  #debug
  2153.             ((++OPTVV)) ;((OPTVV%=2));
  2154.             cmdmsgf 'Debug Request' $(_onoff $OPTVV)
  2155.             ;;
  2156.         -xx|[/!]editor|[/!]ed|[/!]vim|[/!]vi)
  2157.             ((!OPTX)) && cmdmsgf 'Text Editor' 'one-shot'
  2158.             ((OPTX)) || OPTX=2; REPLY= xskip=1
  2159.             ;;
  2160.         -x|editor|ed|vim|vi)
  2161.             ((++OPTX)) ;((OPTX%=2)); REPLY= xskip=1
  2162.             ;;
  2163.         -y|-Y|tiktoken|tik|no-tik)
  2164.             send_tiktokenf '/END_TIKTOKEN/'
  2165.             ((++OPTTIK)) ;((OPTTIK%=2))
  2166.             cmdmsgf 'Tiktoken' $(_onoff $OPTTIK)
  2167.             ;;
  2168.         -[Ww]*|[Ww]*|rec*|whisper*)
  2169.             set -- "${*##@(-[wW][wW]|-[wW]|[wW][wW]|[wW]|rec|whisper)$SPC}";
  2170.             ((OPTW+OPTWW)) && [[ -n $* ]] && OPTW= OPTWW=; OPTX=;
  2171.             case "${args[*]}" in
  2172.                 -W*|W*) OPTWW=1; OPTW= ;;
  2173.                 *)      OPTW=1; OPTWW= ;;
  2174.             esac;
  2175.  
  2176.             ((OPTW%=2)); ((OPTWW%=2));
  2177.             case "${args[*]}" in    -[Ww][Ww]*|[Ww][Ww]*) OPTW=2;; esac;
  2178.            
  2179.             if ((OPTW+OPTWW))
  2180.             then
  2181.               set_reccmdf
  2182.  
  2183.               var="${*##$SPC}"
  2184.               [[ $var = [a-z][a-z][$IFS]*[[:graph:]]* ]] \
  2185.               && set -- "${var:0:2}" "${var:3}"
  2186.               for var
  2187.               do    ((${#var})) || shift; break;
  2188.               done
  2189.  
  2190.               [[ -z $* ]] || WARGS=("$@"); xskip=1;
  2191.               cmdmsgf "Whisper Args #${#WARGS[@]}" "${WARGS[*]:-(auto)}"
  2192.             fi; cmdmsgf 'Whisper Chat' $(_onoff $((OPTW+OPTWW)) );
  2193.             ((OPTW)) || unset OPTW WSKIP SKIP;
  2194.             ;;
  2195.         -z*|tts*|speech*)
  2196.             set -- "${*##@(-z*([zZ])|tts|speech)$SPC}"
  2197.             ((OPTZ)) && [[ -n $* ]] && OPTZ= ;
  2198.             if ((++OPTZ)); ((OPTZ%=2))
  2199.             then    set_playcmdf;
  2200.                 [[ -z $* ]] || ZARGS=("$@"); xskip=1;
  2201.                 cmdmsgf 'TTS Args' "${ZARGS[*]:-unset}";
  2202.             fi; cmdmsgf 'TTS Chat' $(_onoff $OPTZ);
  2203.             ((OPTZ)) || unset OPTZ SKIP;
  2204.             ;;
  2205.         -Z|last)
  2206.             lastjsonf >&2
  2207.             ;;
  2208.         -ZZ)    OPTZZ=2 lastjsonf >&2
  2209.             ;;
  2210.         -ZZZ*)  OPTZZ=3 lastjsonf >&2
  2211.             ;;
  2212.         [/!]k*|k*)  #kill num hist entries
  2213.             typeset IFS dry; IFS=$'\n'; ((PREVIEW)) && BCYAN="${Color9}" PREVIEW= ;
  2214.             [[ ${n:=${*//[!0-9]}} = 0* || $* = [/!]* ]] \
  2215.             && n=${n##*([/!0])} dry=4; ((n>0)) || n=1
  2216.             if arr=( $(
  2217.                 grep -n -e '^[[:space:]]*[^#]' "$FILECHAT" \
  2218.                 | tail -n $n | cut -c 1-160 | sed -e 's/[[:space:]]/ /g') )
  2219.             then
  2220.                 ((n<${#arr[@]})) || n=${#arr[@]}
  2221.                 wc=$((COLUMNS>50 ? COLUMNS-6+dry : 60))
  2222.                 printf "kill${dry:+\\b\\b\\b\\b}:%.${wc}s\\n" "${arr[@]}" >&2
  2223.                 if ((!dry))
  2224.                 then
  2225.                     set --
  2226.                     for ((n=n;n>0;n--))
  2227.                     do  set -- -e "${arr[${#arr[@]}-n]%%:*} s/^/#/" "$@"
  2228.                     done
  2229.                     sed -i "$@" "$FILECHAT";
  2230.                 fi
  2231.             fi
  2232.             ;;
  2233.         i|info)
  2234.             (  unset blku hurl hurlv modmodal rseq sseq stop
  2235.             ((OLLAMA)) && hurl='ollama-url' hurlv=${OLLAMA_BASE_URL}${ENDPOINTS[EPN]};
  2236.             ((GOOGLEAI)) && hurl='google-url' hurlv=${GOOGLE_BASE_URL}${ENDPOINTS[EPN]};
  2237.             ((ANTHROPICAI)) && hurl='anthropic-url' hurlv=${ANTHROPIC_BASE_URL}${ENDPOINTS[EPN]};
  2238.  
  2239.             set_optsf 2>/dev/null
  2240.             stop=${OPTSTOP#*:} stop=${stop%%,} stop=${stop:-\"unset\"}
  2241.             { is_visionf "$MOD" || is_amodelf "$MOD" ;} && modmodal=' / multimodal'
  2242.             ((MTURN+OPTRESUME)) &&
  2243.             OPTC= OLLAMA= GOOGLEAI= OPTHH=1 EPN=0 set_histf >/dev/null;
  2244.  
  2245.             ((${#BLOCK_USR})) && blku="user-json ${BLOCK_USR:+set}";
  2246.  
  2247.             if ((EPN==6))
  2248.             then    rseq='unavailable'
  2249.                 sseq='unavailable'
  2250.             elif ((OPTC))
  2251.             then    rseq=\"${RESTART-$Q_TYPE}\"
  2252.                 sseq=\"${START-$A_TYPE}\"
  2253.             else    rseq=\"${RESTART-unset}\"
  2254.                 sseq=\"${START-unset}\"
  2255.             fi
  2256.  
  2257.             printf "${NC}${BWHITE}%-13s:${NC} %-5s\\n" \
  2258.             $hurl          $hurlv \
  2259.             api-path      "${BASE_URL}${ENDPOINTS[EPN]}" \
  2260.             model-name    "${MOD:-?}${modmodal}" \
  2261.             model-cap     "${MODMAX:-?}" \
  2262.             response-max  "${OPTMAX:-?}${OPTMAX_NILL:+${EPN6:+ - inf.}}" \
  2263.             context-prev  "${MAX_PREV:-${TKN_PREV:-?}}  (${HIST_LOOP:-0} turns)" \
  2264.             token-rate    "${TKN_RATE[2]:-?} tkns/sec  (${TKN_RATE[0]:-?} tkns, ${TKN_RATE[1]:-?} secs)" \
  2265.             session-cost  "${SESSION_COST:-0} \$" \
  2266.             turn-cost-max "$(costf ${MAX_PREV:-0} ${OPTMAX:-0} $(_model_costf "$MOD") 6 ) \$" \
  2267.             seed          "${OPTSEED:-unset}" \
  2268.             tiktoken      "${OPTTIK:-0}" \
  2269.             keep-alive    "${OPT_KEEPALIVE:-unset}" \
  2270.             temperature   "${OPTT:-0}" \
  2271.             pres-penalty  "${OPTA:-unset}" \
  2272.             freq-penalty  "${OPTAA:-unset}" \
  2273.             top-k         "${OPTKK:-unset}" \
  2274.             top-p         "${OPTP:-unset}" \
  2275.             results       "${OPTN:-1}" \
  2276.             best-of       "${OPTB:-unset}" \
  2277.             logprobs      "${OPTBB:-unset}" \
  2278.             insert-mode   "${OPTSUFFIX:-unset}" \
  2279.             streaming     "${STREAM:-unset}" \
  2280.             clipboard     "${OPTCLIP:-unset}" \
  2281.             ctrld-prpter  "${OPTCTRD:-unset}" \
  2282.             cat-prompter  "${CATPR:-unset}" \
  2283.             restart-seq   "${rseq}" \
  2284.             start-seq     "${sseq}" \
  2285.             stop-seqs     "${stop}" \
  2286.             $blku \
  2287.             history-file  "${FILECHAT/"$HOME"/"~"}"  >&2;
  2288.            
  2289.             ((!NOVITAAI)) || {  _printbf 'wait';  #credit_balance
  2290.             curl -L -s "https://api.novita.ai/v3/user" --header "Authorization: Bearer ${NOVITA_API_KEY}"; echo >&2 ;}  )
  2291.             ;;
  2292.         -u|multi|multiline|-uu*(u)|[/!]multi|[/!]multiline)
  2293.             case "$*" in
  2294.                 -uu*|[/!]multi|[/!]multiline)
  2295.                     ((OPTCTRD)) || OPTCTRD=2;
  2296.                     ((OPTCTRD==2)) && cmdmsgf 'Prompter <Ctrl-D>' 'one-shot';;
  2297.                 *)  ((OPTCTRD)) && unset OPTCTRD || OPTCTRD=1
  2298.                     cmdmsgf 'Prompter <Ctrl-D>' $(_onoff $OPTCTRD)
  2299.                     ((OPTCTRD)) && _warmsgf '*' '<Ctrl-V Ctrl-J> for newline * ';;
  2300.             esac
  2301.             ;;
  2302.         -U|-UU*(U))
  2303.             case "$*" in
  2304.                 -UU*)   ((CATPR)) || CATPR=2;
  2305.                     ((CATPR==2)) && _cmdmsgf 'Cat Prompter' "one-shot";;
  2306.                 *)  ((++CATPR)) ;((CATPR%=2))
  2307.                     cmdmsgf 'Cat Prompter' $(_onoff $CATPR);;
  2308.             esac
  2309.             ;;
  2310.         cat*)
  2311.             set -- "${*}";
  2312.             filein=$(trimf "${1##cat*(:)}" "$SPC") fileinq=$filein;
  2313.             if [[ $filein = *\\* ]]
  2314.             then    filein=${filein//\\};
  2315.             else    fileinq=$(printf '%q' "${filein}");
  2316.             fi  #paths with spaces must be backslash-escaped
  2317.  
  2318.             if is_pdff "$filein"
  2319.             then    cmd_runf /pdf"${*##cat}";
  2320.             elif is_docf "$filein"
  2321.             then    cmd_runf /doc"${*##cat}";
  2322.             elif _is_linkf "$filein"
  2323.             then    cmd_runf /url"${*##cat}";
  2324.             else
  2325.                 case "$*" in
  2326.                     cat:*[!$IFS]*)
  2327.                         cmd_runf /sh: "cat ${fileinq:-$filein}";;
  2328.                     cat*[!$IFS]*)
  2329.                         cmd_runf /sh "cat ${fileinq:-$filein}";;
  2330.                     *)
  2331.                         _warmsgf '*' 'Press <Ctrl-D> to flush * '
  2332.                         STDERR=/dev/null  cmd_runf /sh cat </dev/tty;;
  2333.                 esac;
  2334.             fi; return;
  2335.             ;;
  2336.         pdf*)
  2337.             set -- "${*}";
  2338.             filein=$(trimf "${1##pdf*(:)}" "$SPC");
  2339.             [[ $filein = *\\* ]] || filein=$(printf '%q' "${filein}");
  2340.            
  2341.             if command -v pdftotext
  2342.             then    var="pdftotext -layout -nopgbrk ${filein} -";
  2343.             elif command -v gs
  2344.             then    var="gs -sDEVICE=txtwrite -o - ${filein}"
  2345.             elif command -v abiword
  2346.             then    var="abiword --to=txt --to-name='$FILEFIFO' ${filein}; cat -- '$FILEFIFO'"
  2347.             elif command -v ebook-convert
  2348.             then    var="ebook-convert ${filein} '$FILEFIFO'; cat -- '$FILEFIFO'"
  2349.             else    set --; RET=1;  #auto-disable
  2350.                 function _is_pdff {     false ;};
  2351.             fi 2>/dev/null >&2
  2352.  
  2353.             case "$*" in
  2354.                 pdf:*[!$IFS]*)
  2355.                     cmd_runf /sh: "$var";;
  2356.                 pdf*[!$IFS]*)
  2357.                     cmd_runf /sh "$var";;
  2358.                 *)  _warmsgf 'Err:' 'Input or PDF-to-Text software missing';;
  2359.             esac; return;
  2360.             ;;
  2361.         doc*)
  2362.             set -- "${*}";
  2363.             filein=$(trimf "${1##doc*(:)}" "$SPC");
  2364.             [[ $filein = *\\* ]] || filein=$(printf '%q' "${filein}");
  2365.             outdir=$(printf '%q' "${OUTDIR}");
  2366.             out=${outdir}/${filein##*/} out=${out%.*}.txt;
  2367.  
  2368.             if command -v libreoffice
  2369.             then    var="libreoffice --headless --convert-to txt --outdir ${outdir} ${filein} && cat -- ${out}";
  2370.             elif command -v abiword
  2371.             then    var="abiword --to=txt --to-name=${out} ${filein}; cat -- ${out}";
  2372.             else    set --; RET=1;  #auto-disable
  2373.                 function _is_docf {     false ;};
  2374.             fi 2>/dev/null >&2
  2375.  
  2376.             case "$*" in
  2377.                 doc:*[!$IFS]*)
  2378.                     cmd_runf /sh: "$var";;
  2379.                 doc*[!$IFS]*)
  2380.                     cmd_runf /sh "$var";;
  2381.                 *)  _warmsgf 'Err:' 'Input or LibreOffice missing';;
  2382.             esac; return;
  2383.             ;;
  2384.         save*|\#*)
  2385.             shell_histf "${*##@(save|\#)*([$IFS])}"; history -a;
  2386.             ((${#1})) && cmdmsgf 'Shell:' 'Prompt added to history!';
  2387.             REPLY= EDIT= SKIP_SH_HIST=;
  2388.             ;;
  2389.         [/!]sh*)
  2390.             set -- "${*##[/!]@(shell|sh)*([:$IFS])}"
  2391.             if [[ -n $1 ]]
  2392.             then    bash -i -c "${1%%;}; exit"
  2393.             else    bash -i
  2394.             fi  </dev/tty  >&2;  #>/dev/tty
  2395.             ;;
  2396.         shell*|sh*)
  2397.             [[ $* = @(shell|sh):* ]] && opt_append=1;  #append as user
  2398.             set -- "${*##@(shell|sh)*([:$IFS])}";
  2399.             [[ -n $* ]] || set --; xskip=1;
  2400.             while :
  2401.             do  trap 'trap "-" INT' INT;  #disable trap for one <CRTL-C>#
  2402.                 var=$(trap "-" INT; bash --norc --noprofile ${@:+-c} "${@}" </dev/tty | tee $STDERR);
  2403.                 RET=$?; ((RET)) && _warmsgf "ret code:" "$RET";
  2404.                 trap "exit" INT;
  2405.  
  2406.                 #return on empty or signal
  2407.                 if ((!${#var} || RET))
  2408.                 then    ((RET)) || RET=1; SKIP=1 EDIT=1;
  2409.                     _warmsgf "cmd dump:" "(empty)"; return 0;
  2410.                 else    REPLY=$var var=;
  2411.                 fi; printf '\n---\n\n' >&2;
  2412.  
  2413.                 _sysmsgf 'Edit buffer?' '[N]o, [y]es, te[x]t editor, [s]hell, or [r]edo ' ''
  2414.                 ((OPTV>2)) && {     printf '%s\n' 'n' >&2; break ;}
  2415.                 case "$(NO_CLR=1 read_charf)" in
  2416.                     [Q])    RET=202; exit 202;;  #exit
  2417.                     [AaqRr])    SKIP=1 EDIT=1 RET=200 REPLY="!${args[*]}";
  2418.                             REPLY_CMD_DUMP= REPLY_CMD_BLOCK= SKIP_SH_HIST= WSKIP= SKIP=;  #E#
  2419.                             break;;  #abort, redo
  2420.                     [EeYy]|$'\e')   SKIP=1 EDIT=1 RET=199; break;; #yes, bash `read`
  2421.                     [VvXx]|$'\t'|' ')   SKIP=1 EDIT=1 RET=198; ((OPTX)) || OPTX=2; break;; #yes, text editor
  2422.                     [NnOo]|[!Ss]|'')    SKIP=1 PSKIP=1; break;;  #no need to edit
  2423.                 esac; set --;
  2424.             done; _clr_lineupf $((12+1+47));  #!#
  2425.  
  2426.             ((opt_append)) && [[ $REPLY != [/!]* ]] && REPLY=:$REPLY;
  2427.             ((${#args[@]})) && shell_histf "!${args[*]}";
  2428.             ((RET==200)) || REPLY_CMD_BLOCK=1 SKIP_SH_HIST=1;
  2429.             ;;
  2430.         [/!]session*|session*|list*|copy*|cp\ *|fork*|sub*|grep*|[/!][Ss]*|[Ss]*|[/!][cf]\ *|[cf]\ *|ls*|.)
  2431.             echo Session and History >&2; [[ $* = . ]] && args=('fork current');
  2432.             session_mainf /"${args[@]}"
  2433.             ;;
  2434.         photo*)
  2435.             set -- "$(trim_leadf "$*" "photo$SPC")";
  2436.             if [[ -n $TERMUX_VERSION ]]
  2437.             then
  2438.                 case "${1:0:2}" in [0-9]|[0-9][!0-9])   typeset INDEX=${1:0:1}; set -- "${1:1}";; esac;
  2439.  
  2440.                 while var=${OUTDIR%/}/camera_photo${n:+_}${n}.jpg
  2441.                     [[ -e "$var" ]]
  2442.                 do  ((++n));
  2443.                 done
  2444.  
  2445.                 if termux-camera-photo ${INDEX:+-c ${INDEX:-0}} "$var"
  2446.                 then
  2447.                     # Resize and rotate image if necessary
  2448.                     if command -v "magick" >/dev/null 2>&1;
  2449.                     then
  2450.                       typeset var2 size
  2451.                       #var2=${var%.*}s.${var##*.}  #shrink
  2452.                       size=($(magick identify -format "%w %h" "$var"))
  2453.                       _sysmsgf "Camera Raw:" "$(printf '%dx%d' "${size[@]}")  $(duf "$var")";
  2454.                      
  2455.                       #((size[0]>2048 || size[1]>2048)) && ((size[0]!=size[1])) &&
  2456.                       #magick "$var" -auto-orient -resize '2048x2048>' "$var2" >&2;
  2457.                       #
  2458.                       #[[ -s $var2 ]] && var=$var2 size=($(magick identify -format "%w %h" "$var2"));
  2459.                       #
  2460.                       #((size[0]<size[1] ? size[0]>768 : size[1]>768)) &&
  2461.                       #magick "$var" -auto-orient -resize '768x768^' "$var2" >&2;
  2462.  
  2463.                       #[[ -s $var2 ]] && var=$var2 ||
  2464.                       magick mogrify -auto-orient "$var" >&2;
  2465.                       #https://platform.openai.com/docs/guides/vision/calculating-costs
  2466.                     elif command -v "exiftran" >/dev/null 2>&1;
  2467.                     then
  2468.                       exiftran -ai "$var" >&2;
  2469.                     fi
  2470.                     REPLY="${1}${1:+ }${var}";
  2471.                     _sysmsgf "$(duf "$var")" >&2; :;
  2472.                 else
  2473.                     false;
  2474.                 fi || _warmsgf 'Err:' 'Photo camera';
  2475.             else    cmd_runf /pick "$*"; return;
  2476.             fi;
  2477.             SKIP=1 EDIT=1 xskip=1;
  2478.             ;;
  2479.         [/!]photo*)
  2480.             INDEX=1 cmd_runf "$*"; return;
  2481.             ;;
  2482.         pick*|p*)
  2483.             set -- "$(trim_leadf "$*" "@(pick|p)$SPC")";
  2484.             trap "trap 'exit' INT; echo >&2;" INT;
  2485.  
  2486.             if [[ -d $1 ]]
  2487.             then    var=$(trap '-' INT; _file_pickf "$1");
  2488.                 ((${#var})) && REPLY="$var";
  2489.             else    var=$(trap '-' INT; _file_pickf "$PWD");
  2490.                 ((${#var})) && REPLY="${1}${1:+ }${var}";
  2491.             fi && unset TIPS_DIALOG || _warmsgf 'Err:' 'Filepicker';
  2492.            
  2493.             trap 'exit' INT;
  2494.             SKIP=1 EDIT=1 xskip=1;
  2495.             ;;
  2496.         r|rr|''|[/!]|regen|[/!]regen|[$IFS])  #regenerate last response / retry
  2497.             SKIP=1 EDIT=1 SKIP_SH_HIST=1;
  2498.             case "$*" in
  2499.                 rr|[/!]*) REGEN=2;;  #edit prompt
  2500.                 *)
  2501.                     if test_cmplsf
  2502.                     then  _p_linerf "$REPLY_OLD";
  2503.                     elif ((REGEN==1)) && ((!OPTV))
  2504.                     then  printf '\n%s\n' '--- regenerate ---' >&2;
  2505.                     fi;
  2506.                     REGEN=1 REPLY= ;;
  2507.             esac
  2508.             if ((!BAD_RES)) && [[ -s "$FILECHAT" ]] &&
  2509.             [[ "$(tail -n 2 "$FILECHAT")"$'\n' != *[Bb][Rr][Ee][Aa][Kk]*([$' \t'])$'\n'* ]]
  2510.             then    # comment out two lines from tail
  2511.                 wc=$(wc -l <"$FILECHAT") && ((wc>2)) \
  2512.                 && sed -i -e "$((wc-1)),${wc} s/^/#/" "$FILECHAT";
  2513.                 CKSUM_OLD=;
  2514.             fi
  2515.             ;;
  2516.         replay|rep)
  2517.             if ((${#REPLAY_FILES[@]})) || [[ -f $FILEOUT_TTS ]]
  2518.             then    for var in "${REPLAY_FILES[@]:-$FILEOUT_TTS}"
  2519.                 do  [[ -f $var ]] || continue
  2520.                     du -h "$var" >&2 2>/dev/null;
  2521.                     [[ -n $TERMUX_VERSION ]] && set_termuxpulsef;
  2522.                     ${PLAY_CMD} "$var" >&2 & pid=$! PIDS+=($!);
  2523.                     trap "trap 'exit' INT; kill -- $pid 2>/dev/null;" INT;
  2524.                     wait $pid;
  2525.                 done;
  2526.                 trap 'exit' INT;
  2527.             else    _warmsgf 'Err:' 'No TTS audio file to play'
  2528.             fi
  2529.             ;;
  2530.         res|resub|resubmit)
  2531.             RESUBW=1 SKIP=1 WSKIP=1;
  2532.             ;;
  2533.         dialog|no-dialog)
  2534.             ((++NO_DIALOG)) ;((NO_DIALOG%=2))
  2535.             cmdmsgf 'Dialog' $(_onoff $( ((NO_DIALOG)) && echo 0 || echo 1) )
  2536.             ;;
  2537.         q|quit|exit|bye)
  2538.             send_tiktokenf '/END_TIKTOKEN/' && wait
  2539.             echo '[bye]' >&2; exit 0
  2540.             ;;
  2541.         *)  return 181;  #illegal command
  2542.             ;;
  2543.     esac;
  2544.     ((OPTEXIT>1)) && exit;
  2545.     { ((OPTCMPL)) && typeset Q_TYPE; [[ ${RESTART-$Q_TYPE} != @($'\n'|\\n)* ]] && echo >&2 ;}  #newline
  2546.     if ((OPTX && REGEN<1 && !xskip))
  2547.     then    printf "\\r${BWHITE}${ON_CYAN}%s\\a${NC}" ' * Press Enter to Continue * ' >&2;
  2548.         read_charf >/dev/null;
  2549.     fi;
  2550.         return 0;
  2551. }
  2552.  
  2553. #print msg to stderr
  2554. #usage: _sysmsgf [string_one] [string_two] ['']
  2555. function _sysmsgf
  2556. {
  2557.     printf "${BWHITE}%s${NC}${Color200}${2:+ }%s${NC}${3-\\n}" "$1" "$2" >&2
  2558. }
  2559. function sysmsgf
  2560. {
  2561.     ((OPTV>1)) && return
  2562.     _sysmsgf "$@"
  2563. }
  2564.  
  2565. function _warmsgf
  2566. {
  2567.     BWHITE="${RED}" Color200="${Color200:-${RED}}" \
  2568.     _sysmsgf "$@"
  2569. }
  2570.  
  2571. #command feedback
  2572. function _cmdmsgf
  2573. {
  2574.     BWHITE="${WHITE}" Color200="${CYAN}" \
  2575.     _sysmsgf "$(printf '%-14s' "$1")" "=> ${2:-unset}"
  2576. }
  2577. function cmdmsgf
  2578. {
  2579.     ((OPTV>1)) && return
  2580.     _cmdmsgf "$@"
  2581. }
  2582. function _onoff
  2583. {
  2584.     ((${1:-0})) && echo ON || echo OFF
  2585. }
  2586.  
  2587. #check if buffer file is open and set another one
  2588. function set_filetxtf
  2589. {
  2590.     typeset n;
  2591.     while [[ $(ps a) = *"${VISUAL:-${EDITOR:-vim}}"[\ ]"$FILETXT"* ]] && ((n<32))
  2592.     do  ((++n)); FILETXT=${FILETXT%%[0-9]}${n};
  2593.     done;
  2594. }
  2595.  
  2596. #main plain text editor
  2597. function _edf
  2598. {
  2599.     ${VISUAL:-${EDITOR:-vim}} "$1" </dev/tty >/dev/tty
  2600. }
  2601.  
  2602. #text editor stdout wrapper
  2603. function ed_outf
  2604. {
  2605.     set_filetxtf
  2606.     printf "%s${*:+\\n}" "${*}" > "$FILETXT"
  2607.     _edf "$FILETXT" &&
  2608.     cat -- "$FILETXT"
  2609. }
  2610.  
  2611. #text editor chat wrapper
  2612. function edf
  2613. {
  2614.     typeset ed_msg pre rest pos ind sub inst instruction prev reply
  2615.     ed_msg=",,,,,,(edit below this line),,,,,,"
  2616.     ((OPTC)) && rest="${RESTART-$Q_TYPE}" || rest="${RESTART}"
  2617.     rest="$(_unescapef "$rest")"
  2618.     instruction=${GINSTRUCTION:-$INSTRUCTION};
  2619.  
  2620.     if ((CHAT_ENV)) && ((MTURN+OPTRESUME))  #G#
  2621.     then    MAIN_LOOP=1 Q_TYPE="\\n${Q_TYPE}" A_TYPE="\\n${A_TYPE}" MOD= \
  2622.           OLLAMA= TKN_PREV= MAX_PREV= set_histf "${rest}${*}"
  2623.     fi
  2624.  
  2625.     set_filetxtf
  2626.     pre="${instruction}${instruction:+$'\n\n'}""$(unescapef "$HIST")"
  2627.  
  2628.     if ((${#instruction}==${#pre}-2)) || ((${#INSTRUCTION_OLD}==${#pre}-2)) ||
  2629.        ((CHAT_ENV && MTURN+OPTRESUME && HIST_LOOP==1))  #G#
  2630.     then    inst=1 &&  #instruction editing on
  2631.         ed_msg=",,,,,,(edit ABOVE AND BELOW this line),,,,,,"
  2632.     fi
  2633.  
  2634.     ((OPTCMPL)) || [[ $pre != *[!$IFS]* ]] || pre="${pre}"$'\n\n'"${ed_msg}"
  2635.     if ((OPTE>1))
  2636.     then    printf "%s\\n" "${1:+${NL}${NL}}${*}" >> "$FILETXT"; OPTE= pre= ;  #dont clear buffer
  2637.     else    printf "%s\\n" "${pre}${pre:+${NL}${NL}}${rest}${*}" > "$FILETXT";
  2638.     fi
  2639.  
  2640.     _edf "$FILETXT"
  2641.  
  2642.     while [[ -f $FILETXT ]] && pos="$(<"$FILETXT")"
  2643.        
  2644.         if ((inst)) && [[ "$pos" != "${pre}"* ]]
  2645.         then    inst= ;  #instruction editing
  2646.             prev=$(sed -n "1,/${ed_msg}/ p" <<<"${pos}");
  2647.             instruction=$(sed "/${ed_msg}/ d" <<<"${prev}");
  2648.             ((${#prev}==${#instruction})) || {  #skip?
  2649.  
  2650.             pre=$prev instruction=$(trimf "$instruction" "$SPC");
  2651.             if ((${#instruction}))
  2652.             then    if ((GOOGLEAI))
  2653.                 then    GINSTRUCTION="$instruction" INSTRUCTION=;
  2654.                 else    INSTRUCTION="$instruction";
  2655.                 fi; INSTRUCTION_OLD="$instruction"
  2656.             fi
  2657.             ((HIST_LOOP==1)) && OPTX= cmd_runf /break
  2658.             }
  2659.         fi
  2660.         [[ "$pos" != "${pre}"* ]] || [[ "$pos" = *"${rest:-%#%#}" ]]
  2661.     do  _warmsgf "Bad edit:" "[E]dit, [c]ontinue, [/]cmd, [r]edo or [a]bort? " ''
  2662.         reply=$(read_charf)
  2663.         case "$reply" in
  2664.             [-/!]) read_mainf -i "$reply" reply;
  2665.                 cmd_runf "$reply"; _edf "$FILETXT";;  #cmd
  2666.             [AQ]) echo '[bye]' >&2; return 202;;  #exit
  2667.             [aq]) echo '[abort]' >&2; return 201;;  #abort
  2668.             [CcNn]) break;;      #continue
  2669.             [Rr])  return 200;;  #redo
  2670.             [Ee]|$'\e'|*) _edf "$FILETXT";;  #edit
  2671.         esac
  2672.     done; printf '\n---\n\n' >&2;
  2673.  
  2674.     ind=320 sub="${pos:${#pre}:${ind}}"
  2675.     if ((OPTCMPL))
  2676.     then    ((${#rest})) &&
  2677.         sub="${sub##$SPC"${rest}"}"
  2678.     else    sub="${sub##?($SPC"${rest%%$SPC}")$SPC}"
  2679.     fi
  2680.     pos="${sub}${pos:$((${#pre}+${ind}))}"
  2681.  
  2682.     printf "%s\\n" "$pos" > "$FILETXT"
  2683.  
  2684.     if ((CHAT_ENV)) && cmd_runf "$pos"
  2685.     then    return 200;
  2686.     fi
  2687. }
  2688.  
  2689. #readline edit command line in text editor without executing it
  2690. function _edit_no_execf
  2691. {
  2692.     set_filetxtf;
  2693.     printf '%s\n' "$READLINE_LINE" >"$FILETXT";
  2694.     if _edf "$FILETXT" && [[ -s $FILETXT ]]
  2695.     then    READLINE_LINE=$(<"$FILETXT");
  2696.         READLINE_POINT=${#READLINE_LINE};
  2697.         printf "${BCYAN}" >&2;
  2698.     fi;
  2699. }
  2700. #https://superuser.com/questions/1601543/ctrl-x-e-without-executing-command-immediately
  2701.  
  2702. #(un)escape from/to json (bash truncates input on \000)
  2703. function _escapef
  2704. {
  2705.     sed -e 's/\\/\\\\/g; s/$/\\n/' -e '$s/\\n$//'  \
  2706.         -e 's/\r/\\r/g; s/\t/\\t/g; s/"/\\"/g;'  \
  2707.         -e $'s/\a/\\\\a/g; s/\f/\\\\f/g; s/\b/\\\\b/g;'  \
  2708.         -e $'s/\v/\\\\v/g; s/\e/\\\\u001b/g; s/[\03\04]//g;' <<<"$*" \
  2709.     | tr -d '\n\000'
  2710. }  #fallback
  2711. function _unescapef {   printf -- '%b' "$*" ;}  #fallback
  2712.  
  2713. function unescapef {
  2714.     ((${#1})) || return
  2715.     jq -Rr '"\"" + . + "\"" | fromjson' <<<"$*" || ! _unescapef "$*"
  2716. }
  2717. function escapef {
  2718.     ((${#1})) || return
  2719.     printf '%s' "$*" | jq -Rrs 'tojson[1:-1]' || ! _escapef "$*"
  2720. }
  2721. # json special chars: \" \/ b f n r t \\uHEX
  2722. # characters from U+0000 through U+001F must be escaped
  2723.  
  2724. function _break_sessionf
  2725. {
  2726.     [[ -f "$FILECHAT" ]] || return; typeset tail;
  2727.    
  2728.     tail=$(tail -n 20 -- "$FILECHAT") || return;
  2729.     ((${#tail}>2048)) && tail=${tail:${#tail} -2048};
  2730.    
  2731.     [[ BREAK${tail} = *[Bb][Rr][Ee][Aa][Kk]*([$IFS]) ]] \
  2732.     || printf '%s%s\n' "$1" 'SESSION BREAK' >> "$FILECHAT";
  2733. }
  2734. function break_sessionf
  2735. {
  2736.     BREAK_SET=1; _sysmsgf 'SESSION BREAK';
  2737. }
  2738.  
  2739. #fix: remove session break
  2740. function fix_breakf
  2741. {
  2742.     [[ $(tail -n 1 "$1" 2>/dev/null) = *[Bb][Rr][Ee][Aa][Kk]*([$' \t']) ]] &&
  2743.       sed -i -e '$d' "$1" && _sysmsgf 'Session Break Removed';
  2744. }
  2745.  
  2746. #fix variable value, add zero before/after dot.
  2747. function fix_dotf
  2748. {
  2749.     eval "[[ \$$1 = [0-9.] ]] || return"
  2750.     eval "[[ \$$1 = .[0-9]* ]] && $1=0\$${1}"
  2751.     eval "[[ \$$1 = *[0-9]. ]] && $1=\${${1}}0"
  2752. }
  2753.  
  2754. #minify json
  2755. function json_minif
  2756. {
  2757.     typeset blk;
  2758.     if [[ ${BLOCK:0:32} = @* ]]
  2759.     then    blk=$(jq -c . "${BLOCK##@}") || return
  2760.         printf '%s\n' "$blk" >"${BLOCK##@}"
  2761.     else    blk=$(jq -c . <<<"$BLOCK") || return
  2762.         BLOCK="${blk:-$BLOCK}"
  2763.     fi
  2764. }
  2765.  
  2766. #format for chat completions endpoint
  2767. #usage: fmt_ccf [prompt] [role]
  2768. function fmt_ccf
  2769. {
  2770.     typeset var
  2771.     typeset -l ext
  2772.     [[ ${1} = *[!$IFS]* ]] || ((${#MEDIA[@]}+${#MEDIA_CMD[@]})) || return
  2773.    
  2774.     if ! ((${#MEDIA[@]}+${#MEDIA_CMD[@]}))
  2775.     then
  2776.         ((ANTHROPICAI)) && [[ $2 = system ]] && return 1;
  2777.         printf '{"role": "%s", "content": "%s"}\n' "${2:-user}" "$1";
  2778.     elif ((OLLAMA))
  2779.     then
  2780.         printf '{"role": "%s", "content": "%s",\n' "${2:-user}" "$1";
  2781.         ollama_mediaf && printf '%s' ' }'
  2782.     elif is_visionf "$MOD" || is_amodelf "$MOD"
  2783.     then
  2784.         printf '{ "role": "%s", "content": [ ' "${2:-user}";
  2785.         ((${#1})) &&
  2786.         printf '{ "type": "text", "text": "%s" }' "$1";
  2787.         for var in "${MEDIA[@]}" "${MEDIA_CMD[@]}"
  2788.         do
  2789.             case "$var" in \~\/*)   var="$HOME/${var:2}";; esac;
  2790.             if [[ $var != *[!$IFS]* ]]
  2791.             then    continue;
  2792.             elif [[ -s $var ]] && is_audiof "$var"
  2793.             then    ext=${var##*.};
  2794.                 ((${#ext}<7)) || ext=;
  2795.                 case "$ext" in mp3|opus|aac|flac|wav|pcm16) :;;
  2796.                     *)  _warmsgf 'Warning:' "Filetype may be unsupported -- ${ext:-extension_err}" ;;
  2797.                 esac
  2798.                 ((${#1})) && printf ',';
  2799.                 printf '\n{ "type": "input_audio", "input_audio": { "data": "%s", "format": "%s" } }' "$(base64 "$var" | tr -d $'\n')" "${ext:-mp3}" ;
  2800.             elif [[ -s $var ]]
  2801.             then    ext=${var##*.} ext=${ext/[Jj][Pp][Gg]/jpeg};
  2802.                 ((${#ext}<7)) || ext=;
  2803.                 case "$ext" in jpeg|png|gif|webp) :;;  #20MB per image
  2804.                     *)  _warmsgf 'Warning:' "Filetype may be unsupported -- ${ext:-extension_err}" ;;
  2805.                 esac
  2806.                 ((${#1})) && printf ',';
  2807.                 if ((ANTHROPICAI))
  2808.                 then
  2809.                   printf '\n{ "type": "image", "source": { "type": "base64", "media_type": "image/%s", "data": "%s" } }' "${ext:-jpeg}" "$(base64 "$var" | tr -d $'\n')";
  2810.                 else
  2811.                   printf '\n{ "type": "image_url", "image_url": { "url": "data:image/%s;base64,%s" } }' "${ext:-jpeg}" "$(base64 "$var" | tr -d $'\n')";
  2812.                   #groq: detail  string  Optional  #groq: image URL or base64
  2813.                 fi
  2814.             else  #img url
  2815.                 ((ANTHROPICAI)) || {  #mistral groq
  2816.                   ((${#1})) && printf ',';
  2817.                   printf '\n{ "type": "image_url", "image_url": { "url": "%s" } }' "$var";
  2818.                 };
  2819.             fi
  2820.         done;
  2821.         printf '%s\n' ' ] }';
  2822.     fi
  2823. }
  2824.  
  2825. #remove implicit refs .. and .
  2826. #ex: a/b/../img.gif -> a/img.gif
  2827. function rmimpf
  2828. {
  2829.     sed -E  -e 's|[^/]+/\.\./|| ;s|^\./|| ;s|/\./|/|' \
  2830.         -e 's|[^/]+/\.\./|| ;s|^\./|| ;s|/\./|/|' \
  2831.         -e 's|[^/]+/\.\./|| ;s|^\./|| ;s|/\./|/|' #3x
  2832. }
  2833.  
  2834. # file picker
  2835. function _file_pickf
  2836. {
  2837.     typeset file
  2838.     file=${1:-${PWD:-$HOME}}/
  2839.    
  2840.     __set_fpick;
  2841.  
  2842.     case "$file" in
  2843.         \~\/*)  file="$HOME/${file:2}";
  2844.         ;;
  2845.         [!/]*)  [[ -e $PWD/$file ]] && file="$PWD/$file";
  2846.         ;;
  2847.     esac;
  2848.  
  2849.     while file=$(__fpick "$file")
  2850.         case $? in
  2851.         2)  typeset TIPS_DIALOG;
  2852.             continue;
  2853.             ;;
  2854.         1)  __clr_dialogf;
  2855.             _sysmsgf "No file selected";
  2856.             return 1;
  2857.             ;;
  2858.         -1|5|128|130|255) __clr_dialogf;
  2859.             _warmsgf "An unexpected error has occurred";
  2860.             return 1;
  2861.             ;;
  2862.         esac;
  2863.     do  typeset TIPS_DIALOG;
  2864.         [[ -d ${file:-$PWD} ]] || [[ ! -f $file ]] || break;
  2865.     done;
  2866.     __clr_dialogf;
  2867.  
  2868.     printf '%q' "$file";
  2869. }
  2870. function __set_fpick
  2871. {
  2872.     typeset cmd
  2873.     declare -f "__fpick" >/dev/null 2>&1 && return;
  2874.  
  2875.     ((NO_DIALOG)) || {
  2876.       ((${#DISPLAY})) && set -- "$@" kdialog zenity osascript;
  2877.       ((${#TERMUX_VERSION})) && set -- "$@" termux-dialog  ;}
  2878.     for cmd in "$@" vifm ranger nnn dialog
  2879.     do  command -v "$cmd" >/dev/null 2>&1 && break;
  2880.     done;
  2881.     case "$cmd" in  dialog) DIALOG_CLR=1;; esac;
  2882.  
  2883.     eval "function __fpick { _${cmd}_pickf \"\$@\" ;}"  #_dialog_pickf
  2884. }
  2885.  
  2886. function _dialog_pickf
  2887. {
  2888.     ((${#TIPS_DIALOG})) && dialog --colors --timeout 3 --backtitle "File Picker Tips" --begin 3 2 --msgbox "$TIPS_DIALOG" 14 40  >/dev/tty;
  2889.     dialog --help-button --backtitle "File Picker" --title "File Select" --fselect "$1" 24 80  2>&1 >/dev/tty;
  2890.     case $? in
  2891.         2)  dialog --colors --backtitle "File Picker" --title "File Picker Help" --msgbox "$HELP_DIALOG" 24 56  2>&1 >/dev/tty;
  2892.             return 2;;
  2893.         *)  return;;
  2894.     esac
  2895.     #-1:error, 1:cancel, 2:help, 3:extra, 5:timeout, 255:ESC
  2896. }
  2897. #whiptail warp
  2898. function whiptailf
  2899. {
  2900.     typeset arg args; args=();
  2901.     for arg  #option check
  2902.     do  case "$arg" in
  2903.             [!-]*|-1|--backtitle|-button|--checklist|--clear|\
  2904.             --defaultno|--fb|--gauge|-height|--infobox|\
  2905.             --inputbox|-key|--menu|--msgbox|--nocancel|\
  2906.             --noitem|-options|--passwordbox|--radiolist|\
  2907.             --scrolltext|-string|--title|--topleft|--yesno)
  2908.                 args=("${args[@]}" "${arg//\\Z[[:alnum:]]}");;
  2909.             #*) echo "whiptail: ignore option -- $arg" >&2;;
  2910.         esac;
  2911.     done;
  2912.     whiptail "${args[@]}";
  2913. }
  2914. function test_dialogf
  2915. {
  2916.     ((!NO_DIALOG)) || return; ((OK_DIALOG)) && return;
  2917.     if command -v dialog
  2918.     then    OK_DIALOG=1;
  2919.     elif command -v whiptail
  2920.     then    function dialog {   whiptailf "$@" ;};
  2921.         OK_DIALOG=1;
  2922.     else    false;
  2923.     fi >/dev/null 2>&1;
  2924. }
  2925.  
  2926. function _termux-dialog_pickf
  2927. {
  2928.     printf '%s/%s' "${1%%/}" "$(
  2929.         termux-dialog sheet -v"$(IFS=$'\t\n'; printf '%q,' .. $(_ls_pickf "$1") ..)." | jq -r .text;
  2930.     )" | rmimpf;
  2931. }
  2932. #{ termux-storage-get || am start -a android.intent.action.OPEN_DOCUMENT -d /storage/emulated/0 -t '*/*' ;}
  2933. #https://wiki.termux.com/wiki/Internal_and_external_storage
  2934. function _ls_pickf
  2935. {
  2936.     shopt -s nullglob;
  2937.     cd "$1" || return;
  2938.     printf '%s\n' */;
  2939.     printf '%s\n' * |  __ls_pickf
  2940.     printf '%s\n' .*/;
  2941.     printf '%s\n' .* | __ls_pickf
  2942. }
  2943. function __ls_pickf
  2944. {
  2945.     typeset file
  2946.     while IFS= read -r file; do
  2947.     [[ -d $file ]] || [[ -L $file ]] || printf '%s\n' "$file"; done;
  2948. }
  2949. function _nnn_pickf { nnn -p - "$1" ;}
  2950. function _vifm_pickf { vifm --no-configs -c "set nosave" -c "only" -c "set mouse=a" --choose-files - "$1" ;}
  2951. function _ranger_pickf { : >"$FILEFIFO"; ranger -c --cmd="set mouse_enabled true" --choosefile="$FILEFIFO" "$1" >/dev/tty; cat -- "$FILEFIFO" ;}
  2952. function _kdialog_pickf { kdialog --getopenfilename "$1" 2>/dev/null ;}
  2953. function _zenity_pickf { zenity --file-selection --filename="$1" --title="Select a File" 2>/dev/null ;}
  2954. function _osascript_pickf { osascript -l JavaScript -e 'a=Application.currentApplication();a.includeStandardAdditions=true;a.chooseFile({withPrompt:"Please select a file:"}).toString()' ;}
  2955.  
  2956. TIPS_DIALOG='\n- \ZbNavigation:\ZB Use \ZbTAB\ZB, \ZbSHIFT-TAB\ZB and \ZbARROW KEYS\ZB.\n\n- \ZbSelect:\ZB Press \ZbSPACE-BAR\ZB.\n\n- \ZbMouse:\ZB \ZbCLICK\ZB to select and \ZbSPACE\ZB to complete.'
  2957. HELP_DIALOG='\n- \ZbMove:\ZB Use \ZbTAB\ZB, \ZbSHIFT+TAB\ZB, or \ZbARROW KEYS\ZB.\n\n- \ZbScroll:\ZB Use \ZbUP and DOWN\ZB arrow keys in lists.\n\n- \ZbSelect:\ZB Press \ZbSPACE\ZB to copy to text box.\n\n- \ZbAutocomplete:\ZB Type \ZbSPACE\ZB to complete names.\n\n- \ZbConfirm:\ZB Press \ZbENTER\ZB or click "\ZbOK\ZB".\n\n\n\ZuHappy browsing!\ZU'
  2958.  
  2959. #print entries with 1-9 indexes or '.' for dialog
  2960. function _dialog_optf
  2961. {
  2962.     typeset i
  2963.     for ((i=0;i<${#};i++))
  2964.     do  printf "%q %s " "${@: i+1: 1}" "$( ((i<9)) && echo $((i + 1)) || echo .)";
  2965.     done
  2966. }
  2967.  
  2968. #check for pure text completions conditions, or insert mode with null suffix
  2969. function test_cmplsf
  2970. {
  2971.     ((OPTCMPL && !OPTSUFFIX)) || ((OPTSUFFIX)) ||
  2972.     ((!OPTCMPL && !OPTC && !MTURN && !OPTSUFFIX && EPN==0))
  2973. }
  2974.  
  2975. #set media for ollama *generation endpoint*
  2976. function ollama_mediaf
  2977. {
  2978.     typeset var n
  2979.     set -- "${MEDIA[@]}" "${MEDIA_CMD[@]}";
  2980.     [[ -n $* ]] || return;
  2981.     printf '\n"images": ['
  2982.     for var
  2983.     do  ((++n))
  2984.         if [[ $var != *[!$IFS]* ]]
  2985.         then    continue;
  2986.         elif [[ -f $var ]] && [[ -s $var ]]
  2987.         then    printf '"%s"' "$(base64 "$var" | tr -d $'\n')";
  2988.             ((n < ${#})) && printf '%s' ',';
  2989.         fi
  2990.     done;
  2991.         printf '%s' ']'
  2992. }
  2993.  
  2994. #process files and urls from input
  2995. #filenames with spaces must be blackslash-quoted
  2996. function media_pathf
  2997. {
  2998.     typeset var ind m n
  2999.     #process only the last line of input
  3000.     set -- "$(sed -e 's/\\n/\n/g; s/\\\\ /\\ /g; s/^[[:space:]|]*//; s/[[:space:]|]*$//; /^[[:space:]]*$/d' <<<"$*" | sed -n -e '$ p')";
  3001.    
  3002.     while [[ $1 = *[[:alnum:]]* ]] && ((m<128))
  3003.     do
  3004.         ((++m)); var=;
  3005.         set -- "$(trim_leadf "$1" $'*([ \n\t\r|]|\\\\[ntr])')";
  3006.         if [[ -f $1 ]]
  3007.         then    var=$1;
  3008.         else    [[ $1 = *[\|]* ]]   && var=$(sed 's/^.*[|][[:space:]|]*//' <<<"$1");
  3009.             [[ -f ${var//\\} ]] || var=$(sed 's/^.*[^\\][[:space:]]//; s/^[[:space:]|]*//' <<<"$1");
  3010.         fi; ind=${#var};
  3011.         [[ $var = *\\* ]] && var=${var//\\};
  3012.         case "$var" in \~\/*)   var="$HOME/${var:2}";; esac;
  3013.  
  3014.         #check if file or url and add to array (max 20MB)
  3015.         if is_imagef "$var" || { is_amodelf "$MOD" && is_audiof "$var" ;} ||
  3016.             { ((!GOOGLEAI)) && is_linkf "$var" ;}
  3017.         then
  3018.             ((++n));
  3019.             if ((CMD_CHAT))
  3020.             then    MEDIA_CMD=("${MEDIA_CMD[@]}" "$var");
  3021.                 MEDIA_CMD_IND=("${MEDIA_CMD_IND[@]}" "$var");
  3022.             else    MEDIA=("$var" "${MEDIA[@]}");  #read by fmt_ccf()
  3023.                 MEDIA_IND=("$var" "${MEDIA_IND[@]}");
  3024.             fi;
  3025.            
  3026.             ((${#1}-ind < 0)) && {  _warmsgf 'Err: media_pathf():' "negative index -- $((${#1}-ind))"; break ;}
  3027.             set -- "$(trim_trailf "${1: 0: ${#1}-ind}" $'*(\\[tnr]|[ \t\n\r|])')";
  3028.         else
  3029.             ((OPTV>99)) || [[ $var = *[[\]\<\>{}\(\)*?=%\&^\$\#\ ]* ]] ||
  3030.               [[ $var != *[[:alnum:]].[[:alnum:]]* ]] || [[ ${1:0:128} = "${var:0:128}" ]] || {
  3031.               var="${var:0: COLUMNS-25}$([[ -n ${var: COLUMNS-25} ]] && printf '\b\b\b%s' ...)";
  3032.               _warmsgf 'multimodal: invalid --' "\`\`${var//$'\t'/ }''";
  3033.             }
  3034.  
  3035.             break;
  3036.             #set -- "${1: 0: ${#1}-ind}";
  3037.         fi  #https://stackoverflow.com/questions/12199059/
  3038.     done; ((n));
  3039. }
  3040.  
  3041. function _is_linkf
  3042. {
  3043.     [[ ! -f $1 ]] || return;
  3044.     case "$1" in
  3045.         [Hh][Tt][Tt][Pp][Ss]://* | [Hh][Tt][Tt][Pp]://* | [Ff][Tt][Pp]://* | [Ff][Ii][Ll][Ee]://* | telnet://* | gopher://* | about://* | wais://* ) :;;
  3046.         *?.[Hh][Tt][Mm] | *?.[Hh][Tt][Mm][Ll] | *?.[A-Za-z][Hh][Tt][Mm][Ll] | *?.[Hh][Tt][Mm][Ll]? | *?.[Xx][Mm][Ll] | *?.com | *?.com/ | *?.com.[a-z][a-z] | *?.com.[a-z][a-z]/ ) :;;
  3047.         [Ww][Ww][Ww].?* ) :;;
  3048.         *) false;;
  3049.     esac
  3050. }
  3051. function is_linkf
  3052. {
  3053.     [[ ! -f $1 ]] || return;
  3054.     _is_linkf "$1" || [[ \ $LINK_CACHE\  = *\ "${1:-empty}"\ * ]] || {
  3055.       curl --output /dev/null --max-time 10 --silent --head ${FAIL} --location -H "$UAG" -- "$1" 2>/dev/null &&
  3056.       LINK_CACHE="$LINK_CACHE $1" ;}
  3057. }
  3058.  
  3059. function is_txtfilef
  3060. {
  3061.     [[ -f $1 ]] || return
  3062.     case "$1" in
  3063.             *?.[Tt][Xx][Tt] | *?.[Mm][Dd] | *?.[Cc][Ff][Gg] | *?.[Ii][Nn][Ii] | *?.[Ll][Oo][Gg] | *?.[TtCc][Ss][Vv] | *?.[Jj][Ss][Oo][Nn] | *?.[Xx][Mm][Ll] | *?.[Cc][Oo][Nn][Ff] | *?.[Rr][Cc] | *?.[Yy][Aa][Mm][Ll] | *?.[Yy][Mm][Ll] | *?.[Hh][Tt][Mm][Ll] | *?.[Hh][Tt][Mm] | *?.[Ss][Hh] | *?.[CcZz][Ss][Hh] | *?.[Bb][Aa][Ss][Hh] | *?.[Pp][Yy] | *?.[Jj][Ss] | *?.[Cc][Ss][Ss] | *?.[Jj][Aa][Vv][Aa] | *?.[Rr][Bb] | *?.[Pp][Hh][Pp] | *?.[Tt][Cc][Ll] | *?.[Pp][LlSs] | *?.[Rr][Ss][Tt] | *?.[Tt][Ee][Xx] | *?.[Ss][Qq][Ll] | *?.[Ll][Oo][Gg] | *?.c | *.bashrc | *.bash_profile | *.profile | *.zshrc | *.zshenv ) return;;
  3064.     esac
  3065.     ! _is_imagef "$@" && ! _is_videof "$@" &&
  3066.     ! _is_audiof "$@" && ! (__set_outfmtf "$@") &&
  3067.     [[ "$(file -- "$1")" = *[Tt][Ee][Xx][Tt]* ]]
  3068. }
  3069.  
  3070. function _is_pdff
  3071. {
  3072.     case "$1" in
  3073.         *?.[Pp][Dd][Ff] | *?.[Pp][Dd][Ff]? ) :;;
  3074.         *) false;
  3075.     esac;
  3076. }
  3077. function is_pdff {  [[ -f $1 ]] && _is_pdff "$1" ;}
  3078.  
  3079. function _is_imagef
  3080. {
  3081.     case "$1" in
  3082.         *?.[Pp][Nn][Gg] | *?.[Jj][Pp][Gg] | *?.[Jj][Pp][Ee][Gg] | *?.[Ww][Ee][Bb][Pp] | *?.[Gg][Ii][Ff] | *?.[Hh][Ee][Ii][CcFf] | *?.[Gg][Ii][Ff] ) :;;
  3083.         *) false;;
  3084.     esac;
  3085. }
  3086. function is_imagef {    [[ -f $1 ]] && _is_imagef "$1" ;}
  3087.  
  3088. function _is_videof
  3089. {
  3090.     case "$1" in
  3091.         *?.[Mm][Oo][Vv] | *?.[Mm][Pp][Ee][Gg] | *?.[Mm][Pp][Gg4] | *?.[Aa][Vv][Ii] | *?.[Ww][Mm][Vv] | *?.[Ff][Ll][Vv] ) :;;
  3092.         *) false;;
  3093.     esac;
  3094. }
  3095. function is_videof {    [[ -f $1 ]] && _is_videof "$1" ;}
  3096.  
  3097. function _is_audiof
  3098. {
  3099.     case "$1" in  #mp3..
  3100.         *?.[Mm][Pp][34] | *?.[Mm][Pp][Gg] | *?.[Mm][Pp][Ee][Gg] | *?.[Mm][Pp][Gg][Aa] | *?.[Mm]4[Aa] | *?.[Ww][Aa][Vv] | *?.[Ww][Ee][Bb][Mm] | *?.[Oo][Oo][Gg] | *?.[Ff][Ll][Aa][Cc] ) :;;
  3101.         *?.[Oo][Pp][Uu][Ss] | *?.[Aa][Aa][Cc] | *?.[Pp][Cc][Mm]16 | *?.[Pp][Cc][Mm] ) :;;
  3102.         *) false;;
  3103.     esac
  3104. }
  3105. function is_audiof {    [[ -f $1 ]] && _is_audiof "$1" ;}
  3106.  
  3107. #test whether file is text, pdf file, or url and print out filepath
  3108. function is_txturl
  3109. {
  3110.     ((${#1}>320)) && set -- "${1: ${#1}-320}"
  3111.    
  3112.     set -- "$1" "$(INDEX=64 trimf "$1" "$SPC")";
  3113.     if [[ -f ${2:-$1} ]]
  3114.     then    set -- "${2:-$1}";
  3115.     else    set -- "$(trim_leadf "$(trim_trailf "$1" "$SPC")" $'*[!\\\\][ \t\n]')";
  3116.         [[ ${1:0:1} = [$IFS] ]] && set -- "${1:1}";
  3117.     fi  #C#
  3118.     case "$1" in \~\/*)     set -- "$HOME/${1:2}";; esac;
  3119.  
  3120.     [[ $1 = *\\* ]] && set -- "${1//\\}";  #path with spaces must be backslash-quoted
  3121.     is_txtfilef "$1" || is_pdff "$1" || is_docf "$1" ||
  3122.     { _is_linkf "$1" && ! _is_imagef "$1" && ! _is_videof "$1" ;}
  3123.     ((!${?})) && printf '%s' "$1";
  3124. }
  3125.  
  3126. #check for multimodal (vision) model
  3127. function is_visionf
  3128. {
  3129.     typeset -l model; model=${1##*/};
  3130.     case "${model##ft:}" in
  3131.     *vision*|*pixtral*|*llava*|*cogvlm*|*cogagent*|*qwen*|*detic*|*codet*|*kosmos-2*|*fuyu*|*instructir*|*idefics*|*unival*|*glamm*|\
  3132.     gpt-4[a-z]*|gpt-[5-9]*|gpt-4-turbo|gpt-4-turbo-202[4-9]-[0-1][0-9]-[0-3][0-9]|\
  3133.     gemini*-1.[5-9]*|gemini*-[2-9].[0-9]*|*multimodal*|\
  3134.     claude-[3-9]*|llama[3-9][.-]*|llama-[3-9][.-]*|*mistral-7b*) :;;
  3135.     *)  ((MULTIMODAL));;
  3136.     esac;
  3137. }
  3138.  
  3139. #check for audio-model
  3140. function is_amodelf
  3141. {
  3142.     typeset -l model; model=${1##*/};
  3143.     case "${model##ft:}" in
  3144.     *audio*|*speech*|*speaker*|*bark*|*lalm*|*music*|*yi-vl*) :;;
  3145.     *)  ((MULTIMODAL>1));;
  3146.     esac;
  3147. }
  3148.  
  3149. function is_mdf
  3150. {
  3151.     [[ "\\n$1" =~ (\
  3152. [*_][*_][[:alnum:]]|\
  3153. \\n\ *\`\`\`|\
  3154. \\n\#\#*\ |\
  3155. \\n\ \ *[*-]\ |\
  3156. \\n\ \ *[0-9IiVvXx][0-9IiVvXx]*\.\ |\
  3157. \[[^\]]*\]\([^\)]*\)) ]]
  3158. }
  3159.  
  3160. function _is_docf
  3161. {
  3162.     typeset -l ext=$1
  3163.     case "$ext" in
  3164.     *.doc|*.docx|*.odt|*.ott|*.rtf) :;;
  3165.     *) false;;
  3166.     esac;
  3167. }
  3168. function is_docf {  [[ -f $1 ]] && _is_docf "$1" ;}
  3169.  
  3170. # Filtro HTML
  3171. #https://ascii.cl/htmlcodes.htm
  3172. #https://www.freeformatter.com/html-entities.html
  3173. function sedhtmlf
  3174. {
  3175.     sed -E -e 's/\xc2\xa0/ /g ; s/&mdash;/--/g' -e 's/&quot;/"/g' -e "s/&apos;/'/g" \
  3176.     -e 's/&#32;/ /g ;s/&#33;/!/g ;s/&#34;/"/g ;s/&#35;/#/g ;s/&#36;/$/g' \
  3177.     -e 's/&#37;/%/g' -e "s/&#39;/'/g" -e 's/&#40;/(/g ;s/&#41;/)/g ;s/&#42;/*/g' \
  3178.     -e 's/&#43;/+/g ;s/&#44;/,/g ;s/&#45;/-/g ;s/&#46;/./g ;s/&#47;/\//g' \
  3179.     -e 's/&#58;/:/g ;s/&#59;/;/g ;s/&#61;/=/g ;s/&#63;/?/g ;s/&#64;/@/g' \
  3180.     -e 's/&#91;/[/g ;s/&#92;/\\/g ;s/&#93;/]/g ;s/&#94;/^/g ;s/&#95;/_/g' \
  3181.     -e 's/&#96;/`/g ;s/&#123;/{/g ;s/&#124;/|/g ;s/&#125;/}/g ;s/&#126;/~/g' \
  3182.     -e 's/(&amp;|&#38;)/\&/g ;s/(&lt;|&#60;)/</g ;s/(&gt;|&#62;)/>/g ;s/(&Agrave;|&#192;)/À/g' \
  3183.     -e 's/(&Aacute;|&#193;)/Á/g ;s/(&Acirc;|&#194;)/Â/g ;s/(&Atilde;|&#195;)/Ã/g ;s/(&Auml;|&#196;)/Ä/g' \
  3184.     -e 's/(&Aring;|&#197;)/Å/g ;s/(&AElig;|&#198;)/Æ/g ;s/(&Ccedil;|&#199;)/Ç/g ;s/(&Egrave;|&#200;)/È/g' \
  3185.     -e 's/(&Eacute;|&#201;)/É/g ;s/(&Ecirc;|&#202;)/Ê/g ;s/(&Euml;|&#203;)/Ë/g ;s/(&Igrave;|&#204;)/Ì/g' \
  3186.     -e 's/(&Iacute;|&#205;)/Í/g ;s/(&Icirc;|&#206;)/Î/g ;s/(&Iuml;|&#207;)/Ï/g ;s/(&ETH;|&#208;)/Ð/g' \
  3187.     -e 's/(&Ntilde;|&#209;)/Ñ/g ;s/(&Ograve;|&#210;)/Ò/g ;s/(&Oacute;|&#211;)/Ó/g ;s/(&Ocirc;|&#212;)/Ô/g' \
  3188.     -e 's/(&Otilde;|&#213;)/Õ/g ;s/(&Ouml;|&#214;)/Ö/g ;s/(&Oslash;|&#216;)/Ø/g ;s/(&Ugrave;|&#217;)/Ù/g' \
  3189.     -e 's/(&Uacute;|&#218;)/Ú/g ;s/(&Ucirc;|&#219;)/Û/g ;s/(&Uuml;|&#220;)/Ü/g ;s/(&Yacute;|&#221;)/Ý/g' \
  3190.     -e 's/(&THORN;|&#222;)/Þ/g ;s/(&szlig;|&#223;)/ß/g ;s/(&agrave;|&#224;)/à/g ;s/(&aacute;|&#225;)/á/g' \
  3191.     -e 's/(&acirc;|&#226;)/â/g ;s/(&atilde;|&#227;)/ã/g ;s/(&auml;|&#228;)/ä/g ;s/(&aring;|&#229;)/å/g' \
  3192.     -e 's/(&aelig;|&#230;)/æ/g ;s/(&ccedil;|&#231;)/ç/g ;s/(&egrave;|&#232;)/è/g ;s/(&eacute;|&#233;)/é/g' \
  3193.     -e 's/(&ecirc;|&#234;)/ê/g ;s/(&euml;|&#235;)/ë/g ;s/(&igrave;|&#236;)/ì/g ;s/(&iacute;|&#237;)/í/g' \
  3194.     -e 's/(&icirc;|&#238;)/î/g ;s/(&iuml;|&#239;)/ï/g ;s/(&eth;|&#240;)/ð/g ;s/(&ntilde;|&#241;)/ñ/g' \
  3195.     -e 's/(&ograve;|&#242;)/ò/g ;s/(&oacute;|&#243;)/ó/g ;s/(&ocirc;|&#244;)/ô/g ;s/(&otilde;|&#245;)/õ/g' \
  3196.     -e 's/(&ouml;|&#246;)/ö/g ;s/(&oslash;|&#248;)/ø/g ;s/(&ugrave;|&#249;)/ù/g ;s/(&uacute;|&#250;)/ú/g' \
  3197.     -e 's/(&ucirc;|&#251;)/û/g ;s/(&uuml;|&#252;)/ü/g ;s/(&yacute;|&#253;)/ý/g ;s/(&thorn;|&#254;)/þ/g' \
  3198.     -e 's/(&yuml;|&#255;)/ÿ/g ;s/(&#160;|&nbsp;)/ /g ;s/(&iexcl;|&#161;)/¡/g ;s/(&cent;|&#162;)/¢/g' \
  3199.     -e 's/(&pound;|&#163;)/£/g ;s/(&curren;|&#164;)/¤/g ;s/(&yen;|&#165;)/¥/g ;s/(&brvbar;|&#166;)/¦/g' \
  3200.     -e 's/(&sect;|&#167;)/§/g ;s/(&uml;|&#168;)/¨/g ;s/(&copy;|&#169;)/©/g ;s/(&ordf;|&#170;)/ª/g' \
  3201.     -e 's/(&laquo;|&#171;)/«/g ;s/(&not;|&#172;)/¬/g ;s/(&shy;|&#173;)/­/g ;s/(&reg;|&#174;)/®/g' \
  3202.     -e 's/(&macr;|&#175;)/¯/g ;s/(&deg;|&#176;)/°/g ;s/(&plusmn;|&#177;)/±/g ;s/(&sup2;|&#178;)/²/g' \
  3203.     -e 's/(&sup3;|&#179;)/³/g ;s/(&acute;|&#180;)/´/g ;s/(&micro;|&#181;)/µ/g ;s/(&para;|&#182;)/¶/g' \
  3204.     -e 's/(&cedil;|&#184;)/¸/g ;s/(&sup1;|&#185;)/¹/g ;s/(&ordm;|&#186;)/º/g ;s/(&raquo;|&#187;)/»/g' \
  3205.     -e 's/(&frac14;|&#188;)/¼/g ;s/(&frac12;|&#189;)/½/g ;s/(&frac34;|&#190;)/¾/g ;s/(&iquest;|&#191;)/¿/g' \
  3206.     -e 's/(&times;|&#215;)/×/g ;s/(&divide;|&#247;)/÷/g ;s/(&circ;|&#710;)/ˆ/g ;s/(&tilde;|&#732;)/˜/g' \
  3207.     -e 's/(&ensp;|&#8194;)/ /g ;s/(&emsp;|&#8195;)/ /g ;s/(&thinsp;|&#8201;)/ /g ;s/(&ndash;|&#8211;)/–/g' \
  3208.     -e 's/(&mdash;|&#8212;)/—/g ;s/(&lsquo;|&#8216;)/‘/g ;s/(&rsquo;|&#8217;)/’/g ;s/(&sbquo;|&#8218;)/‚/g' \
  3209.     -e 's/(&ldquo;|&#8220;)/“/g ;s/(&rdquo;|&#8221;)/”/g ;s/(&bdquo;|&#8222;)/„/g ;s/(&dagger;|&#8224;)/†/g' \
  3210.     -e 's/(&Dagger;|&#8225;)/‡/g ;s/(&bull;|&#8226;)/•/g ;s/(&hellip;|&#8230;)/…/g ;s/(&permil;|&#8240;)/‰/g' \
  3211.     -e 's/(&prime;|&#8242;)/′/g ;s/(&Prime;|&#8243;)/″/g ;s/(&lsaquo;|&#8249;)/‹/g ;s/(&rsaquo;|&#8250;)/›/g' \
  3212.     -e 's/(&oline;|&#8254;)/‾/g ;s/(&euro;|&#8364;)/€/g ;s/(&trade;|&#8482;)/™/g ;s/(&larr;|&#8592;)/←/g' \
  3213.     -e 's/(&uarr;|&#8593;)/↑/g ;s/(&rarr;|&#8594;)/→/g ;s/(&darr;|&#8595;)/↓/g ;s/(&harr;|&#8596;)/↔/g' \
  3214.     -e 's/(&crarr;|&#8629;)/↵/g';
  3215. }
  3216.  
  3217. #dump youtube video transcription
  3218. function yt_transf
  3219. {
  3220.     curl -Ls "$1" |
  3221.     grep -o '"baseUrl":"https://www.youtube.com/api/timedtext[^"]*' |
  3222.     cut -d \" -f4 |
  3223.     sed 's/\\u0026/\&/g' |
  3224.     xargs curl -Ls |
  3225.     grep -o '<text[^<]*</text>' |
  3226.     sed -E 's/<text start="([^"]*)".*>(.*)<.*/\1 \2/' |
  3227.     sed 's/\xc2\xa0/ /g;s/&amp;/\&/g' |
  3228.     { sedhtmlf || cat ;} |
  3229.     awk '{$1=sprintf("%02d:%02d:%02d",$1/3600,$1%3600/60,$1%60)}1' |
  3230.     awk 'NR%n==1{printf"%s ",$1}{sub(/^[^ ]* /,"");printf"%s"(NR%n?FS:RS),$0}' n=2 |
  3231.     awk 1 | sed 's/^00://';
  3232. }
  3233. #https://stackoverflow.com/questions/9611397
  3234.  
  3235. #dump youtube video description
  3236. function yt_descf
  3237. {
  3238.     curl -Ls "$1" | grep -o -e '"videoDetails":{[^}]*}' | grep -oe '":"[^"]*"' | { sedhtmlf || cat ;};
  3239. }
  3240. #"shortDescription",'eow-description'
  3241. #https://stackoverflow.com/questions/72354649/
  3242. #https://stackoverflow.com/questions/76876281/
  3243.  
  3244. #alternative to du
  3245. function duf
  3246. {
  3247.     typeset s x u y
  3248.     wc -c -- "$@" | while read x y
  3249.       do  if ((x>1024*1224))
  3250.           then  x=$(bc <<<"scale=3; $x/1024/1024") s=2 u=MB;
  3251.           elif ((x>1024*5))
  3252.           then  x=$(bc <<<"scale=2; $x/1024") s=1 u=KB;
  3253.           else  s=0 u=B;
  3254.           fi
  3255.           printf "%'.*f %s  %s\\n" "$s" "$x" "$u" "$y";
  3256.       done;
  3257. }
  3258.  
  3259. #create user log
  3260. function usr_logf
  3261. {
  3262.     printf '%s  Tokens: %s\n\n%s\n' \
  3263.     "${HIST_TIME:-$(date -R 2>/dev/null||date)}" "${MAX_PREV:-?}" "$*"
  3264. }
  3265.  
  3266. #wrap text at spaces rather than mid-word
  3267. function foldf
  3268. {
  3269.     if ((!OPTFOLD)) || ((COLUMNS<18)) || [[ ! -t 1 ]]
  3270.     then    cat
  3271.     else    typeset REPLY r x n text result
  3272.  
  3273.         while IFS= read -r -d ' ' && REPLY=$REPLY' ' || ((${#REPLY}))
  3274.         do
  3275.             r=$REPLY;
  3276.             r=${r//$'\t'/        };  #fix for tabs
  3277.  
  3278.             ((OPTK)) || {  #delete ansi codes
  3279.               text="$r" result=""
  3280.               while [[ "$text" = *$'\e'*[mG]* ]]
  3281.               do
  3282.                 result="${result}${text%%$'\e'*}"
  3283.                 text="${text#*$'\e'*[mGa-zA-Z]}"
  3284.               done
  3285.               r="${result}${text}"
  3286.             }
  3287.            
  3288.             [[ $r = *$'\n'* ]] && x=${r##*$'\n'} r=${r%%$'\n'*};
  3289.  
  3290.             if (( ${#r}>COLUMNS ))  #REPLY alone is bigger than COLUMNS
  3291.             then
  3292.                 ((n = ( (n+${#r})%COLUMNS + COLUMNS)%COLUMNS )); r= ;
  3293.                 #modulus as (a%b + b)%b to avoid negative remainder.
  3294.                 printf '%s' "$REPLY";
  3295.             elif (( n+${#r}>COLUMNS ))
  3296.             then
  3297.                 n= ;
  3298.                 printf '\n%s' "$REPLY";
  3299.             else
  3300.                 (( n+${#r}==COLUMNS )) && r= n= ;
  3301.                 printf '%s' "$REPLY";
  3302.             fi
  3303.             ((n += ${#r}));
  3304.             ((${#x})) && n=${#x} x= ;
  3305.         done
  3306.     fi; return 0;
  3307. }
  3308.  
  3309. #check if a value is within a fp range
  3310. #usage: check_optrangef [val] [min] [max]
  3311. function check_optrangef
  3312. {
  3313.     typeset val min max prop ret
  3314.     val="${1:-0}" min="${2:-0}" max="${3:-0}" prop="${4:-property}"
  3315.  
  3316.     ret=$(bc <<<"($val < $min) || ($val > $max)") || function check_optrangef { : ;}  #no-`bc' systems
  3317.     if [[ $val = *[!0-9.,+-]* ]] || ((ret))
  3318.     then    printf "${RED}Warning: Bad %s${NC}${BRED} -- %s  ${NC}${YELLOW}(%s - %s)${NC}\\n" "$prop" "$val" "$min" "$max" >&2
  3319.         return 1
  3320.     fi ;return ${ret:-0}
  3321. }
  3322.  
  3323. #check and set settings
  3324. function set_optsf
  3325. {
  3326.     typeset s n p stop
  3327.     typeset -a pids
  3328.  
  3329.     case "$MOD" in
  3330.         o[1-9]*) ((MOD_REASON)) || {
  3331.             ((OPTMM<1024*3 && OPTMAX<1024*4)) && {
  3332.                 _warmsgf 'Warning:' 'Reasoning requires large numbers of output tokens';
  3333.                 OPTMAX_REASON=$OPTMAX OPTMAX=25000;
  3334.             }
  3335.             ((STREAM)) && _warmsgf 'Warning:' 'Reasoning models do not support streaming yet';
  3336.             ((${#INSTRUCTION_CHAT}+${#INSTRUCTION})) && _warmsgf 'Warning:' 'Reasoning models do not support system messages yet';
  3337.             #[[ -n $OPTA ]] && _warmsgf 'Warning:' 'Resetting presence_penalty';
  3338.             #[[ -n $OPTAA ]] && _warmsgf 'Warning:' 'Resetting frequency_penalty';
  3339.             STREAM_REASON=$STREAM OPTA_REASON=$OPTA OPTAA_REASON=$OPTAA OPTT_REASON=$OPTT INSTRUCTION_CHAT_REASON=$INSTRUCTION_CHAT INSTRUCTION_REASON=$INSTRUCTION;
  3340.             STREAM= OPTA= OPTAA= OPTT=1 MOD_REASON=1 CURLTIMEOUT="--max-time 900" INSTRUCTION_CHAT= INSTRUCTION=;
  3341.         }
  3342.         ;;
  3343.         llava-v1.5-7b-4096-preview)  #groq vision
  3344.         INSTRUCTION_CHAT= INSTRUCTION=;
  3345.         ;;
  3346.         *) ((MOD_REASON)) && STREAM=$STREAM_REASON OPTA=$OPTA_REASON OPTAA=$OPTAA_REASON OPTT=$OPTT_REASON OPTMAX=${OPTMAX_REASON:-$OPTMAX} INSTRUCTION_CHAT=$INSTRUCTION_CHAT_REASON INSTRUCTION=$INSTRUCTION_REASON MOD_REASON= CURLTIMEOUT=;
  3347.         ;;
  3348.     esac
  3349.     ((GITHUBAI)) && [[ $OPTA$OPTAA = *[1-9]* ]] &&
  3350.     case "$MOD" in
  3351.         Mistral-*|AI21-Jamba*)
  3352.         _warmsgf 'Warning:' 'model may not support frequency_ and/or presence_penalty';
  3353.         OPTA= OPTAA=;
  3354.         ;;
  3355.     esac;
  3356.  
  3357.     ((OPTI+OPTEMBED)) || {
  3358.       ((OPTW+OPTZ && !CHAT_ENV)) || {
  3359.         check_optrangef "$OPTA"   -2.0 2.0 'Presence-penalty'
  3360.         check_optrangef "$OPTAA"  -2.0 2.0 'Frequency-penalty'
  3361.         ((OPTB)) && check_optrangef "${OPTB:-$OPTN}"  "$OPTN" 50 'Best_of'
  3362.         check_optrangef "$OPTBB" 0   5 'Logprobs'
  3363.  
  3364.         check_optrangef "$OPTP"  0.0 1.0 'Top_p'
  3365.         ((!OPTMAX && OPTBB)) ||
  3366.         check_optrangef "$OPTMAX"  1 "$MODMAX" 'Response Max Tokens'
  3367.       }
  3368.       check_optrangef "$OPTT"  0.0 $( ((MISTRALAI+ANTHROPICAI)) && echo 1.0 || echo 2.0) 'Temperature'  #whisper 0.0 - 1.0
  3369.       #change temp or top_p but not both
  3370.     }
  3371.     ((OPTI)) && check_optrangef "$OPTN"  1 10 'Number of Results'
  3372.     case "$OPTSEED" in *[!0-9]*)
  3373.       printf "${RED}Warning: Bad %s${NC}${BRED} -- %s  ${NC}${YELLOW}(integer)${NC}\\n" "seed" "$OPTSEED" >&2;;
  3374.     esac
  3375.  
  3376.     [[ -n $OPTA ]] && OPTA_OPT="\"presence_penalty\": $OPTA," || unset OPTA_OPT
  3377.     [[ -n $OPTAA ]] && OPTAA_OPT="\"frequency_penalty\": $OPTAA," || unset OPTAA_OPT
  3378.     { ((OPTB)) && OPTB_OPT="\"best_of\": $OPTB," || unset OPTB OPTB_OPT;
  3379.       ((OPTBB)) && OPTBB_OPT="\"logprobs\": $OPTBB," || unset OPTBB OPTBB_OPT; } 2>/dev/null
  3380.     [[ -n $OPTP ]] && OPTP_OPT="\"top_p\": $OPTP," || unset OPTP_OPT
  3381.     [[ -n $OPTKK ]] && OPTKK_OPT="\"top_k\": $OPTKK," || unset OPTKK_OPT
  3382.     if ((OPTSUFFIX+${#SUFFIX})); then   OPTSUFFIX_OPT="\"suffix\": \"$(escapef "$SUFFIX")\","; else     unset OPTSUFFIX_OPT; fi;
  3383.     if [[ -n $OPTSEED ]]
  3384.     then #seed  integer or null: openai, groq, ollama.
  3385.       OPTSEED_OPT="\"${MISTRALAI:+random_}seed\": $OPTSEED," || unset OPTSEED
  3386.     fi
  3387.     if ((STREAM))
  3388.     then    STREAM_OPT="\"stream\": true,";
  3389.     else    STREAM_OPT="\"stream\": false,"; unset STREAM;
  3390.     fi
  3391.     ((OPT_KEEPALIVE)) && OPT_KEEPALIVE_OPT="\"keep_alive\": $OPT_KEEPALIVE," || unset OPT_KEEPALIVE_OPT
  3392.     ((OPTV<1)) && unset OPTV  #IPC#
  3393.    
  3394.     if ((${#STOPS[@]})) && [[ "${STOPS[*]}" != "${STOPS_OLD[*]:-%#}" ]]
  3395.     then  #compile stop sequences  #def: <|endoftext|>
  3396.         OPTSTOP=;
  3397.         for s in "${STOPS[@]}"
  3398.         do  [[ -n $s ]] || continue
  3399.             ((++n)) ;((n>4)) && break
  3400.             OPTSTOP="${OPTSTOP}${OPTSTOP:+,}\"$(escapef "$s")\""
  3401.         done
  3402.         ((ANTHROPICAI)) && stop="stop_sequences" || stop="stop";
  3403.         if ((n==1))
  3404.         then    OPTSTOP="\"${stop}\":${OPTSTOP},"
  3405.         elif ((n))
  3406.         then    OPTSTOP="\"${stop}\":[${OPTSTOP}],"
  3407.         fi; STOPS_OLD=("${STOPS[@]}");
  3408.     fi #https://help.openai.com/en/articles/5072263-how-do-i-use-stop-sequences
  3409.     ((EPN==6)) || {
  3410.       [[ "$RESTART" = "$RESTART_OLD" ]] || restart_compf
  3411.       [[ "$START" = "$START_OLD" ]] || start_compf
  3412.     }
  3413.  
  3414.     case "${MOD_PRICE[*]}" in
  3415.     *[0-9]*[$IFS]*[0-9]*)   :;;
  3416.     *[0-9]*|*[a-zA-Z]*)     _warmsgf "err:" "bad model prices -- ${MOD_PRICE[*]}";
  3417.             MOD_PRICE=();;
  3418.     esac;
  3419.  
  3420.     #update pid array
  3421.     for p in ${PIDS[@]}
  3422.     do  kill -0 -- $p 2>/dev/null && pids+=($p);
  3423.     done; PIDS=(${pids[@]});
  3424. }
  3425.  
  3426. function restart_compf { ((${#1}+${#RESTART})) && RESTART=$(escapef "$(unescapef "${1:-$RESTART}")") RESTART_OLD="$RESTART" ;}
  3427. function start_compf { ((${#1}+${#START})) && START=$(escapef "$(unescapef "${1:-$START}")") START_OLD="$START" ;}
  3428.  
  3429. function record_confirmf
  3430. {
  3431.     if ((OPTV<1)) && {  ((!WSKIP)) || [[ ! -t 1 ]] ;}
  3432.     then    printf "\\n${NC}${BWHITE}${ON_PURPLE}%s${NC}" ' * [e]dit_text,  [w]hisper_off * ' \
  3433.                                   ' * Press ENTER to START record * ' >&2;
  3434.         case "$(read_charf)" in [Q])    return 202;; [AaOoqWw])     return 196;; [Ee]|$'\e')    return 199;; esac;
  3435.         _clr_lineupf 33; _clr_lineupf 33;  #!#
  3436.     fi
  3437.     printf "\\n${NC}${BWHITE}${ON_PURPLE}%s\\a${NC}\\n" ' * [e]dit, [r]edo, [w]hspr_off * ' >&2
  3438.     printf "\\r${NC}${BWHITE}${ON_PURPLE}%s\\a${NC}\\n" ' * Press ENTER to  STOP record * ' >&2
  3439. }
  3440.  
  3441. #record mic
  3442. #usage: recordf [filename]
  3443. function recordf
  3444. {
  3445.     typeset termux pid ret
  3446.     case "$REC_CMD" in
  3447.         termux*) termux=1;;
  3448.         false)  return 196;;
  3449.     esac
  3450.    
  3451.     #move out file before writing
  3452.     [[ -s $1 ]] && mv -f -- "$1" "${1%.*}.2.${1##*.}";
  3453.    
  3454.     [[ -n $TERMUX_VERSION ]] &&
  3455.     if is_amodelf "$MOD"
  3456.     then    OPTV=2 set_termuxpulsef;
  3457.         case "$REC_CMD" in *termux*)    _warmsgf 'Warning:' 'Audio-models require SoX or FFmpeg';; esac;
  3458.     else    set_termuxpulsef;
  3459.     fi
  3460.  
  3461.     $REC_CMD "$1" >&2 & pid=$! PIDS+=($!);
  3462.     trap "trap 'exit' INT; ret=199;" INT;
  3463.    
  3464.     #see record_confirmf()
  3465.     case "$(read_charrecf "$@")" in
  3466.         [Q])    ret=202  #exit
  3467.             ;;
  3468.         [AaOoqWw])   ret=196  #whisper off
  3469.             ;;
  3470.         [Ee]|$'\e') ret=199  #text edit (single-shot)
  3471.             ;;
  3472.         [RrSs]) rec_killf $pid $termux;  #redo, quit
  3473.             trap 'exit' INT;
  3474.             wait $pid;
  3475.             OPTV=4 WSKIP= record_confirmf;
  3476.             recordf "$@"; return;
  3477.             ;;
  3478.     esac
  3479.  
  3480.     rec_killf $pid $termux;
  3481.         trap 'exit' INT;
  3482.     wait $pid
  3483.     [[ -n $TERMUX_VERSION ]] && sleep 0.6;  #termux on slow-cpu bug workaround
  3484.  
  3485.     #if command -v ffmpeg >/dev/null 2>&1
  3486.     #then   trim_silencef "$FILEINW";
  3487.     #fi
  3488.     return ${ret:-0};
  3489. }
  3490. #avfoundation for macos: <https://apple.stackexchange.com/questions/326388/>
  3491. function rec_killf
  3492. {
  3493.     typeset pid termux
  3494.     pid=$1 termux=$2
  3495.     ((termux)) && termux-microphone-record -q >&2 || kill -INT -- $pid 2>/dev/null >&2;
  3496. }
  3497.  
  3498. #whisper silence detection (hands-free, options -Wwvv)
  3499. function read_charrecf
  3500. {
  3501.     typeset atrim min_len tmout rms threshold init var
  3502.     tmout=0.3    #read timeout
  3503.     atrim=0.26   #audio trim
  3504.     min_len=1.66 #seconds (float)
  3505.     rms=0.0157   #rms amplitude (0.001 to 0.1)
  3506.     threshold="-26dB"  #noise tolerance (-32dbFS to -26dBFS)
  3507.  
  3508.     if ((OPTV>1)) &&
  3509.         { case "$REC_CMD" in termux-microphone-record*)  false;; *)  :;; esac ;} &&
  3510.         command -v ffmpeg >/dev/null 2>&1
  3511.     then
  3512.       _cmdmsgf "Silence Detection:" "tolerance: ${threshold}  min_length: ${min_len}s";
  3513.       _clr_ttystf; sleep ${min_len};
  3514.       while var=$(
  3515.           ffmpeg -fflags +nobuffer+discardcorrupt \
  3516.             -i "$1" -af silencedetect=n=${threshold}:d=${min_len} -f null - 2>&1 </dev/null |
  3517.             sed -n 's/^.*silence_start:[[:space:]]*//p' | sed -n '$ p'
  3518.         )  #!# ignore start silence until speech
  3519.         (( $(bc <<<"${var:-0} < (${min_len} * 1.6)") ))
  3520.       do
  3521.         NO_CLR=1 read_charf -t ${tmout} && break 1;
  3522.       done;
  3523.     elif ((OPTV>1)) &&
  3524.         { case "$REC_CMD" in termux-microphone-record*)  false;; *)  :;; esac ;} &&
  3525.         command -v sox >/dev/null 2>&1
  3526.     then
  3527.       _cmdmsgf "Silence Detection:" "rms_amplitude: ${rms}  min_length: ${min_len}s";
  3528.       _clr_ttystf; sleep ${min_len};
  3529.       while var=$(
  3530.           sox "$1" -n trim -${min_len} stat 2>&1 </dev/null |
  3531.             sed -n 's/RMS .*amplitude:[[:space:]]*//p'
  3532.         )
  3533.         if ((!init))  #!# enable detection after first rms peak
  3534.         then  (( $(bc <<<"${var:-0} > (${rms} * 2.0)") )) && init=1 || var=100;
  3535.         fi
  3536.         (( $(bc <<<"${var:-100} > ${rms}") ))
  3537.       do
  3538.         NO_CLR=1 read_charf -t ${tmout} && break 1;
  3539.       done;
  3540.     else  #defaults
  3541.         read_charf;
  3542.     fi
  3543. }
  3544. #https://www.izotope.com/en/learn/what-is-crest-factor.html
  3545.  
  3546. #remove silence from both ends of audio file
  3547. function trim_silencef
  3548. {
  3549.     typeset out atrim start_periods start_silence threshold
  3550.     out=${1%.*}.2.${1##*.}
  3551.     atrim=0.26
  3552.     start_periods=1
  3553.     start_silence=0.2
  3554.     threshold="-38dB"
  3555.  
  3556.     ffmpeg -fflags +discardcorrupt -y -i "${1}" -af \
  3557. atrim=start=${atrim},areverse,atrim=start=${atrim},asetpts=PTS-STARTPTS,\
  3558. silenceremove=start_periods=${start_periods}:start_threshold=${threshold}:start_silence=${start_silence}:detection=peak,\
  3559. areverse,asetpts=PTS-STARTPTS,\
  3560. silenceremove=start_periods=${start_periods}:start_threshold=${threshold}:start_silence=${start_silence}:detection=peak \
  3561.     "${out}" >/dev/null 2>&1 &&
  3562.     mv -f "${out}" "${1}";
  3563. }
  3564. #https://ffmpeg.org/ffmpeg-filters.html#toc-Examples-23
  3565. #https://lists.ffmpeg.org/pipermail/ffmpeg-user/2021-August/053415.html
  3566. #https://github.com/openai/openai-cookbook/blob/main/examples/Whisper_processing_guide.ipynb
  3567. #tip: set threshold to "mean_vol", or "max_vol minus -3dB to -6dB"  #ffmpeg mailing
  3568. #voice-gate: lowpass=200,highpass=100  #man ffmpeg-filters
  3569. #lowpass=5000,highpass=200  #fast filtering not perfect
  3570. #-af lowpass=3000,highpass=200,afftdn=nf=-25
  3571. #-af arnndn=m=cb.rnnn  #voice filters
  3572.  
  3573. #extract mean and max volume from audio stream
  3574. #ffmpeg -i "${1}" -af "atrim=start=0.2,areverse,atrim=start=0.2,volumedetect" -f null - 2>&1 | sed -n -e 's/[[:space:]]*dB//' -e 's/.*mean_volume:[[:space:]]*//p' -e 's/.*max_volume:[[:space:]]*//p'
  3575.  
  3576. #amplitude ratio <-> decibel FS converter
  3577. #bc -l <<<"scale=4; 20 * l( ${rms} ) / l(10)"
  3578. #bc -l <<<"scale=6; e((${dbfs} / 20) * l(10))"
  3579.  
  3580. #set whisper language
  3581. function _set_langf
  3582. {
  3583.     if [[ $1 = [a-z][a-z] ]]
  3584.     then    if ((!OPTWW))
  3585.         then    LANGW="-F language=$1"
  3586.             ((OPTV)) || sysmsgf 'Language:' "$1"
  3587.         fi ;return 0
  3588.     fi ;return 1
  3589. }
  3590.  
  3591. #whisper
  3592. function whisperf
  3593. {
  3594.     typeset file rec var max pid granule scale;
  3595.     typeset -a args; args=(); WHISPER_OUT=;
  3596.    
  3597.     if ((!(CHAT_ENV+MTURN) ))
  3598.     then    sysmsgf 'Whisper Model:' "$MOD_AUDIO";
  3599.         sysmsgf 'Temperature:' "${OPTTW:-$OPTT}";
  3600.     fi;
  3601.     check_optrangef "${OPTTW:-$OPTT}" 0 1.0 Temperature
  3602.     set_model_epnf "$MOD_AUDIO"
  3603.    
  3604.     ((${#})) || [[ -z ${WARGS[*]} ]] || set -- "${WARGS[@]}" "$@";
  3605.     for var
  3606.     do    [[ $var != *[!$IFS]* ]] && shift || break;
  3607.     done; var= ; args=("$@");
  3608.  
  3609.     #set language ISO-639-1 (two letters)
  3610.     if _set_langf "$1"
  3611.     then    shift
  3612.     elif _set_langf "$2"
  3613.     then    set -- "${@:1:1}" "${@:3}"
  3614.     fi
  3615.    
  3616.     if {    ((!$#)) || [[ ! -e $1 && ! -e ${@:${#}} ]] ;} && ((!CHAT_ENV))
  3617.     then    printf "${PURPLE}%s ${NC}" 'Record mic input? [Y/n]' >&2
  3618.         [[ -t 1 ]] && echo >&2 || var=$(read_charf)
  3619.         case "$var" in
  3620.             [Q])    return 202;;  #exit
  3621.             [AaNnq]|$'\e')  :;;
  3622.             *)  ((CHAT_ENV)) || sysmsgf 'Rec Cmd:' "\"${REC_CMD%% *}\"";
  3623.                 OPTV=4 record_confirmf || return
  3624.                 WSKIP=1 recordf "$FILEINW"
  3625.                 set -- "$FILEINW" "$@"; rec=1;;
  3626.         esac
  3627.     fi
  3628.    
  3629.     if is_audiof "$1"
  3630.     then    file="$1"; shift;
  3631.     elif ((${#} >1)) && is_audiof "${@:${#}}"
  3632.     then    file="${@:${#}}"; set -- "${@:1:$((${#}-1))}";
  3633.     else    printf "${BRED}Err: %s --${NC} %s\\n" 'Unknown audio format' "${1:-nill}" >&2
  3634.         return 1
  3635.     fi ;[[ -f $1 ]] && shift  #get rid of eventual second filename
  3636.     if var=$(wc -c <"$file"); ((var > 25000000));
  3637.     then    du -h "$file" >&2;
  3638.         _warmsgf 'Warning:' "Whisper input exceeds API limit of 25 MB";
  3639.     fi
  3640.    
  3641.     #set a prompt (224 tokens, GPT2 encoding)
  3642.     max=490  #2-3 chars/tkn code and foreign languages, 4 chars/tkn english
  3643.     if var=$*; [[ $var = *[!$IFS]* ]]
  3644.     then
  3645.         ((${#var}>max)) && var=${var: ${#var}-max};
  3646.         set -- -F prompt="$var";
  3647.         ((CHAT_ENV+MTURN)) || sysmsgf 'Text Prompt:' "${var:0: COLUMNS-17}$([[ -n ${var: COLUMNS-17} ]] && echo ...)";
  3648.     fi
  3649.  
  3650.     ((OPTW>2||OPTWW>2)) && granule=word || granule=segment;
  3651.    
  3652.     if [[ $granule = segment ]] || ((GROQAI))
  3653.     then    scale=2;
  3654.     else  #word level
  3655.         scale=${OPTW:-3};
  3656.         set -- -F "timestamp_granularities[]=${granule}" "$@";
  3657.     fi
  3658.  
  3659.     [[ -s $FILE ]] && mv -f -- "$FILE" "${FILE%.*}.2.${FILE##*.}";
  3660.  
  3661.     #response_format (timestamps)
  3662.     if ((OPTW>1 || OPTWW>1)) && ((!CHAT_ENV))
  3663.     then
  3664.         OPTW_FMT=verbose_json   #json, text, srt, verbose_json, or vtt.
  3665.         set -- -F "response_format=${OPTW_FMT}" "$@";
  3666.  
  3667.         prompt_audiof "$file" $LANGW "$@" && {
  3668.         jq -r "def scale: ${scale}; ${JQCOLNULL} ${JQCOL} ${JQDATE}
  3669.             \"Task: \(.task)\" +
  3670.             \"    \" + \"Gran: ${granule}\" +
  3671.             \"    \" + \"Lang: \(.language)\" +
  3672.             \"    \" + \"Dur: \(.duration|seconds_to_time_string)\" +
  3673.             \"\\n\", (.text//empty) +
  3674.             \"\\n\", (.${granule}s[]| \"[\" + yellow + \"\(.start|seconds_to_time_string)\" + reset + \"]\" +
  3675.             \" \" + bpurple + (.text//.${granule}) + reset)" "$FILE" | foldf \
  3676.         || jq -r "if .${granule}s then (.${granule}s[] | (.start|tostring) + (.text//.${granule}//empty)) else (.text//.${granule}//empty) end" "$FILE" || ! _warmsgf 'Err' ;}
  3677.     else
  3678.         prompt_audiof "$file" $LANGW "$@" && {
  3679.         jq -r "def scale: 1; ${JQCOLNULL} ${JQCOL} ${JQDATE}
  3680.         bpurple + (.text//.${granule}//empty) + reset" "$FILE" | foldf \
  3681.         || jq -r ".text//.${granule}//empty" "$FILE" || ! _warmsgf 'Err' ;}
  3682.     fi & pid=$! PIDS+=($!);
  3683.     trap "trap 'exit' INT; kill -- $pid 2>/dev/null; BAD_RES=1" INT;
  3684.  
  3685.     wait $pid; trap 'exit' INT; wait $pid &&  #check exit code
  3686.     if WHISPER_OUT=$(jq -r "def scale: ${scale}; ${JQDATE} if .${granule}s then (.${granule}s[] | \"[\(.start|seconds_to_time_string)]\" + (.text//.${granule}//empty)) else (.text//.${granule}//empty) end" "$FILE" 2>/dev/null) &&
  3687.         ((${#WHISPER_OUT}))
  3688.     then
  3689.         ((!CHAT_ENV)) && [[ -d ${FILEWHISPERLOG%/*} ]] &&  #log output
  3690.         printf '\n====\n%s\n\n%s\n' "$(date -R 2>/dev/null||date)" "$WHISPER_OUT" >>"$FILEWHISPERLOG" &&
  3691.         _sysmsgf 'Whisper Log:' "$FILEWHISPERLOG";
  3692.  
  3693.         ((OPTCLIP && !CHAT_ENV)) && (${CLIP_CMD:-false} <<<"$WHISPER_OUT" &);  #clipboard
  3694.         :;
  3695.     else    false;
  3696.     fi || {
  3697.         #[[ -s $FILE ]] && jq . "$FILE" >&2 2>/dev/null;
  3698.         _warmsgf $'\nerr:' 'whisper response';
  3699.         printf 'Retry request? Y/n ' >&2;
  3700.         var=$(if ((!BAD_RES)) && [[ -s $FILEINW ]]; then  _printbf 'wait'; sleep 0.6; _printbf '    '; else    read_charf; fi)
  3701.         case "$var" in
  3702.             [Q])    return 202;;
  3703.             [AaNnq]) false;;  #no
  3704.             *)  ((rec)) && args+=("$FILEINW")
  3705.                 BAD_RES=1 whisperf "${args[@]}";;
  3706.         esac
  3707.     }
  3708. }
  3709. #JQ function: seconds to compound time
  3710. JQDATE="def yscale: 2;
  3711. def pad(x): tostring | (length | if . >= x then \"\" else \"0\" * (x - .) end) as \$padding | \"\(\$padding)\(.)\";
  3712. def pade(x): tostring | (length | if . >= x then \"\" else \"0\" * (x - .) end) as \$padding | \"\(.)\(\$padding)\";
  3713. def padf(x;y): tostring | split(\".\") |  ( (first | pad(x)) + \".\" + (last | pade(y)));
  3714. def seconds_to_time_string:
  3715.  def nonzero: floor | if . > 0 then . else empty end;
  3716.  def decimal_places:
  3717.    . as \$in | (\$in * pow(10;scale) | floor) as \$rounded | (\$rounded / pow(10;scale) | tostring) | split(\".\") | (\"0.\" + last) ;
  3718.  if . == 0 then \"00\"
  3719.  else
  3720.    [(./60/60         | nonzero),
  3721.     (./60       % 60 | pad(2)),
  3722.     ( (. % 60) + ( (. - (. | tostring | split(\".\") | first | tonumber) ) | decimal_places | tonumber) | padf(2;scale))]
  3723.  | join(\":\")
  3724.  end;"
  3725. #https://rosettacode.org/wiki/Convert_seconds_to_compound_duration#jq
  3726. #https://stackoverflow.com/questions/64957982/how-to-pad-numbers-with-jq
  3727.  
  3728. #request tts prompt
  3729. function prompt_ttsf
  3730. {
  3731.     curl -N -Ss ${FAIL} -L "${BASE_URL}${ENDPOINTS[EPN]}" \
  3732.         -X POST \
  3733.         -H "Authorization: Bearer $OPENAI_API_KEY" \
  3734.         -H 'Content-Type: application/json' \
  3735.         -d "$BLOCK" \
  3736.         -o "$FOUT"
  3737. }
  3738. #disable curl progress-bar because of `chunk transfer encoding'
  3739.  
  3740. #speech synthesis (tts)
  3741. function _ttsf
  3742. {
  3743.     typeset FOUT VOICEZ SPEEDZ fname input max ret pid var secs ok n m i
  3744.     typeset -a SPIN_CHARS=("${SPIN_CHARS8[@]}");
  3745.     ((${#OPTZ_VOICE})) && VOICEZ=$OPTZ_VOICE
  3746.     ((${#OPTZ_SPEED})) && SPEEDZ=$OPTZ_SPEED
  3747.    
  3748.     ((${#})) || [[ -z ${ZARGS[*]} ]] || set -- "${ZARGS[@]}" "$@";
  3749.     for var
  3750.     do    [[ $var != *[!$IFS]* ]] && shift || break;
  3751.     done; var= ;
  3752.    
  3753.     if ((!CHAT_ENV)) || ((${#ZARGS[@]}))
  3754.     then    #set speech voice, out file format, and speed
  3755.         _set_ttsf "$3" && set -- "${@:1:2}" "${@:4}"
  3756.         _set_ttsf "$2" && set -- "${@:1:1}" "${@:3}"
  3757.         _set_ttsf "$1" && shift
  3758.     fi
  3759.  
  3760.     [[ $FOUT = "-" ]] || FOUT=$(set_fnamef "${FILEOUT_TTS%.*}.${OPTZ_FMT}");
  3761.  
  3762.     [[ ${MOD_SPEECH} = tts-1* ]] && max=4096 || max=40960;
  3763.     ((${#} >1)) && set -- "$*";
  3764.  
  3765.     if ((!CHAT_ENV))
  3766.     then    sysmsgf 'Speech Model:' "$MOD_SPEECH";
  3767.         sysmsgf 'Voice:' "$VOICEZ";
  3768.         sysmsgf 'Speed:' "${SPEEDZ:-1}";
  3769.     fi; ((${#SPEEDZ})) && check_optrangef "$SPEEDZ" 0.25 4 'TTS speed'
  3770.     [[ $1 = *[!$IFS]* ]] || ! echo '(empty)' >&2 || return 2
  3771.  
  3772.     if ((${#1}>max))
  3773.     then    _warmsgf 'Warning:' "User input ${#1} chars / max ${max} chars"  #max ~5 minutes
  3774.         i=1 FOUT=${FOUT%.*}-${i}.${OPTZ_FMT};
  3775.     fi  #https://help.openai.com/en/articles/8555505-tts-api
  3776.     REPLAY_FILES=();
  3777.  
  3778.     while input=${1:0: max}; set -- "${1:max}";
  3779.     do
  3780.         if ((!CHAT_ENV))
  3781.         then    var=${input//\\\\[nt]/ };
  3782.             _sysmsgf $'\nFile Out:' "${FOUT/"$HOME"/"~"}";
  3783.             sysmsgf 'Text Prompt:' "${var:0: COLUMNS-17}$([[ -n ${input: COLUMNS-17} ]] && echo ...)";
  3784.         fi; REPLAY_FILES=("${REPLAY_FILES[@]}" "$FOUT"); var= ;
  3785.        
  3786.         BLOCK="{
  3787. \"model\": \"${MOD_SPEECH}\",
  3788. \"input\": \"${input:-$*}\",
  3789. \"voice\": \"${VOICEZ}\", ${SPEEDZ:+\"speed\": ${SPEEDZ},}
  3790. \"response_format\": \"${OPTZ_FMT}\"${BLOCK_USR_TTS:+,$NL}$BLOCK_USR_TTS
  3791. }"
  3792.         ((OPTVV)) && _warmsgf "TTS:" "Model: ${MOD_SPEECH:-unset}, Voice: ${VOICEZ:-unset}, Speed: ${SPEEDZ:-unset}, Block: ${BLOCK}"
  3793.         _sysmsgf 'TTS:' '<ctr-c> [k]ill, <enter> play ' '';  #!#
  3794.  
  3795.         prompt_ttsf & pid=$! secs=$SECONDS;
  3796.         trap "trap 'exit' INT; kill -- $pid 2>/dev/null; return;" INT;
  3797.         while _spinf; ok=
  3798.             kill -0 -- $pid  >/dev/null 2>&1 || ! echo >&2
  3799.         do  var=$(
  3800.               if ((OPTV>0)) && ((!${#TERMUX_VERSION}))
  3801.               then
  3802.                 printf '%s\n' 'p' >&2;
  3803.               else
  3804.                 NO_CLR=1 read_charf -t 0.3;
  3805.               fi
  3806.             ) &&
  3807.             case "$var" in
  3808.                 [Pp]|' '|''|$'\t')  ok=1;
  3809.                     ((SECONDS>secs+2)) ||  #buffer
  3810.                     read_charf -t $((secs+2-SECONDS)) >/dev/null 2>&1;
  3811.                     break 1;;
  3812.                 [CcEeKkQqSs]|$'\e')  ok=1 ret=130;
  3813.                     kill -s INT -- $pid 2>/dev/null;
  3814.                     break 1;;
  3815.             esac
  3816.         done </dev/tty; _clr_lineupf $((4+1+29+${#var}));  #!#
  3817.  
  3818.         ((ok)) || wait $pid || ((ret+=$?));
  3819.         trap 'exit' INT;
  3820.         jq . "$FOUT" >&2 2>/dev/null && ((ret+=$?));  #json response is an err
  3821.  
  3822.         case $ret in
  3823.             1[2-9][0-9]|2[0-5][0-9]) break 1;;
  3824.             [1-9]|[1-9][0-9])
  3825.                 _warmsgf $'\rerr:' 'tts response';
  3826.                 printf 'Retry request? Y/n ' >&2;
  3827.                 case "$(read_charf)" in
  3828.                     [Q])    return 202;;
  3829.                     [AaNnQq]) break 1;;  #no
  3830.                     *)  continue;;
  3831.                 esac;;
  3832.         esac
  3833.  
  3834.     [[ $FOUT = "-"* ]] || [[ ! -e $FOUT ]] || {
  3835.         du -h "$FOUT" >&2 2>/dev/null || _sysmsgf 'TTS File:' "$FOUT";
  3836.         ((OPTV && !CHAT_ENV)) || [[ ! -s $FOUT ]] || {
  3837.             ((CHAT_ENV)) || sysmsgf 'Play Cmd:' "\"${PLAY_CMD}\"";
  3838.             case "$PLAY_CMD" in false)  return $ret;; esac;
  3839.         while
  3840.             [[ -n $TERMUX_VERSION ]] && set_termuxpulsef;
  3841.             ${PLAY_CMD} "$FOUT" >&2 & pid=$! PIDS+=($!);
  3842.         do
  3843.             trap "trap 'exit' INT; kill -- $pid 2>/dev/null; case \"\$PLAY_CMD\" in *termux-media-player*) termux-media-player stop;; esac;" INT;
  3844.             wait $pid;
  3845.             case $? in
  3846.                 0)  case "$PLAY_CMD" in *termux-media-player*) while sleep 1 ;[[ $(termux-media-player info 2>/dev/null) = *[Pp]laying* ]] ;do : ;done;; esac;  #termux fix
  3847.                     var=3;  #3+1 secs
  3848.                     ((OPTV)) && var=2;;
  3849.                 *)  wait $pid;
  3850.                     var=8;;  #8+1 secs
  3851.             esac;
  3852.             trap 'exit' INT;
  3853.             _warmsgf $'\nReplay?' 'N/y/[w]ait ' '';  #!# #F#
  3854.             for ((n=var;n>-1;n--))
  3855.             do  printf '%s\b' "$n" >&2
  3856.                 if var=$(NO_CLR=1 read_charf -t 1)
  3857.                 then    case "$var" in
  3858.                     [Q])    return 202;;
  3859.                     [RrYy]|$'\t') continue 2;;
  3860.                     [PpWw]|[$' \e']) printf '%s' waiting.. >&2;
  3861.                         read_charf >/dev/null;
  3862.                         continue 2;;  #wait key press
  3863.                     *)  break;;
  3864.                     esac;
  3865.                 fi; ((n)) || echo >&2;
  3866.             done; _clr_lineupf 19;  #!#
  3867.             break;
  3868.         done
  3869.         }
  3870.         ((++i)); FOUT=${FOUT%-*}-${i}.${OPTZ_FMT};
  3871.         ((${#1})) && ((!ret)) || break 1;
  3872.     }
  3873.     done;
  3874.     return $ret
  3875. }
  3876. function ttsf
  3877. {
  3878.     if ((CHAT_ENV))
  3879.     then    typeset BASE_URL OPENAI_API_KEY ENDPOINTS EPN MOD;
  3880.         ENDPOINTS=(); MOD=$MOD_SPEECH;
  3881.         EPN=10 ENDPOINTS[10]="/audio/speech";
  3882.         BASE_URL=$OPENAI_BASE_URL_DEF;
  3883.         OPENAI_API_KEY=$OPENAI_API_KEY_DEF;  #only OpenAI
  3884.     fi
  3885.     _ttsf "$@";
  3886. }
  3887. function _set_ttsf {    __set_outfmtf "$1" || __set_voicef "$1" || __set_speedf "$1" ;}
  3888. function __set_voicef
  3889. {
  3890.     case "$1" in
  3891.         #alloy|echo|fable|onyx|nova|shimmer
  3892.         #alloy|ash|ballad|coral|echo|sage|shimmer|verse  #realtime
  3893.         [Aa][Ll][Ll][Oo][Yy]|[Ee][Cc][Hh][Oo]|[Ff][Aa][Bb][Ll][Ee]|[Oo][Nn][YyIi][Xx]|[Nn][Oo][Vv][Aa]|[Ss][Hh][Ii][Mm][Mm][Ee][Rr]|\
  3894.         [Ss][Kk][Yy]|[Aa][Ss][Hh]|[Bb][Aa][Ll][Ll][Aa][Dd]|[Cc][Oo][Rr][Aa][Ll]|[Ss][Aa][Gg][Ee]|[Vv][Ee][Rr][Ss][Ee])  VOICEZ=$1;;
  3895.         *)  false;;
  3896.     esac
  3897. }
  3898. function __set_outfmtf
  3899. {
  3900.     case "$1" in  #mp3|opus|aac|flac
  3901.         [Mm][Pp]3|[Oo][Pp][Uu][Ss]|[Aa][Aa][Cc]|[Ff][Ll][Aa][Cc])   OPTZ_FMT=$1;;
  3902.         *?.[Mm][Pp]3|*?.[Oo][Pp][Uu][Ss]|*?.[Aa][Aa][Cc]|*?.[Ff][Ll][Aa][Cc])   OPTZ_FMT=${1##*.} FILEOUT_TTS=$1;;
  3903.         *?/)    [[ -d $1 ]] && FILEOUT_TTS=${1%%/}/${FILEOUT_TTS##*/};;
  3904.         -)  FOUT='-';;
  3905.         *)  false;;
  3906.     esac
  3907. }
  3908. function __set_speedf
  3909. {
  3910.     case "$1" in
  3911.         [.,][0-9]*([0-9]))  SPEEDZ=0${1//,/.};;
  3912.         [0-9]*([0-9.,]))    SPEEDZ=${1//,/.};;
  3913.         *)  false;;
  3914.     esac
  3915. }
  3916.  
  3917. #image generations
  3918. function imggenf
  3919. {
  3920.     typeset block_x;
  3921.    
  3922.     if ((LOCALAI))
  3923.     then    block_x="\"model\": \"$MOD_IMAGE\",";
  3924.     elif [[ $MOD_IMAGE = *dall-e*[3-9] ]]
  3925.     then    block_x="\"model\": \"$MOD_IMAGE\",
  3926. \"quality\": \"${OPTS_HD:-standard}\", ${OPTI_STYLE:+\"style\": \"$OPTI_STYLE\",}";
  3927.     fi
  3928.    
  3929.     BLOCK="{
  3930. \"prompt\": \"${*:?IMG PROMPT ERR}\",
  3931. \"size\": \"$OPTS\", $block_x
  3932. \"n\": ${OPTN:-1},
  3933. \"response_format\": \"$OPTI_FMT\"${BLOCK_USR:+,$NL}$BLOCK_USR
  3934. }"  #dall-e-2: n<=10, dall-e-3: n==1
  3935.     promptf
  3936. }
  3937.  
  3938. #image variations
  3939. function prompt_imgvarf
  3940. {
  3941.     curl -\# ${OPTV:+-Ss} ${FAIL} -L "${BASE_URL}${ENDPOINTS[EPN]}" \
  3942.         -H "Authorization: Bearer $OPENAI_API_KEY" \
  3943.         -F image="@$1" \
  3944.         -F response_format="$OPTI_FMT" \
  3945.         -F n="$OPTN" \
  3946.         -F size="$OPTS" \
  3947.         "${@:2}" \
  3948.         -o "$FILE"
  3949. }
  3950.  
  3951. #image edits+variations
  3952. function imgvarf
  3953. {
  3954.     typeset size prompt mask ;unset ARGS PNG32
  3955.     [[ -f ${1:?input PNG path required} ]]
  3956.  
  3957.     if command -v magick >/dev/null 2>&1
  3958.     then    if ! _is_pngf "$1" || ! _is_squaref "$1" || ! _is_rgbf "$1" ||
  3959.             {   ((${#} > 1)) && [[ ! -e $2 ]] ;} || [[ -n ${OPT_AT+force} ]]
  3960.         then  #not png or not square, or needs alpha
  3961.             if ((${#} > 1)) && [[ ! -e $2 ]]
  3962.             then  #needs alpha
  3963.                 _set_alphaf "$1"
  3964.             else  #no need alpha
  3965.                   #resize and convert (to png32?)
  3966.                 if _is_opaquef "$1"
  3967.                 then  #is opaque
  3968.                     ARGS="" PNG32="" ;((OPTV)) ||
  3969.                     printf '%s\n' 'Alpha not needed, opaque image' >&2
  3970.                 else  #is transparent
  3971.                     ARGS="-alpha set" PNG32="png32:" ;((OPTV)) ||
  3972.                     printf '%s\n' 'Alpha not needed, transparent image' >&2
  3973.                 fi
  3974.             fi
  3975.             _is_rgbf "$1" || {  PNG32="png32:" ;printf '%s\n' 'Image colour space is not RGB(A)' >&2 ;}
  3976.             img_convf "$1" $ARGS "${PNG32}${FILEIN}" &&
  3977.                 set -- "${FILEIN}" "${@:2}"  #adjusted
  3978.         else    ((OPTV)) ||
  3979.             printf '%s\n' 'No adjustment needed in image file' >&2
  3980.         fi ;unset ARGS PNG32
  3981.                        
  3982.         if [[ -f $2 ]]  #edits + mask file
  3983.         then    size=$(print_imgsizef "$1")
  3984.             if ! _is_pngf "$2" || ! _is_rgbf "$2" || {
  3985.                 [[ $(print_imgsizef "$2") != "$size" ]] &&
  3986.                 {   ((OPTV)) || printf '%s\n' 'Mask size differs' >&2 ;}
  3987.             } || _is_opaquef "$2" || [[ -n ${OPT_AT+true} ]]
  3988.             then    mask="${FILEIN%.*}_mask.png" PNG32="png32:" ARGS=""
  3989.                 _set_alphaf "$2"
  3990.                 img_convf "$2" -scale "$size" $ARGS "${PNG32}${mask}" &&
  3991.                     set  -- "$1" "$mask" "${@:3}"  #adjusted
  3992.             else    ((OPTV)) ||
  3993.                 printf '%s\n' 'No adjustment needed in mask file' >&2
  3994.             fi
  3995.         fi
  3996.     fi ;unset ARGS PNG32
  3997.    
  3998.     _chk_imgsizef "$1" || return 2
  3999.  
  4000.     ## one prompt  --  generations
  4001.     ## one file  --  variations
  4002.     ## one file (alpha) and one prompt  --  edits
  4003.     ## two files, (and one prompt)  --  edits
  4004.     if [[ -f $1 ]] && ((${#} > 1))  #img edits
  4005.     then    OPTII=1 EPN=9  #MOD=image-ed
  4006.         if ((${#} > 2)) && [[ -f $2 ]]
  4007.         then    prompt="${@:3}" ;set -- "${@:1:2}"
  4008.         elif ((${#} > 1)) && [[ ! -e $2 ]]
  4009.         then    prompt="${@:2}" ;set -- "${@:1:1}"
  4010.         fi
  4011.         [[ -f $2 ]] && set -- "${@:1:1}" -F mask="@$2"
  4012.     elif [[ -f $1 ]]  #img variations
  4013.     then    OPTII=1 EPN=4  #MOD=image-var
  4014.     fi
  4015.     [[ -n $prompt ]] && set -- "$@" -F prompt="$prompt"
  4016.  
  4017.     prompt_imgvarf "$@" &&
  4018.     prompt_imgprintf
  4019. }
  4020. #https://legacy.imagemagick.org/Usage/resize/
  4021. #https://imagemagick.org/Usage/masking/#alpha
  4022. #https://stackoverflow.com/questions/41137794/
  4023. #https://stackoverflow.com/questions/2581469/
  4024. #https://superuser.com/questions/1491513/
  4025. #
  4026. #set alpha flags for IM
  4027. function _set_alphaf
  4028. {
  4029.     unset ARGS PNG32
  4030.     if _has_alphaf "$1"
  4031.     then  #has alpha
  4032.         if _is_opaquef "$1"
  4033.         then  #is opaque
  4034.             ARGS="-alpha set -fuzz ${OPT_AT_PC:-0}% -transparent ${OPT_AT:-black}" PNG32="png32:"
  4035.             ((OPTV)) ||
  4036.             printf '%s\n' 'File has alpha but is opaque' >&2
  4037.         else  #is transparent
  4038.             ARGS="-alpha set" PNG32="png32:"
  4039.             ((OPTV)) ||
  4040.             printf '%s\n' 'File has alpha and is transparent' >&2
  4041.         fi
  4042.     else  #no alpha, is opaque
  4043.         ARGS="-alpha set -fuzz ${OPT_AT_PC:-0}% -transparent ${OPT_AT:-black}" PNG32="png32:"
  4044.         ((OPTV)) ||
  4045.         printf '%s\n' 'File has alpha but is opaque' >&2
  4046.     fi
  4047. }
  4048. #check if file ends with .png
  4049. function _is_pngf
  4050. {
  4051.     if [[ $1 != *.[Pp][Nn][Gg] ]]
  4052.     then    ((OPTV)) || printf '%s\n' 'Not a PNG image' >&2
  4053.         return 1
  4054.     fi ;return 0
  4055. }
  4056. #convert image
  4057. #usage: img_convf [in_file] [opt..] [out_file]
  4058. function img_convf
  4059. {
  4060.     if ((!OPTV))
  4061.     then    [[ $ARGS = *-transparent* ]] &&
  4062.         printf "${BWHITE}%-12s --${NC} %s\\n" "Transparent colour" "${OPT_AT:-black}" "Fuzz" "${OPT_AT_PC:-2}%" >&2
  4063.         sysmsgf 'Edit with ImageMagick?' '[Y/n] ' ''
  4064.         case "$(read_charf)" in [AaNnQq]|$'\e')     return 2;; esac
  4065.     fi
  4066.  
  4067.     if magick "$1" -background none -gravity center -extent 1:1 "${@:2}"
  4068.     then    if ((!OPTV))
  4069.         then    set -- "${@##png32:}" ;_openf "${@:${#}}"
  4070.             sysmsgf 'Confirm Edit?' '[Y/n] ' ''
  4071.             case "$(read_charf)" in [AaNnQq]|$'\e')     return 2;; esac
  4072.         fi
  4073.     else    false
  4074.     fi
  4075. }
  4076. #check for image alpha channel
  4077. function _has_alphaf
  4078. {
  4079.     typeset alpha
  4080.     alpha=$(magick identify -format '%A' "$1")
  4081.     [[ $alpha = [Tt][Rr][Uu][Ee] ]] || [[ $alpha = [Bb][Ll][Ee][Nn][Dd] ]]
  4082. }
  4083. #check if image is opaque
  4084. function _is_opaquef
  4085. {
  4086.     typeset opaque
  4087.     opaque=$(magick identify -format '%[opaque]' "$1")
  4088.     [[ $opaque = [Tt][Rr][Uu][Ee] ]]
  4089. }
  4090. #https://stackoverflow.com/questions/2581469/detect-alpha-channel-with-imagemagick
  4091. #check if image is square
  4092. function _is_squaref
  4093. {
  4094.     if (( $(magick identify -format '%[fx:(h != w)]' "$1") ))
  4095.     then    ((OPTV)) || printf '%s\n' 'Image is not square' >&2
  4096.         return 2
  4097.     fi
  4098. }
  4099. #print image size
  4100. function print_imgsizef
  4101. {
  4102.     magick identify -format "%wx%h\n" "$@"
  4103. }
  4104. #check file size of image
  4105. function _chk_imgsizef
  4106. {
  4107.     typeset chk_fsize
  4108.     if chk_fsize=$(wc -c <"$1" 2>/dev/null) ;(( (chk_fsize+500000)/1000000 >= 4))
  4109.     then    _warmsgf "Warning:" "Max image size is 4MB [file:$((chk_fsize/1000))KB]"
  4110.         (( (chk_fsize+500000)/1000000 < 5))
  4111.     fi
  4112. }
  4113. #is image colour space rgb?
  4114. function _is_rgbf
  4115. {
  4116.     [[ " $(magick identify -format "%r" "$@") " = *[Rr][Gg][Bb]* ]]
  4117. }
  4118.  
  4119. #embeds
  4120. function embedf
  4121. {
  4122.     BLOCK="{
  4123. $( ((MISTRALAI)) || echo "\"temperature\": $OPTT, $OPTP_OPT
  4124. \"max_tokens\": $OPTMAX, \"n\": $OPTN," )
  4125. \"model\": \"$MOD\", ${BLOCK_USR:+$NL}$BLOCK_USR
  4126. \"input\": \"${*:?INPUT ERR}\"
  4127. }"
  4128.     promptf 2>&1
  4129. }
  4130.  
  4131. function moderationf
  4132. {
  4133.     BLOCK="{
  4134. \"model\": \"$MOD\",
  4135. \"input\": \"${*:?INPUT ERR}\"${BLOCK_USR:+,$NL}$BLOCK_USR
  4136. }"
  4137.     _promptf
  4138. }
  4139.  
  4140. # Awesome-chatgpt-prompts
  4141. function awesomef
  4142. {
  4143.     typeset REPLY act_keys act_keys_n options glob act zh a l n
  4144.     [[ "$INSTRUCTION" = %* ]] && FILEAWE="${FILEAWE%%.csv}-zh.csv" zh=1
  4145.  
  4146.     set -- "$(trimf "${INSTRUCTION##[/%]}" "*( )" )";
  4147.     set -- "${1// /_}";
  4148.     FILECHAT="${FILECHAT%/*}/awesome.tsv"
  4149.     _cmdmsgf 'Awesome Prompts' "$1"
  4150.  
  4151.     if [[ ! -s $FILEAWE ]] || [[ $1 = [/%]* ]]  #second slash
  4152.     then    set -- "${1##[/%]}"
  4153.         if  if ((zh))
  4154.             then    ! { curl -\# -L ${FAIL} "$AWEURLZH" \
  4155.                 | jq '"act,prompt",(.[]|join(","))' \
  4156.                 | sed 's/,/","/' >"$FILEAWE" ;}  #json to csv
  4157.             else    ! curl -\# -L ${FAIL} "$AWEURL" -o "$FILEAWE"
  4158.             fi
  4159.         then    [[ -f $FILEAWE ]] && rm -- "$FILEAWE"
  4160.             return 1
  4161.         fi
  4162.     fi;
  4163.  
  4164.     #map prompts to indexes and get user selection
  4165.     act_keys=$(sed -e '1d; s/,.*//; s/^"//; s/"$//; s/""/\\"/g; s/[][()`*_]//g; s/ /_/g' "$FILEAWE")
  4166.     act_keys_n=$(wc -l <<<"$act_keys")
  4167.     case "$1" in
  4168.         list*|ls*|+([./%*?-]))  #list awesome keys
  4169.             {   pr -T -t -n:3 -W $COLUMNS -$(( (COLUMNS/80)+1)) || cat ;} <<<"$act_keys" >&2;
  4170.             return 210;;
  4171.     esac
  4172.  
  4173.     ((${#1}==1)) && glob='^';
  4174.     if test_dialogf
  4175.     then
  4176.         if ((${#1})) &&
  4177.         options=( $(_dialog_optf $(grep -i -e "${glob}${1//[ _-]/[ _-]}" <<<"${act_keys}" | sort) ) )
  4178.             ((!${#options[@]}))
  4179.         then  options=( $(_dialog_optf $(printf '%s\n' ${act_keys:-err} | sort) ) )
  4180.         fi
  4181.         REPLY=$(
  4182.           dialog --backtitle "Awesome Picker" --title "Select an Act" \
  4183.             --menu "The following acts are available:" 0 40 0 \
  4184.             -- "${options[@]}"  2>&1 >/dev/tty;
  4185.         ) || typeset NO_DIALOG=1;
  4186.         _clr_dialogf;
  4187.  
  4188.         for act in ${act_keys}
  4189.         do  ((++n))
  4190.             case "$REPLY" in "$act")  act=${n:-1}; break;; esac;
  4191.         done
  4192.     else
  4193.     echo >&2;
  4194.     set -- "${1:-%#}";
  4195.     while ! {   ((act && act <= act_keys_n)) ;}
  4196.     do  if ! act=$(grep -n -i -e "${glob}${1//[ _-]/[ _-]}" <<<"${act_keys}")
  4197.         then    _clr_ttystf;
  4198.             select act in ${act_keys}
  4199.             do  break
  4200.             done </dev/tty; act="$REPLY";
  4201.         elif act="$(cut -f1 -d: <<<"$act")"
  4202.             [[ ${act} = *$'\n'?* ]]
  4203.         then    while read l;
  4204.             do  ((++n));
  4205.                 for a in ${act};
  4206.                 do  ((n==a)) && printf '%d) %s\n' "$n" "$l" >&2;
  4207.                 done;
  4208.             done <<<"${act_keys}";
  4209.             printf '\n#? <enter> ' >&2
  4210.             _clr_ttystf; read -r -e act </dev/tty;
  4211.             printf '\n\n' >&2;
  4212.         fi ;set -- "$act"; glob= n= a= l=;
  4213.     done
  4214.     fi
  4215.  
  4216.     INSTRUCTION=$(sed -n -e 's/^[^,]*,//; s/^"//; s/"$//; s/""/"/g' -e "$((act+1))p" "$FILEAWE")
  4217.     ((CMD_CHAT)) ||
  4218.     if _clr_ttystf; ((OPTX))  #edit chosen awesome prompt
  4219.     then    INSTRUCTION=$(ed_outf "$INSTRUCTION") || exit
  4220.         printf '%s\n\n' "$INSTRUCTION" >&2 ;
  4221.     else    read_mainf -i "$INSTRUCTION" INSTRUCTION
  4222.         ((OPTCTRD)) && INSTRUCTION=$(trim_trailf "$INSTRUCTION" $'*([\r])')
  4223.     fi </dev/tty
  4224.     case "$INSTRUCTION" in ''|prompt|act)
  4225.         _warmsgf 'Err:' 'awesome-chatgpt-prompts fail'
  4226.         unset OPTAWE INSTRUCTION;return 1
  4227.     esac;
  4228. }
  4229.  
  4230. # Custom prompts
  4231. function custom_prf
  4232. {
  4233.     typeset file filechat name template list msg new skip ret
  4234.     filechat="$FILECHAT"
  4235.     FILECHAT="${FILECHAT%%.[Tt][SsXx][VvTt]}.pr"
  4236.     case "$INSTRUCTION" in  #lax syntax  -S.prompt.
  4237.         *[!.,][.])  INSTRUCTION=".${INSTRUCTION%%[.]}";;
  4238.         *[!.,][,])  INSTRUCTION=",${INSTRUCTION%%[,]}";;
  4239.     esac
  4240.  
  4241.     #options
  4242.     case "${INSTRUCTION:0:32}"  in
  4243.         +([.,])@(list|\?)|[.,]+([.,/*?-]))
  4244.             INSTRUCTION= list=1
  4245.             _cmdmsgf 'Prompt File' 'LIST'
  4246.             ;;
  4247.         ,,?*) #edit template prompt file
  4248.             INSTRUCTION="${INSTRUCTION##[.,]*( )}"
  4249.             template=1 skip=0 msg='EDIT TEMPLATE'
  4250.             ;;
  4251.         ,?*)  #single-shot edit
  4252.             INSTRUCTION="${INSTRUCTION##[.,]*( )}"
  4253.             skip=0 msg='LOAD (single-shot edit)'
  4254.             ;;
  4255.         [.,]) #pick prompt file
  4256.             INSTRUCTION=
  4257.             ;;
  4258.     esac
  4259.    
  4260.     #set skip confirmation (catch ./file)
  4261.     [[ $INSTRUCTION = .* ]] && [[ $INSTRUCTION != .?(.)/*([!/]) ]] \
  4262.     && INSTRUCTION="${INSTRUCTION##[.,]}" skip=${skip:-1}
  4263.    
  4264.     [[ ! -f $INSTRUCTION ]] && [[ $INSTRUCTION != ./*([!/]) ]] \
  4265.     && INSTRUCTION="${INSTRUCTION##[.,]}"
  4266.    
  4267.     name=$(trim_leadf "$INSTRUCTION" '*( )')
  4268.  
  4269.     #set source prompt file
  4270.     if [[ -f $name ]]
  4271.     then    file="$name"
  4272.     elif [[ $name = */* ]] ||
  4273.         ! file=$(
  4274.             SESSION_LIST=$list SGLOB='[Pp][Rr]' EXT='pr' \
  4275.             session_globf "$name")
  4276.     then    template=1
  4277.         file=$(
  4278.             SGLOB='[Pp][Rr]' EXT='pr' \
  4279.             session_name_choosef "$name")
  4280.         [[ -f $file ]] && msg=${msg:-LOAD} || msg=CREATE
  4281.     fi
  4282.     ((list)) && {   printf '%s\n' "$file"; exit ;}
  4283.  
  4284.     case "$file" in
  4285.         [Cc]urrent|.)   file="${FILECHAT}";;
  4286.         [Aa]bort|[Cc]ancel|[Ee]xit|[Qq]uit)     return 201;;
  4287.     esac
  4288.     if [[ -f "$file" ]]
  4289.     then    msg=${msg:-LOAD}    INSTRUCTION=$(<"$file")
  4290.     else    msg=${msg:-CREATE}  INSTRUCTION=  template=1 new=1
  4291.     fi
  4292.  
  4293.     FILECHAT="${filechat%/*}/${file##*/}"
  4294.     FILECHAT="${FILECHAT%%.[Pp][Rr]}.tsv"
  4295.     if ((OPTHH>1 && OPTHH<=4))
  4296.     then    session_sub_fifof "$FILECHAT"
  4297.         return
  4298.     fi
  4299.     ((CMD_CHAT)) ||
  4300.     _sysmsgf 'Hist   File:' "${FILECHAT/"$HOME"/"~"}"
  4301.     _sysmsgf 'Prompt File:' "${file/"$HOME"/"~"}"
  4302.     _cmdmsgf "${new:+New }Prompt Cmd" " ${msg}"
  4303.  
  4304.     if {    [[ $msg = *[Cc][Rr][Ee][Aa][Tt][Ee]* ]] && INSTRUCTION="$*" ret=200 ;} ||
  4305.         [[ $msg = *[Ee][Dd][Ii][Tt]* ]] || (( (MTURN+CHAT_ENV) && OPTRESUME!=1 && skip==0))
  4306.     then
  4307.         _clr_ttystf;
  4308.         if ((OPTX))  #edit prompt
  4309.         then    INSTRUCTION=$(ed_outf "$INSTRUCTION") || exit
  4310.             printf '%s\n\n' "$INSTRUCTION" >&2 ;
  4311.         else    _printbf '>'; read_mainf -i "$INSTRUCTION" INSTRUCTION;
  4312.             ((OPTCTRD)) && INSTRUCTION=$(trim_trailf "$INSTRUCTION" $'*([\r])')
  4313.         fi </dev/tty
  4314.  
  4315.         if ((template))  #push changes to file
  4316.         then    printf '%s' "$INSTRUCTION"${INSTRUCTION:+$'\n'} >"$file"
  4317.             [[ -f "$file" && ! -s "$file" ]] && { rm -v -- "$file" || rm -- "$file" ;} >&2
  4318.         fi
  4319.         if [[ -z $INSTRUCTION ]]
  4320.         then    _warmsgf 'Err:' 'Custom prompts fail'
  4321.             return 1
  4322.         fi
  4323.     fi
  4324.     return ${ret:-0}
  4325. } #exit codes: 1) err;  200) create new pr;     201) abort.
  4326.  
  4327. #set an empty output filename
  4328. function set_fnamef
  4329. {
  4330.     typeset f n m ext fname;
  4331.     ext=${1##*.} f=$1;
  4332.  
  4333.     if [[ -s $1 ]]
  4334.     then    n=0 m=0  #set a filename for output
  4335.         for fname in "${1%.*}"*
  4336.         do  fname=${fname##*/} fname=${fname%.*}
  4337.             fname=${fname%%-*([0-9])} fname=${fname##*[!0-9]}
  4338.             ((m>fname)) || ((m=fname+1))
  4339.         done
  4340.         set -- "${1%.*}"; set -- "${1%%?(-)*([0-9])}";
  4341.         while [[ -s ${1}${m}.${ext} ]]; do  ((++m)); done;
  4342.         set -- "${1}${m}.${ext}";
  4343.     fi
  4344.     printf '%s\n' "${1:-${f}}"; ((${#1}));
  4345. }
  4346.  
  4347. # Set the clipboard command
  4348. function set_clipcmdf
  4349. {
  4350.     ((${#CLIP_CMD})) ||
  4351.     if command -v termux-clipboard-set
  4352.     then    CLIP_CMD='termux-clipboard-set'
  4353.     elif command -v pbcopy
  4354.     then    CLIP_CMD='pbcopy'
  4355.     elif command -v xsel
  4356.     then    CLIP_CMD='xsel -b'
  4357.     elif command -v xclip
  4358.     then    CLIP_CMD='xclip -selection clipboard'
  4359.     else    CLIP_CMD='false'
  4360.     fi >/dev/null 2>&1
  4361. }
  4362.  
  4363. # Set the audio play command
  4364. function set_playcmdf
  4365. {
  4366.     ((${#PLAY_CMD})) && return;
  4367.  
  4368.     if [[ -n $TERMUX_VERSION ]]
  4369.     then    is_amodelf "$MOD" && typeset OPTV=2;
  4370.         set_termuxpulsef ||
  4371.         if command -v play-audio
  4372.         then    PLAY_CMD='play-audio';
  4373.             return 0;
  4374.         elif command -v termux-media-player
  4375.         then    PLAY_CMD='termux-media-player play';
  4376.             return 0;
  4377.         fi >/dev/null 2>&1
  4378.     fi
  4379.  
  4380.     if command -v mpv
  4381.     then    PLAY_CMD='mpv --no-video --vo=null'
  4382.     #--profile=low-latency  --vo=xv  #low latency
  4383.     elif command -v play  #sox
  4384.     then    PLAY_CMD='play'
  4385.     #--input-buffer=40000
  4386.     #https://sourceforge.net/p/sox/patches/124/
  4387.     elif command -v cvlc
  4388.     then    PLAY_CMD='cvlc --play-and-exit --no-loop --no-repeat'
  4389.     #--network-caching=150 --sout-mux-caching=50 --live-caching=100 --clock-jitter=0 --file-caching=0 --no-audio-time-stretch
  4390.     elif command -v ffplay
  4391.     then    PLAY_CMD='ffplay -nodisp -hide_banner -autoexit'
  4392.     #-fflags +nobuffer -flags low_delay -framedrop
  4393.     #-fflags discardcorrupt, too aggressive (breaks audio-video sync)
  4394.     elif command -v afplay  #macos
  4395.     then    PLAY_CMD='afplay'
  4396.     else    PLAY_CMD='false'
  4397.     fi >/dev/null 2>&1
  4398. }
  4399.  
  4400. #set audio recorder command
  4401. function set_reccmdf
  4402. {
  4403.     ((${#REC_CMD})) && return;
  4404.  
  4405.     if [[ -n $TERMUX_VERSION ]]
  4406.     then    is_amodelf "$MOD" && typeset OPTV=2;
  4407.         set_termuxpulsef ||
  4408.         if command -v termux-microphone-record
  4409.         then    REC_CMD='termux-microphone-record -r 16000 -c 1 -l 0 -f';
  4410.             is_amodelf "$MOD" ||  #termux-mic encodes m4a only
  4411.             FILEINW="${FILEINW%.*}.m4a";  #encoder aac
  4412.             return 0;
  4413.         fi >/dev/null 2>&1
  4414.     fi
  4415.  
  4416.     if command -v sox
  4417.     then    REC_CMD='sox -d -r 16000 -c 1'
  4418.     #--input-buffer=40000, silence 1 0.50 0.1%
  4419.     #https://sourceforge.net/p/sox/patches/124/
  4420.     elif command -v arecord  #alsa utils
  4421.     then    REC_CMD='arecord -f S16_LE -r 16000 -c 1 -i'
  4422.     #-B --buffer-time=#, --buffer-size=#
  4423.     elif command -v ffmpeg
  4424.     then    case "${OSTYPE:-$(uname -a)}" in
  4425.             *[Dd]arwin*)
  4426.             REC_CMD='ffmpeg -hide_banner -f avfoundation -i ":1" -ar 16000 -ac 1 -y';;
  4427.             *)  REC_CMD='ffmpeg -hide_banner -f alsa -i pulse -ar 16000 -ac 1 -y';;
  4428.         esac;
  4429.     else    REC_CMD='false'
  4430.     fi >/dev/null 2>&1
  4431. }
  4432.  
  4433. #check and set termux pulseaudio configuration
  4434. function set_termuxpulsef
  4435. {
  4436.     ((OPTV>1)) || return;
  4437.     if case "$OPT_SLES" in
  4438.         [Yy])   :;;
  4439.         [Nn])   return 1;;
  4440.         *)  command -v pulseaudio >/dev/null 2>&1 &&
  4441.             command -v pactl >/dev/null 2>&1;;
  4442.        esac;
  4443.     then
  4444.         case "$(pactl list modules 2>&1)" in
  4445.           *module-sles-source*)  :;;
  4446.           *module-sles-sink*|*)
  4447.             _warmsgf 'Pulseaudio:' "\`module-sles-source' not active";
  4448.             _warmsgf 'Pulseaudio:' "configure \`pulseaudio' with \`module-sles-source'";
  4449.             _sysmsgf 'See' "<https://gitlab.com/fenixdragao/shellchatgpt#termux-users>";
  4450.  
  4451.             ((${#OPT_SLES})) ||
  4452.               printf '\n%s  [N/y] \a' "Enable \`module-sles-source'?" >&2;
  4453.             case "${OPT_SLES:-$(read_charf -t 4||echo >&2)}" in
  4454.               [YySs]|[$' \t'])
  4455.                 OPT_SLES=y;
  4456.                 _printbf "pulse";
  4457.                 pulseaudio -k; sleep 0.1;
  4458.                 pulseaudio -L "module-sles-source" -D &
  4459.                 disown $!; sleep 0.2;
  4460.                 _printbf "     ";
  4461.                 ;;
  4462.               [NnAaQq]|$'\e'|*)
  4463.                 OPT_SLES=n;
  4464.                 false;
  4465.                 ;;
  4466.             esac;
  4467.             ;;
  4468.         esac;
  4469.     else
  4470.           sysmsgf 'Tip:' "install \`pulseaudio', \`sox', and \`ffmpeg' for enhanced audio";
  4471.           OPT_SLES=n;
  4472.           false;
  4473.     fi
  4474. }
  4475. #https://madskjeldgaard.dk/posts/sox-tutorial-sox-on-android/
  4476.  
  4477. #append to shell hist list
  4478. function shell_histf
  4479. {
  4480.     [[ ${*} = *[!$IFS]* ]] || return
  4481.     history -s -- "$*"
  4482. }
  4483. #history file must start with a timestamp (# plus Unix timestamp) or else
  4484. #the history command will still split on each line of a multi-line command
  4485. #https://askubuntu.com/questions/1133015/
  4486. #https://lists.gnu.org/archive/html/bug-bash/2011-02/msg00025.html
  4487.  
  4488. #print checksum
  4489. function cksumf
  4490. {
  4491.     [[ -f "$1" ]] && wc -l -- "$@"
  4492. }
  4493.  
  4494. #list session files in cache dir
  4495. function session_listf
  4496. {
  4497.     SESSION_LIST=1 session_globf "$@"
  4498. }
  4499. #pick session files by globbing cache dir
  4500. function session_globf
  4501. {
  4502.     typeset REPLY file glob sglob ext ok options
  4503.     sglob="${SGLOB:-[Tt][Ss][Vv]}" ext="${EXT:-tsv}"
  4504.  
  4505.     [[ ! -f "$1" ]] || return
  4506.     case "$1" in
  4507.         [Nn]ew)     return 2;;
  4508.         [Cc]urrent|.)   printf '%s' "${FILECHAT:-$1}"; return;;
  4509.     esac
  4510.  
  4511.     cd -- "${CACHEDIR}"
  4512.     glob="${1%%.${sglob}}" glob="${glob##*/}"
  4513.     #input is exact filename, or ends with extension wo whitespaces?
  4514.     [[ -f "${glob}".${ext} ]] || [[ "$1" = *?.${sglob} && "$1" != *\ * ]] \
  4515.     || set -- *${glob}*.${sglob}  #set the glob
  4516.    
  4517.     if ((SESSION_LIST))
  4518.     then    ls -- "$@";
  4519.         return 0;
  4520.     fi
  4521.  
  4522.     if ((${#} >1)) && [[ "$glob" != *[$IFS]* ]]
  4523.     then    _clr_ttystf;
  4524.         if test_dialogf
  4525.         then    options=( $(_dialog_optf 'current' 'new' "${@%%.${sglob}}") )
  4526.             file=$(
  4527.               dialog --backtitle "Selection Menu" --title "$([[ $ext = *[Tt][Ss][Vv] ]] && echo History File || echo Prompt) Selection" \
  4528.                 --menu "Choose one of the following:" 0 40 0 \
  4529.                 -- "${options[@]}"  2>&1 >/dev/tty;
  4530.             ) || { file=abort; typeset NO_DIALOG=1 ;}
  4531.             _clr_dialogf;
  4532.         else
  4533.             printf '# Pick file [.%s]:\n' "${ext}" >&2
  4534.             select file in 'current' 'new' 'abort' "${@%%.${sglob}}"
  4535.             do  break
  4536.             done </dev/tty
  4537.         fi
  4538.         file="${file:-$REPLY}"
  4539.     else    file="${1}"
  4540.     fi
  4541.  
  4542.     case "$file" in
  4543.         [Cc]urrent|.|'')
  4544.             file="${FILECHAT##*/}"
  4545.             ;;
  4546.         [Nn]ew) session_name_choosef
  4547.             return
  4548.             ;;
  4549.         [Aa]bort|[Cc]ancel|[Ee]xit|[Qq]uit)
  4550.             echo abort; echo '[abort]' >&2
  4551.             return 201
  4552.             ;;
  4553.         "$REPLY")
  4554.             ok=1
  4555.             ;;
  4556.     esac
  4557.  
  4558.     file="${CACHEDIR%%/}/${file:-${*:${#}}}"
  4559.     file="${file%%.${sglob}}.${ext}"
  4560.     [[ -f $file || $ok -gt 0 ]] && printf '%s' "${file}"
  4561. }
  4562. #set tsv filename based on input
  4563. function session_name_choosef
  4564. {
  4565.     typeset fname new print_name sglob ext var item
  4566.     fname="$1" sglob="${SGLOB:-[Tt][Ss][Vv]}" ext="${EXT:-tsv}"
  4567.     ((OPTEXIT>1)) && return
  4568.     case "$fname" in [Nn]ew|*[N]ew.${sglob})    set --; fname= ;; esac
  4569.     while
  4570.         fname="${fname%%\/}"
  4571.         fname="${fname%%.${sglob}}"
  4572.         fname="${fname/\~\//"$HOME"\/}"
  4573.         case "pr" in ${sglob}) item="prompt";;
  4574.             *) item="session";;
  4575.         esac;
  4576.        
  4577.         if [[ -d "$fname" ]]
  4578.         then    _warmsgf 'Err:' 'Is a directory'
  4579.             fname="${fname%%/}"
  4580.         (   cd "$fname" &&
  4581.             ls -- "${fname}"/*.${sglob} ) >&2 2>/dev/null
  4582.             shell_histf "${fname}${fname:+/}"
  4583.             unset fname
  4584.         fi
  4585.  
  4586.         if [[ ${fname} != *[!$IFS]* ]]
  4587.         then
  4588.             if test_dialogf
  4589.             then    fname=$(
  4590.                 dialog --backtitle "${item} manager" \
  4591.                 --title "new ${item} file" \
  4592.                 --inputbox "enter new ${item} name" 8 32  2>&1 >/dev/tty )
  4593.                 _clr_dialogf;
  4594.             else
  4595.                 _sysmsgf "New ${item} name <enter/abort>:" \
  4596.                 _clr_ttystf; read -r -e -i "$fname" fname </dev/tty;
  4597.             fi;
  4598.             case "${fname}" in \~\/*)   fname="$HOME/${fname:2}";; esac;
  4599.             fname=${fname%%.${sglob}} fname=${fname//[ \<\>\*\;:,?!]/_} fname=${fname//__/_};
  4600.         fi
  4601.  
  4602.         if [[ -d "$fname" ]]
  4603.         then    unset fname
  4604.             continue
  4605.         fi
  4606.  
  4607.         if [[ $fname != *?/?* ]] && [[ ! -e "$fname" ]]
  4608.         then    fname="${CACHEDIR%%/}/${fname:-abort}"
  4609.         fi; fname="${fname:-abort}"
  4610.         if [[ ! -f "$fname" ]]
  4611.         then    fname="${fname}.${ext}"
  4612.             new=" new"
  4613.         fi
  4614.  
  4615.         if [[ $fname = "$FILECHAT" ]]
  4616.         then    print_name=current
  4617.         else    print_name="${fname/"$HOME"/"~"}"
  4618.         fi
  4619.         if [[ ! -e $fname ]]
  4620.         then    case "$fname" in *[N]ew.${sglob})   :;; *[Aa]bort.${sglob}|*[Cc]ancel.${sglob}|*[Ee]xit.${sglob}|*[Qq]uit.${sglob})     echo abort; echo '[abort]' >&2; return 201;; esac
  4621.             if test_dialogf
  4622.             then    dialog --colors --backtitle "${item} manager" \
  4623.                 --title "confirm${new} ${item} file?" \
  4624.                 --yesno " \\Zb${new:+\\Z1}${print_name}\\Zn" 8 $((${#print_name}+6))  >/dev/tty
  4625.                 case $? in
  4626.                     -1|1|5|255) var=abort;;
  4627.                 esac;
  4628.                 _clr_dialogf;
  4629.             else
  4630.                 _sysmsgf "Confirm${new}? [Y]es/[n]o/[a]bort:" "${print_name} " '' ''
  4631.                 var=$(read_charf)
  4632.             fi
  4633.             case "$var" in [AaQq]|$'\e'|*[Aa]bort|*[Aa]bort.${sglob})   echo abort; echo '[abort]' >&2; return 201;; [NnOo])    :;; *)  false;; esac
  4634.         else    false
  4635.         fi
  4636.     do  unset fname new print_name
  4637.     done
  4638.    
  4639.     if [[ ! -e ${fname} ]]
  4640.     then    [[ ${fname} = *.[Pp][Rr] ]] \
  4641.         && printf '(new prompt file)\n' >&2 \
  4642.         || printf '(new hist file)\n' >&2
  4643.     fi
  4644.     [[ ${fname} = *[!$IFS]* ]] && printf '%s\n' "$fname"
  4645. }
  4646. #pick and print a session from hist file
  4647. function session_sub_printf
  4648. {
  4649.     typeset REPLY reply file time token string buff buff_end index regex skip sopt copt ok m n
  4650.     typeset -a SPIN_CHARS=("${SPIN_CHARS0[@]}");
  4651.     sopt= copt= ok= buff= buff_end=;
  4652.     [[ -s ${file:=$1} ]] || return; [[ $file = */* ]] || [[ ! -e "./$file" ]] || file="./$file";
  4653.     FILECHAT_OLD="$file" regex="${REGEX%%${NL}*}";
  4654.  
  4655.     while ((skip)) || IFS= read -r
  4656.     do  _spinf; skip= ;
  4657.         if [[ ${REPLY} = *([$IFS])\#* ]] && ((OPTHH<3))
  4658.         then    continue
  4659.         elif [[ ${REPLY} = *[Bb][Rr][Ee][Aa][Kk]*([$IFS]) ]]
  4660.         then
  4661. for ((m=1;m<2;++m))
  4662. do  _spinf  #grep session with user regex
  4663.             ((skip)) && skip= ||
  4664.             if ((${regex:+1}))
  4665.             then    if ((!ok))
  4666.                 then    [[ $regex = -?* ]] && sopt="${regex%% *}" regex="${regex#* }"
  4667.                     grep $sopt "${regex}" <<<" " >/dev/null  #test user syntax
  4668.                     (($?<2)) || return 1; ((OPTK)) || copt='--color=always';
  4669.                    
  4670.                     _sysmsgf 'Regex': "\`${regex}'";
  4671.                     if ! grep -q $copt $sopt "${regex}" "$file" 1>&2 2>/dev/null;  #grep regex match in current file
  4672.                     then    grep -n -o $copt $sopt "${regex}" "${file%/*}"/*"${file##*.}" 1>&2 2>/dev/null  #grep other files
  4673.                         _warmsgf 'Err:' "No match at \`${file/"$HOME"/"~"}'";
  4674.                         buff= ; break 2;
  4675.                     fi; ok=1;
  4676.                 fi;
  4677.                 grep $copt $sopt "${regex}" < <(_unescapef "$(cut -f1,3- -d$'\t' <<<"$buff")") >&2 &&
  4678.                   printf '%s\n' '---' >&2 || buff= ;
  4679.             else
  4680.                 for ((n=0;n<12;++n))
  4681.                 do  _spinf
  4682.                     IFS=$'\t' read -r time token string || break
  4683.                     buff_end="${buff_end}${buff_end:+$NL}${string:1: ${#string}-2}"
  4684.                 done <<<"${buff}"
  4685.             fi
  4686.            
  4687.             ((OPTHH && OPTV)) && break 2;  #IPC#
  4688.             ((${#buff})) && {
  4689.               if ((${#buff_end}))
  4690.               then  ((${#buff_end}>640)) && ((index=${#buff_end}-640)) || index= ;
  4691.                 printf -- '===\n%s%s\n---\n' "${index:+[..]}" "$(_unescapef "${buff_end:${index:-0}}")" | foldf >&2;
  4692.               fi
  4693.               ((OPTPRINT)) && break 2;
  4694.  
  4695.               if ((${regex:+1}))
  4696.               then  _sysmsgf "Correct session?" '[Y/n/p/r/a] ' ''
  4697.               else  _sysmsgf "Tail of the correct session?" '[Y]es, [n]o, [p]rint, [r]egex, [a]bort ' ''
  4698.               fi;
  4699.               reply=$(read_charf);
  4700.               case "$reply" in
  4701.                 []GgSsRr/?:\;-]|[$' \t']) _sysmsgf 'grep:' '<-opt> <regex> <enter>';
  4702.                     _clr_ttystf;
  4703.                     read -r -e -i "${regex:-${reply//[!-]}}" regex </dev/tty;
  4704.                     skip=1 ok= ;
  4705.                     continue 2;
  4706.                     ;;
  4707.                 [Pp])   _unescapef "\\n\\n$(sed -e $'s/^.*\t//' -e 's/^"//; s/"$/\n/' <<<"${buff:-err}")\\n---\\n" >&2;
  4708.                     skip=1 m=0 buff_end= ;
  4709.                     continue 1;
  4710.                     ;;
  4711.                 [NnOo]|$'\e') ((${regex:+1})) && printf '%s\n\n' '---' >&2;
  4712.                     false;
  4713.                     ;;
  4714.                 [AaQq]) echo '[abort]' >&2;
  4715.                     return 201;
  4716.                     ;;
  4717.                 *)  ((${regex:+1})) && printf '%s\n' '---' >&2;
  4718.                     break 2;
  4719.                     ;;
  4720.             esac
  4721.             }
  4722. done
  4723.             REPLY= reply= time= token= string= buff= buff_end= index= m= n=
  4724.             continue
  4725.         fi
  4726.         buff="${REPLY}"${buff:+$'\n'}"${buff}"
  4727.     done < <(   tac -- "$file" && {
  4728.             ((OPTPRINT+OPTHH)) || _warmsgf '(end of hist file)' ;}
  4729.             echo BREAK;
  4730.         ); _printbf ' '
  4731.     [[ -n ${buff} ]] && printf '%s\n' "$buff"
  4732. }
  4733. #copy session to another session file, print destination filename
  4734. function session_copyf
  4735. {
  4736.     typeset src dest buff
  4737.    
  4738.     ((${#}==1)) && [[ "$1" = +([!\ ])[\ ]+([!\ ]) ]] && set -- $@  #filename with spaces
  4739.  
  4740.     _sysmsgf 'Source hist file: ' '' ''
  4741.     if ((${#}==1)) && [[ "$1" != [Cc]urrent && "$1" != . ]]
  4742.     then    src=${FILECHAT}; echo "${src:-err}" >&2
  4743.     else    src="$(session_globf "${@:1:1}" || session_name_choosef "${@:1:1}")"; echo "${src:-err}" >&2
  4744.         set -- "${@:2:1}"
  4745.     fi; case "$src" in [Aa]bort|[Cc]ancel|[Ee]xit|[Qq]uit)  echo '[abort]' >&2; return 201;; esac
  4746.     _sysmsgf 'Destination hist file: ' '' ''
  4747.     dest="$(session_globf "$@" || session_name_choosef "$@")"; echo "${dest:-err}" >&2
  4748.     dest="${dest:-$FILECHAT}"; case "$dest" in [Aa]bort|[Cc]ancel|[Ee]xit|[Qq]uit)  echo '[abort]' >&2; return 201;; esac
  4749.  
  4750.     buff=$(session_sub_printf "$src") \
  4751.     && if [[ -f "$dest" ]] ;then    [[ "$(<"$dest")" != *"${buff}" ]] || return 0 ;fi \
  4752.     && { FILECHAT="${dest}" INSTRUCTION_OLD= INSTRUCTION= cmd_runf /break 2>/dev/null;
  4753.          FILECHAT="${dest}" _break_sessionf; OLD_DEST="${dest}";
  4754.          #check if dest is the same as current
  4755.          [[ "$dest" = "$FILECHAT" ]] && BREAK_SET= MAIN_LOOP= HIST_LOOP= TOTAL_OLD= MAX_PREV= ;} \
  4756.     && _sysmsgf 'SESSION FORK' \
  4757.     && printf '%s\n' "$buff" >> "$dest" \
  4758.     && printf '%s\n' "$dest"
  4759. }
  4760. #create or copy a session, search for and change to a session file.
  4761. function session_mainf
  4762. {
  4763.     typeset name file optsession arg break msg
  4764.     typeset -a args
  4765.     name="${*}"               ;((${#name}<512)) || return
  4766.     name=$(trimf "${name}" "*([$IFS])") ;[[ $name = [/!]* ]] || return
  4767.     name="${name##?([/!])*([$IFS])}"
  4768.  
  4769.     case "${name}" in
  4770.         #list files: /list [awe|pr|all|session]
  4771.         list*|ls*)
  4772.             name="${name##@(list|ls)*([$IFS])}"
  4773.             case "$name" in
  4774.                 [Aa]wesome|[Aa]we)
  4775.                     INSTRUCTION=/list awesomef;
  4776.                     return 0;;  #e:210
  4777.                 [Pp][Rr]|[Pp]rompt|[Pp]rompts)
  4778.                     typeset SGLOB='[Pp][Rr]' EXT='pr' name= msg=Prompt;;  #duplicates opt `-S .list` fun
  4779.                 [Aa]ll|[Ee]verything|[Aa]nything|+([./*?-]))
  4780.                     typeset SGLOB='*' EXT='*' name= msg=All;;
  4781.                 [Tt][Ss][Vv]|[Ss]ession|[Ss]essions|*)
  4782.                     name= msg=Session;;
  4783.             esac
  4784.             _cmdmsgf "$msg Files" $'list\n'
  4785.             session_listf "$name"; ((OPTEXIT>1)) && exit;
  4786.             return 0;
  4787.             ;;
  4788.         #fork current session to [dest_hist]: /fork
  4789.         fork*|f\ *)
  4790.             _cmdmsgf 'Session' 'fork'
  4791.             optsession=4 ;set -- "$*"
  4792.             set -- "${1##*([/!])@(fork|f)*([$IFS])}"
  4793.             set -- current "${1/\~\//"$HOME"\/}"
  4794.             ;;
  4795.         #search for and copy session to tail: /sub [regex]
  4796.         sub*|grep*)     set -- current current
  4797.             REGEX="${name##@(sub|grep)*([$IFS])}" optsession=3
  4798.             unset name
  4799.             ;;
  4800.         #copy session from hist option: /copy
  4801.         copy*|cp\ *|c\ *)
  4802.             _cmdmsgf 'Session' 'copy'
  4803.             optsession=3
  4804.             set -- "${1##*([/!])@(copy|cp|c)*([$IFS])}" "${@:2}" #two args
  4805.             set -- "${@/\~\//"$HOME"\/}"
  4806.             ;;
  4807.         #change to, or create a hist file session
  4808.         #break session: //
  4809.         [/!]*)  if [[ "$name" != /[!/]*/* ]] || [[ "$name" = [/!]/*/* ]]
  4810.             then    optsession=2 break=1
  4811.                 name="${name##[/!]}"
  4812.             fi;;
  4813.     esac
  4814.    
  4815.     name="${name##@(session|sub|grep|[Ss])*([$IFS])}"
  4816.     name="${name/\~\//"$HOME"\/}"
  4817.  
  4818.     #del unused positional args
  4819.     args=("$@") ;set --
  4820.     for arg in "${args[@]}"
  4821.     do  [[ ${arg} = *[!$IFS]* ]] && set -- "$@" "$arg"
  4822.     done
  4823.  
  4824.     #print hist option
  4825.     if ((OPTHH>1 && OPTHH<=4))
  4826.     then    session_sub_fifof "$name"
  4827.         return
  4828.     #copy/fork session to destination
  4829.     elif ((optsession>2))
  4830.     then
  4831.         session_copyf "$@" >/dev/null || unset file
  4832.         [[ "${OLD_DEST}" = "${FILECHAT}" ]] &&  #check if target is the same as current
  4833.         INSTRUCTION_OLD=${GINSTRUCTION:-${INSTRUCTION:-$INSTRUCTION_OLD}} INSTRUCTION= GINSTRUCTION= OPTRESUME=1;
  4834.         unset OLD_DEST;
  4835.     #change to hist file
  4836.     else
  4837.         #set source session file
  4838.         if [[ -f $name ]]
  4839.         then    file="$name"
  4840.         elif [[ $name = */* ]] ||
  4841.             ! file=$(session_globf "$name")
  4842.         then
  4843.             file=$(session_name_choosef "${name}")
  4844.         fi
  4845.  
  4846.         case "$file" in
  4847.             [Cc]urrent|.)   file="${FILECHAT}";;
  4848.             [Aa]bort|[Cc]ancel|[Ee]xit|[Qq]uit)     return 201;;
  4849.         esac
  4850.         [[ -f "$file" ]] && msg=change || msg=create
  4851.         ((!MAIN_LOOP&&!OPTRESUME)) && break=1  #1st invocation
  4852.         _cmdmsgf 'Session' "$msg ${break:+ + session break}"
  4853.  
  4854.         #break session?
  4855.         if [[ -f "${file:-$FILECHAT}" ]]
  4856.         then
  4857.             if ((OPTRESUME!=1)) && {  ((break)) || {
  4858.                 _sysmsgf 'Break session?' '[N/ys] ' ''
  4859.                 case "$(read_charf)" in [YySs])     :;; $'\e'|*)    false ;;esac
  4860.                 } ;}
  4861.             then  FILECHAT="${file:-$FILECHAT}" cmd_runf /break;
  4862.                   unset MAIN_LOOP HIST_LOOP TOTAL_OLD MAX_PREV;
  4863.             else  #print snippet of tail session
  4864.                   [[ ${file:-$FILECHAT} = "$FILECHAT" ]] || ((OPTV+BREAK_SET+break)) ||
  4865.                     OPTPRINT=1 session_sub_printf "${file:-$FILECHAT}" >/dev/null
  4866.             fi
  4867.         fi
  4868.     fi
  4869.  
  4870.     [[ ${file:-$FILECHAT} = "$FILECHAT" ]] && msg=Current || msg=Change;
  4871.         FILECHAT="${file:-$FILECHAT}"; _sysmsgf "History $msg:" "${FILECHAT/"$HOME"/"~"}"$'\n';
  4872.     ((OPTEXIT>1)) && exit;
  4873.         return 0
  4874. }
  4875. function session_sub_fifof
  4876. {
  4877.     if [[ -f "$*" ]]
  4878.     then    FILECHAT_OLD="$*"
  4879.         session_sub_printf "${*}"
  4880.     else    FILECHAT_OLD="$(session_globf "${*:-*}")" &&
  4881.         session_sub_printf "$FILECHAT_OLD"
  4882.     fi  >"$FILEFIFO"
  4883.     FILECHAT="$FILEFIFO"
  4884. }
  4885.  
  4886. function cleanupf
  4887. {
  4888.     ((${#PIDS[@]})) || return 0
  4889.     for pid in ${PIDS[@]}
  4890.         do  kill -- $pid 2>/dev/null;
  4891.         done;
  4892.     wait ${PIDS[@]}  &>/dev/null;
  4893. }
  4894.  
  4895. #ollama fun
  4896. function set_ollamaf
  4897. {
  4898.     function list_modelsf
  4899.     {
  4900.         if ((${#1}))
  4901.         then    curl -\# -L "${OLLAMA_BASE_URL}/api/show" -d "{\"name\": \"$1\"}" -o "$FILE" &&
  4902.             { jq . "$FILE" || ! _warmsgf 'Err' ;}; echo >&2;
  4903.             ollama show "$1" --modelfile  2>/dev/null;
  4904.         else    {
  4905.               printf '\nName\tFamily\tFormat\tParam\tQLvl\tSize\tModification\n'
  4906.               curl -s -L "${OLLAMA_BASE_URL}/api/tags" -o "$FILE" &&
  4907.               { jq -r '.models[]|.name?+"\t"+(.details.family)?+"\t"+(.details.format)?+"\t"+(.details.parameter_size)?+"\t"+(.details.quantization_level)?+"\t"+((.size/1000000)|tostring)?+"MB\t"+.modified_at?' "$FILE" || ! _warmsgf 'Err' ;}
  4908.             } | {   column -t -s $'\t' 2>/dev/null || ! _warmsgf 'Err' ;}  #tsv
  4909.         fi
  4910.     }
  4911.    
  4912.     ENDPOINTS[0]="/api/generate" ENDPOINTS[5]="/api/embeddings" ENDPOINTS[6]="/api/chat";
  4913.     ((${#OLLAMA_BASE_URL})) || OLLAMA_BASE_URL=${OPENAI_BASE_URL:-$OLLAMA_BASE_URL_DEF};
  4914.     ((${#OPENAI_API_KEY})) || OPENAI_API_KEY=$PLACEHOLDER  #set placeholder as this field is required
  4915.    
  4916.     OLLAMA_BASE_URL=${OLLAMA_BASE_URL%%*([/$IFS])}; set_model_epnf "$MOD";
  4917.     _sysmsgf "OLLAMA URL / Endpoint:" "$OLLAMA_BASE_URL${ENDPOINTS[EPN]}";
  4918. }
  4919.  
  4920. #host url / endpoint
  4921. function set_localaif
  4922. {
  4923.     if [[ $OPENAI_BASE_URL = *[!$IFS]* ]] || ((OLLAMA))
  4924.     then
  4925.         BASE_URL=${OPENAI_BASE_URL%%*([/$IFS])};
  4926.         ((LOCALAI)) &&
  4927.         function list_modelsf  #LocalAI only
  4928.         {
  4929.             if ((${#1}))
  4930.             then
  4931.                 curl -\# -L "${BASE_URL}/models/available" -o "$FILE" &&
  4932.                 { jq ".[] | select(.name | contains(\"$1\"))" "$FILE" || ! _warmsgf 'Err' ;}
  4933.             else
  4934.                 curl -\# -L ${FAIL} "${BASE_URL}/models/available" -o "$FILE" &&
  4935.                 { jq -r '.[]|.gallery.name+"@"+(.name//empty)' "$FILE" || ! _warmsgf 'Err' ;} ||
  4936.                 ! curl -\# -L "${BASE_URL}/models/" | jq .
  4937.                 #bug# https://github.com/mudler/LocalAI/issues/2045
  4938.             fi
  4939.         }  #https://localai.io/models/
  4940.         set_model_epnf "$MOD";
  4941.         ((${#OPENAI_BASE_URL})) && LOCALAI=1;
  4942.         ((${#OPENAI_API_KEY})) || OPENAI_API_KEY=$PLACEHOLDER
  4943.         ((!LOCALAI)) || _sysmsgf "HOST URL / Endpoint:" "${BASE_URL}${ENDPOINTS[EPN]}${ENDPOINTS[*]:+ [auto-select]}";
  4944.     else    false;
  4945.     fi
  4946. }
  4947.  
  4948. #google ai
  4949. function set_googleaif
  4950. {
  4951.     FILE_PRE="${FILE%%.json}.pre.json";
  4952.     : ${GOOGLE_API_KEY:?Required}
  4953.     ((${#OPENAI_API_KEY})) || OPENAI_API_KEY=$PLACEHOLDER
  4954.     ((${#GOOGLE_BASE_URL})) || GOOGLE_BASE_URL=${OPENAI_BASE_URL:-$GOOGLE_BASE_URL_DEF};
  4955.     ((OPTC)) || OPTC=2;
  4956.  
  4957.     function list_modelsf
  4958.     {
  4959.         if [[ -z $* ]]
  4960.         then    curl -\# ${FAIL} -L "$GOOGLE_BASE_URL/models?key=$GOOGLE_API_KEY" -o "$FILE"
  4961.         else    curl -\# ${FAIL} -L "$GOOGLE_BASE_URL/models/${1}?key=$GOOGLE_API_KEY" -o "$FILE"
  4962.         fi && {
  4963.         if ((OPTL>1))
  4964.         then    jq . -- "$FILE";
  4965.         else    jq -r '(.models|.[]?|.name|split("/")|.[1])//.' -- "$FILE" | tee -- "$FILEMODEL";
  4966.         fi || cat -- "$FILE" ;};
  4967.     }
  4968.     function __promptf
  4969.     {
  4970.         typeset epn;
  4971.         epn='generateContent';
  4972.         ((STREAM)) && epn='streamGenerateContent'; : >"$FILE_PRE";
  4973.         if curl "$@" ${FAIL} -L "$GOOGLE_BASE_URL/models/$MOD:${epn}?key=$GOOGLE_API_KEY" \
  4974.             -H 'Content-Type: application/json' -X POST \
  4975.             -d "$BLOCK" | tee "$FILE_PRE" | sed -n 's/^ *"text":.*/{ & }/p'
  4976.         then    [[ \ $*\  = *\ -s\ * ]] || _clr_lineupf;
  4977.         else    return $?;  #E#
  4978.         fi
  4979.     }
  4980.     function embedf
  4981.     {
  4982.         curl ${FAIL} -L "$GOOGLE_BASE_URL/models/$MOD:embedContent?key=$GOOGLE_API_KEY" \
  4983.             -H 'Content-Type: application/json' -X POST \
  4984.             -d "{ \"model\": \"models/embedding-001\",
  4985.                 \"content\": { \"parts\":[{
  4986.                   \"text\": \"${1}\"}]} }";
  4987.     }
  4988.     function __tiktokenf
  4989.     {
  4990.         typeset epn block buff ret;
  4991.         if [[ $MOD = *embedding* ]]
  4992.         then    epn="countTextTokens";
  4993.             block="{ \"prompt\": {\"text\": \"${*}\"}}";
  4994.         else    epn="countTokens";
  4995.             block="{ \"contents\": [{ \"parts\":[{ \"text\": \"${*}\"}]}]}";
  4996.         fi
  4997.         if ((${#block}>32000))  #32KB
  4998.         then    buff="${FILE%.*}.block.json";
  4999.             printf '%s\n' "$block" >"$buff";
  5000.             block="@${buff}";
  5001.         fi
  5002.         printf '%s\b' 'o' >&2;
  5003.         ((!${#1})) ||
  5004.           curl -sS --max-time 10 -L "$GOOGLE_BASE_URL/models/$MOD:${epn}?key=$GOOGLE_API_KEY" \
  5005.             -H 'Content-Type: application/json' -X POST \
  5006.             -d "$block" | jq -er '.totalTokens//.tokenCount//empty'; ret=$?;
  5007.         printf '%s\b' ' ' >&2;
  5008.         ((!ret)) || _tiktokenf "$*";
  5009.     }
  5010.     function tiktokenf
  5011.     {
  5012.         if [[ -t 0 ]]
  5013.         then    ((!${#1})) || __tiktokenf "$*";
  5014.         else    __tiktokenf "$(<$STDIN)";
  5015.         fi
  5016.     }
  5017.     function response_tknf
  5018.     {
  5019.         typeset var
  5020.         ((STREAM)) && var='[-1]';
  5021.         jq -r ".${var} | .usageMetadata |
  5022.         ( (.promptTokenCount//\"0\"), (.candidatesTokenCount//\"0\"), \"0\")" "$@";
  5023.     }
  5024.     function fmt_ccf
  5025.     {
  5026.         typeset var ext role
  5027.         [[ ${1} = *[!$IFS]* ]] || ((${#MEDIA[@]}+${#MEDIA_CMD[@]})) || return
  5028.         var= ext= role=;
  5029.        
  5030.         case "$2" in
  5031.             assistant)  role=model;;
  5032.             ''|system|user|*)   role=user;;
  5033.         esac;
  5034.         printf '{"role": "%s", "parts": [ ' "${role}";
  5035.         ((${#1})) &&
  5036.         printf '{"text": "%s"}' "$1";
  5037.         for var in "${MEDIA[@]}" "${MEDIA_CMD[@]}"
  5038.         do
  5039.             if [[ $var != *[!$IFS]* ]]
  5040.             then    continue;
  5041.             elif [[ -s $var ]]
  5042.             then    ext=${var##*.}; ((${#ext}<7)) && ext=${ext/[Jj][Pp][Gg]/jpeg} || ext=;
  5043.                 ((${#1})) && printf ',';
  5044.                 printf '
  5045.  {
  5046.    "inline_data": {
  5047.      "mime_type":"%s/%s",
  5048.      "data": "%s"
  5049.    }
  5050. }' "$(_is_videof "$var" && echo video || echo image)" "${ext:-jpeg}" "$(base64 "$var" | tr -d $'\n')";
  5051.             elif is_linkf "$var"
  5052.             then    _warmsgf 'GoogleAI: illegal URL --' "${var:0: COLUMNS-25}";
  5053.                 continue;
  5054.             fi
  5055.         done;
  5056.         printf '%s\n'  ' ] }';
  5057.     }
  5058. }
  5059. #https://ai.google.dev/gemini-api/docs/models/gemini
  5060. #Top-P, Top-K, Temperature, Stop sequence, Max output length, Number of response candidates
  5061.  
  5062. #anthropic api
  5063. function set_anthropicf
  5064. {
  5065.     : ${ANTHROPIC_API_KEY:?Required}
  5066.     ((${#OPENAI_API_KEY})) || OPENAI_API_KEY=$PLACEHOLDER;
  5067.     ((${#ANTHROPIC_BASE_URL})) || ANTHROPIC_BASE_URL=${OPENAI_BASE_URL:-$ANTHROPIC_BASE_URL_DEF};
  5068.     ENDPOINTS[0]="/complete" ENDPOINTS[6]="/messages" OPTA= OPTAA= ;
  5069.     if ((ANTHROPICAI)) && ((EPN==0))
  5070.     then    [[ -n ${RESTART+1} ]] || RESTART='\n\nHuman: ';
  5071.         [[ -n ${START+1} ]] || START='\n\nAssistant:';
  5072.     fi;
  5073.  
  5074.     function __promptf
  5075.     {
  5076.         [[ $MOD =  claude-3-5-sonnet-20240620 ]] &&  #8192 output tokens is in beta
  5077.           set -- "$@" --header "anthropic-beta: max-tokens-3-5-sonnet-2024-07-15";
  5078.  
  5079.         if curl "$@" ${FAIL} -L "${ANTHROPIC_BASE_URL}${ENDPOINTS[EPN]}" \
  5080.             --header "x-api-key: $ANTHROPIC_API_KEY" \
  5081.             --header "anthropic-version: 2023-06-01" \
  5082.             --header "content-type: application/json" \
  5083.             --data "$BLOCK";
  5084.         then    [[ \ $*\  = *\ -s\ * ]] || _clr_lineupf;
  5085.         else    return $?;  #E#
  5086.         fi
  5087.     }
  5088.     function response_tknf
  5089.     {
  5090.         jq -r '(.usage.output_tokens)//"0",
  5091.             (.usage.input_tokens)//(.message.usage.input_tokens)//"0", "0"' "$@";
  5092.     }
  5093.     function _list_modelsf
  5094.     {
  5095.         printf '%s\n' claude-3-5-sonnet-20240620 claude-3-opus-20240229 \
  5096.         claude-3-sonnet-20240229 claude-3-haiku-20240307 \
  5097.         claude-2.1 claude-2.0 claude-instant-1.2
  5098.     }
  5099.     function list_modelsf
  5100.     {
  5101.         set -- "https://raw.githubusercontent.com/anthropics/anthropic-sdk-python/main/src/anthropic/types";
  5102.         {
  5103.           { curl -\# ${FAIL} -L "${1}/model.py" ||
  5104.             curl -\# ${FAIL} -L "${1}/model_param.py" ;} |
  5105.               grep -oe '"[a-z0-9:.-]*"' | tr -d '"';
  5106.           _list_modelsf;
  5107.         } | sort | uniq;
  5108.     }
  5109. }
  5110. #there is no model listing endpoint
  5111. #rely on the `usage` property in the response for exact token counts
  5112. #https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/_client.py
  5113.  
  5114. #@#[[ ${BASH_SOURCE[0]} != "${0}" ]] && return 0;  #!#important for the test script
  5115.  
  5116.  
  5117. #parse opts  #DIJQX
  5118. unset OPTMM OPTMARG MAIN_LOOP HIST_LOOP; STOPS=();
  5119. optstring="a:A:b:B:cCdeEfFgGhHij:kK:lL:m:M:n:N:p:Pqr:R:s:S:t:ToOuUvVxwWyYzZ0123456789@:/,:.:-:"
  5120. while getopts "$optstring" opt
  5121. do
  5122.     case "$opt" in -)  #order matters: anthropic anthropic:ant
  5123.         for opt in api-key  multimodal  vision  audio  markdown  markdown:md  \
  5124. no-markdown  no-markdown:no-md  fold  fold:wrap  no-fold  no-fold:no-wrap \
  5125. localai  localai:local-ai  localai:local  google  google:goo  mistral \
  5126. openai  groq  groq:grok  anthropic  anthropic:ant  j:seed  keep-alive \
  5127. keep-alive:ka  @:alpha  M:max-tokens  M:max  N:mod-max  N:modmax \
  5128. a:presence-penalty  a:presence  a:pre  A:frequency-penalty  A:frequency \
  5129. A:freq  b:best-of  b:best  B:logprobs  c:chat  C:resume  C:resume  C:continue \
  5130. d:text  e:edit  E:exit  f:no-conf  g:stream  G:no-stream  h:help  H:hist \
  5131. i:image 'k:no-colo*'  K:top-k  K:topk  l:list-model  l:list-models  L:log  m:model \
  5132. m:mod  n:results  o:clipboard  o:clip  O:ollama  p:top-p  p:topp  q:insert \
  5133. r:restart-sequence  r:restart-seq  r:restart  R:start-sequence  R:start-seq \
  5134. R:start  s:stop  S:instruction  t:temperature  t:temp  T:tiktoken  \
  5135. u:multiline  u:multi  U:cat  v:verbose  x:editor  X:media  w:transcribe \
  5136. w:stt  W:translate  y:tik  Y:no-tik  z:tts  z:speech  Z:last  P:print  \
  5137. github  github:git  novita  novita:nov  version
  5138.         do
  5139.             name="${opt##*:}"  name="${name/[_-]/[_-]}"
  5140.             opt="${opt%%:*}"
  5141.             case "$OPTARG" in $name*)   break;; esac
  5142.         done
  5143.  
  5144.         case "$OPTARG" in
  5145.             $name|$name=)
  5146.                 if [[ $optstring = *"$opt":* ]]
  5147.                 then    OPTARG="${@:$OPTIND:1}"
  5148.                     OPTIND=$((OPTIND+1))
  5149.                 fi;;
  5150.             $name=*)
  5151.                 OPTARG="${OPTARG##$name=}"
  5152.                 ;;
  5153.             [0-9]*)  #max resp tkns option
  5154.                 OPTARG="$OPTMM-$OPTARG" opt=M
  5155.                 ;;
  5156.             *)  _warmsgf "Unknown option:" "--$OPTARG"
  5157.                 exit 2;;
  5158.         esac; unset name;;
  5159.     esac
  5160.     fix_dotf OPTARG
  5161.  
  5162.     case "$opt" in
  5163.         @)  OPT_AT="$OPTARG" EPN=9  #colour name/spec
  5164.             if [[ $OPTARG = *%* ]]  #fuzz percentage
  5165.             then    if [[ $OPTARG = *% ]]
  5166.                 then    OPT_AT_PC="${OPTARG##${OPTARG%%??%}}"
  5167.                     OPT_AT_PC="${OPT_AT_PC:-${OPTARG##${OPTARG%%?%}}}"
  5168.                     OPT_AT_PC="${OPT_AT_PC//[!0-9]}"
  5169.                     OPT_AT="${OPT_AT%%"$OPT_AT_PC%"}"
  5170.                 else    OPT_AT_PC="${OPTARG%%%*}"
  5171.                     OPT_AT="${OPT_AT##*%}"
  5172.                     OPT_AT="${OPT_AT##"$OPT_AT_PC%"}"
  5173.                 fi ;OPT_AT_PC="${OPT_AT_PC##0}"
  5174.             fi;;
  5175.         [0-9/-])    OPTMM="$OPTMM$opt";;
  5176.         M)  OPTMM="$OPTARG";;
  5177.         N)  [[ $OPTARG = *[!0-9\ ]* ]] && OPTMM="$OPTARG" || OPTNN="$OPTARG";;
  5178.         a)  OPTA="$OPTARG";;
  5179.         api-key) if [[ $OPTARG != api-key ]]
  5180.             then    OPENAI_API_KEY="$OPTARG";
  5181.             else    OPENAI_API_KEY=${@: OPTIND:1}; ((++OPTIND));
  5182.             fi;;
  5183.         A)  OPTAA="$OPTARG";;
  5184.         b)  OPTB="$OPTARG";;
  5185.         B)  OPTBB="$OPTARG";;
  5186.         c)  ((++OPTC));;
  5187.         C)  ((++OPTRESUME));;
  5188.         d)  OPTCMPL=1;;
  5189.         e)  ((++OPTE));;
  5190.         E)  ((++OPTEXIT));;
  5191.         f$OPTF) unset EPN MOD MOD_CHAT MOD_AUDIO MOD_SPEECH MOD_IMAGE MODMAX INSTRUCTION OPTZ_VOICE OPTZ_SPEED OPTZ_FMT OPTC OPTI OPTLOG USRLOG OPTRESUME OPTCMPL CHAT_ENV OPTTIKTOKEN OPTTIK OPTYY OPTFF OPTK OPTKK OPT_KEEPALIVE OPTHH OPTL OPTMARG OPTMM OPTNN OPTMAX OPTA OPTAA OPTB OPTBB OPTN OPTP OPTT OPTTW OPTV OPTVV OPTW OPTWW OPTZ OPTZZ OPTSTOP OPTCLIP CATPR OPTCTRD OPTMD OPT_AT_PC OPT_AT Q_TYPE A_TYPE RESTART START STOPS OPTS_HD OPTI_STYLE OPTSUFFIX SUFFIX CHATGPTRC REC_CMD PLAY_CMD CLIP_CMD STREAM MEDIA MEDIA_CMD MD_CMD OPTE OPTEXIT BASE_URL OLLAMA MISTRALAI LOCALAI GROQAI ANTHROPICAI GITHUBAI NOVITAAI GPTCHATKEY READLINEOPT MULTIMODAL OPTFOLD HISTSIZE WAPPEND NO_DIALOG NO_OPTMD_AUTO WHISPER_GROQ;
  5192.             unset RED BRED YELLOW BYELLOW PURPLE BPURPLE ON_PURPLE CYAN BCYAN WHITE BWHITE INV ALERT BOLD NC;
  5193.             unset Color1 Color2 Color3 Color4 Color5 Color6 Color7 Color8 Color9 Color10 Color11 Color200 Inv Alert Bold Nc;
  5194.             OPTF=1 OPTIND=1 OPTARG= ;. "${BASH_SOURCE[0]:-$0}" "$@" ;exit;;
  5195.         F)  ((++OPTFF));;
  5196.         fold)   OPTFOLD=1;;
  5197.         no-fold)    unset OPTFOLD;;
  5198.         g)  STREAM=1;;
  5199.         G)  unset STREAM;;
  5200.         h)  printf '%s\n' "$REPLY" "$HELP"
  5201.             exit;;
  5202.         H)  ((++OPTHH));;
  5203.         P)  ((OPTHH)) && ((++OPTHH)) || OPTHH=2;;
  5204.         i)  OPTI=1 EPN=3;;
  5205.         keep-alive)
  5206.             if [[ $OPTARG != @(keep-alive|ka) ]]
  5207.             then    OPT_KEEPALIVE=$OPTARG;
  5208.             elif { (( ${@: OPTIND:1} )) ;} 2>/dev/null
  5209.             then    OPT_KEEPALIVE=${@: OPTIND:1}; ((++OPTIND));
  5210.             fi;;
  5211.         k)  OPTK=1;;
  5212.         K)  OPTKK="$OPTARG";;
  5213.         l)  ((++OPTL));;
  5214.         L)  OPTLOG=1
  5215.             if [[ -d "$OPTARG" ]]
  5216.             then    USRLOG="${OPTARG%%/}/${USRLOG##*/}"
  5217.             else    USRLOG="${OPTARG:-${USRLOG}}"
  5218.             fi
  5219.             USRLOG="${USRLOG/\~\//"$HOME"\/}"
  5220.             _sysmsgf 'Log File' "<${USRLOG/"$HOME"/"~"}>";;
  5221.         m)  OPTMARG="${OPTARG:-$MOD}" MOD="$OPTMARG";;
  5222.         markdown)   ((++OPTMD));
  5223.             if [[ $OPTARG != @(markdown|md) ]]
  5224.             then    MD_CMD=$OPTARG;
  5225.             elif var=${@: OPTIND:1}
  5226.                 command -v "${var%% *}" &>/dev/null
  5227.             then    MD_CMD=${@: OPTIND:1}; ((++OPTIND));
  5228.             fi; unset var;;
  5229.         no-markdown)    OPTMD=0;;
  5230.         audio)  MULTIMODAL=2 EPN=6;;
  5231.         multimodal|vision) MULTIMODAL=1 EPN=6;;
  5232.         n)  [[ $OPTARG = *[!0-9\ ]* ]] && OPTMM="$OPTARG" ||  #compat with -Nill option
  5233.             OPTN="$OPTARG" ;;
  5234.         o)  OPTCLIP=1;;
  5235.         O)  OLLAMA=1 GOOGLEAI= MISTRALAI= GROQAI= ANTHROPICAI= GITHUBAI= NOVITAAI= ;;
  5236.         google) GOOGLEAI=1 OLLAMA= MISTRALAI= GROQAI= ANTHROPICAI= GITHUBAI= NOVITAAI= ;;
  5237.         mistral) MISTRALAI=1 OLLAMA= GOOGLEAI= GROQAI= ANTHROPICAI= GITHUBAI= NOVITAAI= ;;
  5238.         localai) LOCALAI=1;;
  5239.         openai) GOOGLEAI= OLLAMA= MISTRALAI= GROQAI= ANTHROPICAI= WHISPER_GROQ= GITHUBAI= NOVITAAI= ;;
  5240.         groq)   GROQAI=1 GOOGLEAI= OLLAMA= MISTRALAI= ANTHROPICAI= WHISPER_GROQ=1 GITHUBAI= NOVITAAI= ;;
  5241.         anthropic) ANTHROPICAI=1 GROQAI= GOOGLEAI= OLLAMA= MISTRALAI= GITHUBAI= NOVITAAI= ;;
  5242.         github) GITHUBAI=1 ANTHROPICAI= GROQAI= GOOGLEAI= OLLAMA= MISTRALAI= NOVITAAI= ;;
  5243.         novita) NOVITAAI=1 ANTHROPICAI= GROQAI= GOOGLEAI= OLLAMA= MISTRALAI= GITHUBAI= ;;
  5244.         p)  OPTP="$OPTARG";;
  5245.         q)  ((++OPTSUFFIX)); EPN=0;;
  5246.         r)  RESTART="$OPTARG";;
  5247.         R)  START="$OPTARG";;
  5248.         j)  OPTSEED=$OPTARG;;
  5249.         s)  STOPS=("$OPTARG" "${STOPS[@]}");;
  5250.         S|.|,)  if [[ $opt == S ]] && [[ -f "$OPTARG" ]]
  5251.             then    INSTRUCTION="${opt##S}$(<"$OPTARG")"
  5252.             else    INSTRUCTION="${opt##S}$OPTARG"
  5253.             fi;;
  5254.         t)  OPTT="$OPTARG" OPTTARG="$OPTARG";;
  5255.         T)  ((++OPTTIKTOKEN));;
  5256.         u)  ((OPTCTRD)) && unset OPTCTRD || OPTCTRD=1
  5257.             cmdmsgf 'Prompter <Ctrl-D>' $(_onoff $OPTCTRD);;
  5258.         U)  CATPR=1;;
  5259.         v)  ((++OPTV));;
  5260.         V)  ((++OPTVV));;  #debug
  5261.         version) while read; do     [[ $REPLY = \#\ v* ]] || continue; printf '%s\n' "$REPLY"; exit; done <"${BASH_SOURCE[0]:-$0}";;
  5262.         x)  ((OPTX)) && OPTX=2 || OPTX=1;;
  5263.         w)  ((++OPTW)); WSKIP=1;;
  5264.         W)  ((++OPTW)); ((++OPTWW)); WSKIP=1;;
  5265.         y)  OPTTIK=1;;
  5266.         Y)  OPTTIK= OPTYY=1;;
  5267.         z)  OPTZ=1;;
  5268.         Z)  ((++OPTZZ));;
  5269.         \?)     exit 1;;
  5270.     esac; OPTARG= ;
  5271. done
  5272. shift $((OPTIND -1))
  5273. unset LANGW MTURN CHAT_ENV SKIP EDIT INDEX HERR BAD_RES REPLY REPLY_CMD REPLY_CMD_DUMP REPLY_CMD_BLOCK REPLY_TRANS REGEX SGLOB EXT PIDS NO_CLR WARGS ZARGS WCHAT_C MEDIA MEDIA_CMD MEDIA_IND MEDIA_CMD_IND SMALLEST DUMP RINSERT BREAK_SET SKIP_SH_HIST OK_DIALOG DIALOG_CLR PREVIEW OPT_SLES RET CURLTIMEOUT MOD_REASON STURN init buff var tkn n s
  5274. typeset -a PIDS MEDIA MEDIA_CMD MEDIA_IND MEDIA_CMD_IND WARGS ZARGS
  5275. typeset -l VOICEZ OPTZ_FMT  #lowercase vars
  5276.  
  5277. set -o ${READLINEOPT:-emacs};
  5278. bind 'set enable-bracketed-paste on';
  5279. bind -x '"\C-x\C-e": "_edit_no_execf"';
  5280. bind '"\C-j": "\C-v\C-j"';  #add newline with Ctrl-J
  5281. [[ $BASH_VERSION = [5-9]* ]] || ((OPTV)) || _warmsgf 'Warning:' 'Bash 5+ recommended';
  5282.  
  5283. [[ -t 1 ]] || OPTK=1 ;((OPTK)) || {
  5284.   #map colours
  5285.   : "${RED:=${Color1:=${Red}}}"       "${BRED:=${Color2:=${BRed}}}"  #warning / error
  5286.   : "${YELLOW:=${Color3:=${Bold}}}"   "${BYELLOW:=${Color4}}"  #response
  5287.   : "${PURPLE:=${Color5:=${Purple}}}" "${BPURPLE:=${Color6:=${BPurple}}}" "${ON_PURPLE:=${Color7:=${On_Purple}}}"  #whisper
  5288.   : "${CYAN:=${Color8:=${Cyan}}}"     "${BCYAN:=${Color9:=${BCyan}}}"  "${ON_CYAN:=${Color12:=${On_Cyan}}}"  #user, Color12 needs adding to all themes
  5289.   : "${WHITE:=${Color10}}"            "${BWHITE:=${Color11:=${Bold}}}"  #system
  5290.   : "${INV:=${Inv}}" "${ALERT:=${Alert}}" "${BOLD:=${Bold}}" "${NC:=${Nc}}"
  5291.   JQCOL="\
  5292.  def red:     \"${RED//\\e/\\u001b}\";     \
  5293.  def yellow:  \"${YELLOW//\\e/\\u001b}\";  \
  5294.  def byellow: \"${BYELLOW//\\e/\\u001b}\"; \
  5295.  def bpurple: \"${BPURPLE//\\e/\\u001b}\"; \
  5296.  def reset:   \"${NC//\\e/\\u001b}\";"
  5297. }
  5298. JQCOLNULL="\
  5299. def red:     null; \
  5300. def yellow:  null; \
  5301. def byellow: null; \
  5302. def bpurple: null; \
  5303. def reset:   null;"
  5304.  
  5305. ((!(OPTCMPL+OPTC+OPTZZ+OPTL+OPTI+OPTTIKTOKEN+OPTFF) )) && OPTT=${OPTT:-0.8} STURN=1;  #single-turn no-hist demo
  5306. ((OPTL+OPTZZ)) && unset OPTX
  5307. ((OPTZ && OPTW)) && unset OPTX
  5308. ((OPTI)) && unset OPTC
  5309. ((OPTCLIP)) && set_clipcmdf
  5310. ((OPTW+OPTWW)) && ((!(OPTI+OPTL+OPTFF+OPTHH+OPTZZ+OPTTIKTOKEN) )) && set_reccmdf
  5311. ((OPTZ)) && set_playcmdf
  5312. ((OPTC)) || OPTT="${OPTT:-0}"  #!#temp *must* be set
  5313. ((OPTCMPL)) && unset OPTC  #opt -d
  5314. ((!OPTC)) && ((OPTRESUME>1)) && OPTCMPL=${OPTCMPL:-$OPTRESUME}  #1# txt cmpls cont
  5315. ((OPTCMPL)) && ((!OPTRESUME)) && OPTCMPL=2  #2# txt cmpls new
  5316. ((OPTC+OPTCMPL || OPTRESUME>1)) && MTURN=1  #multi-turn, interactive
  5317. ((OPTSUFFIX)) && ((OPTC)) && { ((OPTC>1)) || { [[ -n ${RESTART+1} ]] || RESTART=; [[ -n ${START+1} ]] || START= ;}; OPTC=1; };  #-qqc and -qqcc  #weyrd combo#
  5318. ((OPTSUFFIX>1)) && MTURN=1 OPTSUFFIX=1      #multi-turn -q insert mode
  5319. ((OPTCTRD)) || unset OPTCTRD  #(un)set <ctrl-d> prompter flush [bash]
  5320. [[ ${INSTRUCTION} = *[!$IFS]* ]] || unset INSTRUCTION
  5321.  
  5322. #map models
  5323. if [[ -n $OPTMARG ]]
  5324. then    ((OPTI)) && MOD_IMAGE=$OPTMARG  #default models for functions
  5325.     ((OPTW && !(OPTC+OPTCMPL+MTURN) )) && MOD_AUDIO=$OPTMARG
  5326.     ((OPTZ && !(OPTC+OPTCMPL+MTURN) )) && MOD_SPEECH=$OPTMARG
  5327.     case "$MOD" in moderation|oderation)    MOD="text-moderation-stable";; esac;
  5328.     [[ $MOD = *moderation* ]] && unset OPTC OPTW OPTWW OPTZ OPTI OPTII MTURN OPTRESUME OPTCMPL OPTEMBED
  5329. else
  5330.     if ((OLLAMA))
  5331.     then    MOD=$MOD_OLLAMA
  5332.     elif ((GOOGLEAI))
  5333.     then    MOD=$MOD_GOOGLE
  5334.     elif ((MISTRALAI)) || [[ $OPENAI_BASE_URL = *mistral* ]]
  5335.     then    MOD=$MOD_MISTRAL
  5336.     elif ((GROQAI))
  5337.     then    MOD=$MOD_GROQ MOD_AUDIO=$MOD_AUDIO_GROQ
  5338.     elif ((ANTHROPICAI))
  5339.     then    MOD=$MOD_ANTHROPIC
  5340.     elif ((LOCALAI))
  5341.     then    MOD=$MOD_LOCALAI
  5342.     elif ((GITHUBAI))
  5343.     then    MOD=$MOD_GITHUB
  5344.     elif ((NOVITAAI))
  5345.     then    MOD=$MOD_NOVITA
  5346.     elif ((!OPTCMPL))
  5347.     then    if ((OPTC>1)) ||  #chat / single-turn
  5348.             ((STURN && !(OPTW+OPTZ+OPTI) ))
  5349.         then    MOD=$MOD_CHAT
  5350.         elif ((OPTW)) && ((!MTURN))  #whisper endpoint
  5351.         then    ((GROQAI)) && MOD_AUDIO=$MOD_AUDIO_GROQ
  5352.             MOD=$MOD_AUDIO
  5353.         elif ((OPTZ)) && ((!MTURN))  #speech endpoint
  5354.         then    MOD=$MOD_SPEECH
  5355.         elif ((OPTI))
  5356.         then    MOD=$MOD_IMAGE
  5357.         fi
  5358.     fi
  5359. fi
  5360.  
  5361. #image endpoints
  5362. if ((OPTI))
  5363. then    command -v base64 >/dev/null 2>&1 || OPTI_FMT=url;
  5364.     n=; for arg
  5365.     do  [[ -f $arg ]] && OPTII=1 n=$((n+1));  #img vars or edits
  5366.     done;
  5367.     ((${#OPT_AT} || n>1)) && OPTII=1 OPTII_EDITS=1;  #img edits
  5368.     case "$3" in vivid|natural) OPTI_STYLE=$3;; hd|HD|standard) OPTS_HD=$3; set -- "${@:1:2}" "${@:4}";; esac;
  5369.     case "$2" in vivid|natural) OPTI_STYLE=$2;; hd|HD|standard) OPTS_HD=$2; set -- "${@:1:1}" "${@:3}";; esac;
  5370.     case "$1" in vivid|natural) OPTI_STYLE=$1;; hd|HD|standard) OPTS_HD=$1; shift;; esac;
  5371.     [[ -n $OPTS ]] && set_imgsizef "$OPTS";
  5372.     set_imgsizef "$1" && shift;
  5373.     unset STREAM arg n;
  5374. fi
  5375.  
  5376. #google integration
  5377. if case "${GOOGLE_BASE_URL:-$OPENAI_BASE_URL}" in *googleapis.com*) :;; *) ((GOOGLEAI));; esac
  5378. then    set_googleaif;
  5379.     unset OPTTIK OLLAMA MISTRALAI GROQAI ANTHROPICAI GITHUBAI NOVITAAI;
  5380. else    unset GOOGLEAI;
  5381. fi
  5382.  
  5383. #groq integration
  5384. if case "${GROQ_BASE_URL:-$OPENAI_BASE_URL}" in *api.groq.com*) :;; *) ((GROQAI));; esac
  5385. then
  5386.     BASE_URL=${GROQ_BASE_URL:-${OPENAI_BASE_URL:-$GROQ_BASE_URL_DEF}};
  5387.     OPENAI_API_KEY=${GROQ_API_KEY:?Required}
  5388.     ((OPTC==1 || OPTCMPL)) && OPTC=2;
  5389.     ENDPOINTS[0]=${ENDPOINTS[6]};
  5390.     unset OLLAMA GOOGLEAI MISTRALAI ANTHROPICAI GITHUBAI NOVITAAI;
  5391. else    unset GROQAI;
  5392. fi  #https://console.groq.com/docs/api-reference
  5393.  
  5394. #anthropic integration
  5395. if case "${ANTHROPIC_BASE_URL:-$OPENAI_BASE_URL}" in *api.anthropic.com*) :;; *) ((ANTHROPICAI));; esac
  5396. then    set_anthropicf;
  5397.     unset OLLAMA GOOGLEAI MISTRALAI GROQAI GITHUBAI NOVITAAI;
  5398. else    unset ANTHROPICAI;
  5399. fi
  5400.  
  5401. #ollama integration
  5402. if case "${OPENAI_BASE_URL}" in *localhost:11434*) :;; *) ((OLLAMA));; esac
  5403. then    set_ollamaf;
  5404.     unset GOOGLEAI MISTRALAI GROQAI ANTHROPICAI GITHUBAI NOVITAAI;
  5405. else    unset OLLAMA OLLAMA_BASE_URL;
  5406. fi
  5407.  
  5408. #custom host / localai
  5409. if case "${OPENAI_URL_PATH}${OPENAI_BASE_URL}" in *[!$IFS]*) :;; *) ((LOCALAI));; esac
  5410. then
  5411.     [[ ${OPENAI_URL_PATH} = *[!$IFS]* ]] && OPENAI_BASE_URL=$OPENAI_URL_PATH ENDPOINTS=();  #endpoint auto select
  5412.     [[ ${OPENAI_BASE_URL} = *[!$IFS]* ]] || OPENAI_BASE_URL=;
  5413.     ((${#OPENAI_BASE_URL})) || OPENAI_BASE_URL=$LOCALAI_BASE_URL_DEF;
  5414.     set_localaif;
  5415. else    unset OPENAI_URL_PATH;
  5416. fi
  5417.  
  5418. #mistral ai api
  5419. if case "${MISTRAL_BASE_URL:-$OPENAI_BASE_URL}" in *api.mistral.ai*) :;; *) ((MISTRALAI));; esac
  5420. then
  5421.     OPENAI_API_KEY=${MISTRAL_API_KEY:?Required};
  5422.     BASE_URL=${MISTRAL_BASE_URL:-${OPENAI_BASE_URL:-$MISTRAL_BASE_URL_DEF}};
  5423.  
  5424.     if [[ $MOD = *code* ]]
  5425.     then    ENDPOINTS[0]="/fim/completions"
  5426.         ((OPTSUFFIX)) && ((OPTC)) && OPTC=1;
  5427.     elif [[ $MOD != *embed* ]]
  5428.     then    OPTSUFFIX= OPTCMPL= OPTC=2;
  5429.     fi; MISTRALAI=1;
  5430.     unset LOCALAI OLLAMA GOOGLEAI GROQAI ANTHROPICAI GITHUBAI OPTA OPTAA OPTB NOVITAAI;
  5431. elif unset MISTRAL_API_KEY MISTRAL_BASE_URL MISTRALAI;
  5432. #github azure api
  5433.     case "${GITHUB_BASE_URL:-$OPENAI_BASE_URL}" in *ai.azure.com*) :;; *) ((GITHUBAI));; esac
  5434. then
  5435.     OPENAI_API_KEY=${GITHUB_API_KEY:-${GITHUB_TOKEN:?Required}}
  5436.     BASE_URL=${GITHUB_BASE_URL:-${OPENAI_BASE_URL:-$GITHUB_BASE_URL_DEF}};
  5437.  
  5438.     function list_modelsf
  5439.     {
  5440.         curl -L -\# "${BASE_URL}/models" -H "Authorization: Bearer $GITHUB_TOKEN" |
  5441.         jq -r '.[].name' | tee -- "$FILEMODEL";
  5442.         [[ -s $FILEMODEL ]] ||
  5443.         curl -\# -L -H "$UAG" "https://github.com/marketplace/models" |
  5444.         sed -n 's/"original_name":"[^"]*",/\n&\n/gp' | sed -n 's/"original_name"://p' |
  5445.         sed 's/[",]//g' | tee -- "$FILEMODEL";
  5446.         #https://github.com/marketplace/info
  5447.     };
  5448.     GITHUBAI=1 OPTC=2;  #chat completions only
  5449.     unset LOCALAI OLLAMA GOOGLEAI GROQAI ANTHROPICAI MISTRALAI NOVITAAI;
  5450. elif unset GITHUB_TOKEN GITHUB_BASE_URL GITHUBAI;
  5451.     case "${NOVITA_BASE_URL:-$OPENAI_BASE_URL}" in *api.novita.ai*) :;; *) ((NOVITAAI));; esac
  5452. then
  5453.     OPENAI_API_KEY=${NOVITA_API_KEY:?Required};
  5454.     BASE_URL=${NOVITA_BASE_URL:-${OPENAI_BASE_URL:-$NOVITA_BASE_URL_DEF}};
  5455.     NOVITAAI=1;
  5456. else    
  5457.     unset NOVITA_API_KEY NOVITA_BASE_URL NOVITAAI;
  5458. fi
  5459.  
  5460. OPENAI_API_KEY="${OPENAI_API_KEY:-${OPENAI_KEY:-${OPENAI_API_KEY:?Required}}}"
  5461.  
  5462. pick_modelf "$MOD"
  5463. #``model endpoint'' and ``model capacity''
  5464. [[ -n $EPN ]] || set_model_epnf "$MOD"
  5465. ((MODMAX)) || model_capf "$MOD"
  5466.  
  5467. #``max model / response tkns''
  5468. [[ -n $OPTNN && -z $OPTMM ]] ||
  5469. set_maxtknf "${OPTMM:-$OPTMAX}"
  5470. [[ -n $OPTNN ]] && MODMAX="$OPTNN"
  5471.  
  5472. #model options
  5473. ((OPTFF+OPTHH+OPTZZ+OPTL+OPTTIKTOKEN)) ||
  5474. set_optsf  #IPC#
  5475.  
  5476. #model prices (promote var to array)
  5477. (( ${#MOD_PRICE[@]}+${#COST_CUSTOM[@]} )) &&  #$COST_CUSTOM is deprecated
  5478.   MOD_PRICE=( ${MOD_PRICE[@]:-${COST_CUSTOM[@]}} )
  5479.  
  5480. #markdown rendering
  5481. if ((OPTMD+${#MD_CMD}))
  5482. then    set_mdcmdf "$MD_CMD";
  5483.     ((OPTMD)) || OPTMD=1;
  5484. fi
  5485. ((${#OPTMD}+${#MD_CMD})) && NO_OPTMD_AUTO=1  #disable markdown auto detect
  5486.  
  5487. #stdin and stderr filepaths
  5488. if [[ -n $TERMUX_VERSION ]]
  5489. then    STDIN='/proc/self/fd/0' STDERR='/proc/self/fd/2'
  5490. else    STDIN='/dev/stdin'      STDERR='/dev/stderr'
  5491. fi
  5492.  
  5493. #dump and append text from supported file types, and stdin
  5494. if ((OPTX)) && ((OPTEMBED+OPTI+OPTZ+OPTTIKTOKEN)) && ((!(OPTC+OPTCMPL) ))
  5495. then
  5496.     ((OPTEMBED+OPTI+OPTZ)) && ((${#})) &&
  5497.     if is_txtfilef "${@:${#}}" || is_pdff "${@:${#}}" || is_docf "${@:${#}}"
  5498.     then
  5499.         OPTV=4 cmd_runf /cat "${@:${#}}";
  5500.         ((!RET && ${#REPLY})) && set -- "${@:1:${#}-1}" "$REPLY";
  5501.     elif is_txtfilef "$1" || is_pdff "$1" || is_docf "$1"
  5502.     then
  5503.         OPTV=4 cmd_runf /cat "$1";
  5504.         ((!RET && ${#REPLY})) && set -- "$REPLY" "${@:2}";
  5505.     fi  #D#
  5506.  
  5507.     { ((OPTI)) && ((${#})) && [[ -f ${@:${#}} ]] ;} ||
  5508.       [[ -t 0 ]] || set -- "$@" "$(<$STDIN)";
  5509.    
  5510.     edf "$@" && set -- "$(<"$FILETXT")";
  5511. elif ! ((OPTTIKTOKEN+OPTI))
  5512. then
  5513.     ((${#})) &&
  5514.     if is_txtfilef "${@:${#}}" || is_pdff "${@:${#}}" || is_docf "${@:${#}}"
  5515.     then
  5516.         OPTV=4 cmd_runf /cat "${@:${#}}";
  5517.         ((!RET && ${#REPLY})) && set -- "${@:1:${#}-1}" "$REPLY";
  5518.     elif is_txtfilef "$1" || is_pdff "$1" || is_docf "$1"
  5519.     then
  5520.         OPTV=4 cmd_runf /cat "$1";
  5521.         ((!RET && ${#REPLY})) && set -- "$REPLY" "${@:2}";
  5522.     fi  #D#
  5523.  
  5524.     [[ -t 0 ]] || ((OPTZZ+OPTL+OPTFF+OPTHH)) || set -- "$@" "$(<$STDIN)";
  5525. fi; REPLY= RET=;
  5526.  
  5527. #tips and warnings
  5528. if ((!(OPTI+OPTL+OPTW+OPTZ+OPTZZ+OPTTIKTOKEN+OPTFF) || (OPTC+OPTCMPL && OPTW+OPTZ) )) && [[ $MOD != *moderation* ]]
  5529. then    if ((!OPTHH))
  5530.     then    sysmsgf "Max Response / Capacity:" "$OPTMAX${OPTMAX_NILL:+${EPN6:+ - inf.}} / $MODMAX tkns"
  5531.     elif ((OPTHH>1))
  5532.     then    sysmsgf 'Language Model:' "$MOD"
  5533.     fi
  5534. fi
  5535.  
  5536. (( (OPTI+OPTEMBED) || (OPTW+OPTZ && !MTURN) )) &&
  5537. for arg  #!# escape input
  5538. do  ((init++)) || set --
  5539.     set -- "$@" "$(escapef "$arg")"
  5540. done; unset arg init;
  5541.  
  5542. if ((OPTW+OPTZ))  #handle options of combined modes in chat + whisper + tts
  5543. then    typeset -a argn; argn=();
  5544.     n=1; for arg
  5545.     do  case "${arg:0:4}" in --) argn=(${argn[@]} $n);; esac; ((++n));
  5546.     done; #map double hyphens `--'
  5547.     if ((${#argn[@]}>=2)) && ((OPTW)) && ((OPTZ))  #improbable case
  5548.     then    ((ii=argn[1]-argn[0])); ((ii<1)) && ii=1;
  5549.         WARGS=("${@: argn[0]+1: ii-1}");
  5550.         ZARGS=("${@: argn[1]+1}");
  5551.         set -- "${@:1: argn[0]-1}";
  5552.     elif ((${#argn[@]}==1)) && ((OPTW)) && ((OPTZ))
  5553.     then    WARGS=("${@:1: argn[0]-1}");
  5554.         ZARGS=("${@: argn[0]+1}");
  5555.         set -- ;
  5556.     elif ((${#argn[@]})) && ((OPTW))
  5557.     then    WARGS=("${@: argn[0]+1}");
  5558.         set -- "${@:1: argn[0]-1}";
  5559.     elif ((${#argn[@]})) && ((OPTZ))
  5560.     then    ZARGS=("${@: argn[0]+1}");
  5561.         set -- "${@:1: argn[0]-1}";
  5562.     elif ((MTURN))
  5563.     then    if ((OPTW))
  5564.         then    WARGS=("$@");
  5565.         elif ((OPTZ))
  5566.         then    ZARGS=("$@");
  5567.         fi; set -- ;
  5568.     fi
  5569.     [[ -z ${WARGS[*]} ]] && unset WARGS;
  5570.     [[ -z ${ZARGS[*]} ]] && unset ZARGS;
  5571.     ((${#WARGS[@]})) && ((${#ZARGS[@]})) && ((${#})) && {
  5572.       var=$* p=${var:128} var=${var:0:128}; cmdmsgf 'Text Prompt' "${var//\\\\[nt]/  }${p:+ [..]}" ;}
  5573.     ((${#WARGS[@]})) && cmdmsgf "Whisper Args #${#WARGS[@]}" "${WARGS[*]:-unset}"
  5574.     ((${#ZARGS[@]})) && cmdmsgf 'TTS Args' "${ZARGS[*]:-unset}";
  5575.     unset n p ii var arg argn;
  5576. fi
  5577.  
  5578. ((${#TERMUX_VERSION})) && [[ ! -d $OUTDIR ]] && _warmsgf 'Err:' "Output directory -- ${OUTDIR/"$HOME"/"~"}";
  5579. [[ -d "$CACHEDIR" ]] || mkdir -p "$CACHEDIR" ||
  5580.   { _warmsgf 'Err:' "Cannot create cache directory -- \`${CACHEDIR/"$HOME"/"~"}'"; exit 1; }
  5581. if ! command -v jq >/dev/null 2>&1
  5582. then    function jq {   false ;}
  5583.     function escapef {  _escapef "$@" ;}
  5584.     function unescapef {    _unescapef "$@" ;}
  5585.     Color200=$INV _warmsgf 'Warning:' 'JQ not found. Please, install JQ.'
  5586. fi
  5587. command -v tac >/dev/null 2>&1 || function tac {    tail -r "$@" ;}  #bsd
  5588. ((!(OPTHH+OPTFF+OPTZZ) )) &&
  5589. [[ $(curl --help all 2>&1) = *"fail-with-body"* ]] && FAIL="--fail-with-body" || FAIL="--fail";
  5590.  
  5591. trap 'cleanupf; exit;' EXIT
  5592. trap 'exit' HUP QUIT TERM KILL
  5593.  
  5594. if ((OPTZZ))  #last response json
  5595. then    lastjsonf;
  5596. elif ((OPTL))  #model list
  5597. then    #(shell completion script)
  5598.     ((OPTL>2)) && [[ -s $FILEMODEL ]] && cat -- "$FILEMODEL" ||
  5599.     list_modelsf "$@";
  5600. elif ((OPTFF))
  5601. then    if [[ -s "$CHATGPTRC" ]] && ((OPTFF<2))
  5602.     then    _edf "$CHATGPTRC";
  5603.     else    curl --fail -L "https://gitlab.com/fenixdragao/shellchatgpt/-/raw/main/.chatgpt.conf";
  5604.         CHATGPTRC="stdout [$CHATGPTRC]";
  5605.     fi; _sysmsgf 'Conf File:' "${CHATGPTRC/"$HOME"/"~"}";
  5606. elif ((OPTHH && OPTW)) && ((!(OPTC+OPTCMPL+OPTRESUME+MTURN) )) && [[ -f $FILEWHISPERLOG ]]
  5607. then  #whisper log
  5608.     if ((OPTHH>1))
  5609.     then    BUFF="";
  5610.         while IFS= read -r || [[ -n $REPLY ]]
  5611.         do  [[ $REPLY = ==== ]] && [[ -n $BUFF ]] && break;
  5612.             BUFF=${REPLY}$'\n'${BUFF};
  5613.         done < <(tac "$FILEWHISPERLOG");
  5614.         printf '%s' "$BUFF";
  5615.     else    _edf "$FILEWHISPERLOG"
  5616.     fi; _sysmsgf 'Whisper Log:' "$FILEWHISPERLOG";
  5617. elif ((OPTHH))  #edit history/pretty print last session
  5618. then    OPTRESUME=1
  5619.     [[ -z $INSTRUCTION && $1 = [.,][!$IFS]* ]] && INSTRUCTION=$1 && shift;
  5620.     if [[ $INSTRUCTION = [.,]* ]]
  5621.     then    custom_prf
  5622.     elif [[ -n $* ]] && [[ $* != *($SPC)/* ]]
  5623.     then    set -- /session"$@"
  5624.     fi
  5625.     session_mainf "${@}"
  5626.     fix_breakf "$FILECHAT";
  5627.  
  5628.     if ((OPTHH>1))
  5629.     then
  5630.         ((OPTC || EPN==6)) && OPTC=2;
  5631.         ((OPTC+OPTRESUME+OPTCMPL)) || OPTC=1;
  5632.         MODMAX=$((MODMAX+1048576)) || MODMAX=1048576;
  5633.         Q_TYPE="\\n${Q_TYPE}" A_TYPE="\\n${A_TYPE}" OLLAMA= set_histf '';
  5634.  
  5635.         HIST=$(unescapef "${HIST:-"-*>[SESSION BREAK]<*-"}")
  5636.         if ((OPTMD))
  5637.         then    usr_logf "$HIST" | mdf
  5638.         else    usr_logf "$HIST" | foldf
  5639.         fi
  5640.         [[ ! -e $FILEFIFO ]] || rm -- "$FILEFIFO"
  5641.     elif [[ -t 1 ]]
  5642.     then    _edf "$FILECHAT"
  5643.     else    cat -- "$FILECHAT"
  5644.     fi
  5645.     _sysmsgf "Hist   File:" "${FILECHAT_OLD:-$FILECHAT}"
  5646. elif ((OPTTIKTOKEN))
  5647. then
  5648.     ((OPTTIKTOKEN>2)) || sysmsgf 'Language Model:' "$MOD"
  5649.     ((${#})) || [[ -t 0 ]] || set -- "-"
  5650.     [[ -f $* ]] && [[ -t 0 ]] &&
  5651.     if is_pdff "$*" || is_docf "$*"
  5652.     then    exec 0< <(OPTV=4 cmd_runf /cat "$*"; printf '%s\n' "$REPLY") && set -- "-";
  5653.     else    exec 0<"$*" && set -- "-";  #exec max one file
  5654.     fi
  5655.     if ((OPTYY))  #option -Y (debug, mostly)
  5656.     then    if [[ ! -t 0 ]]
  5657.             then    __tiktokenf "$(<$STDIN)";
  5658.             else    __tiktokenf "$*";
  5659.         fi
  5660.     else
  5661.         tiktokenf "$*" || ! _warmsgf "Err:" "Python / Tiktoken"
  5662.     fi
  5663. elif ((OPTW)) && ((!MTURN))  #audio transcribe/translation
  5664. then
  5665.     [[ -z ${WARGS[*]} ]] || set -- "${WARGS[@]}" "$@";
  5666.     if [[ $1 = @(.|last|retry) ]] && [[ -s $FILEINW ]]
  5667.     then    set -- "$FILEINW" "${@:2}";
  5668.     elif ((${#} >1)) && [[ ${@:${#}} = @(.|last|retry) ]] && [[ -s $FILEINW ]]
  5669.     then    set -- "$FILEINW" "${@:1:${#}-1}";
  5670.     fi
  5671.     ((${#OPTTARG})) && OPTTW=$OPTTARG;
  5672.     whisperf "$@" &&
  5673.     if ((OPTZ)) && WHISPER_OUT=$(jq -r "if .segments then (.segments[].text//empty) else (.text//empty) end" "$FILE" 2>/dev/null) &&
  5674.         ((${#WHISPER_OUT}))
  5675.     then    _sysmsgf $'\nText-To-Speech'; CHAT_ENV=1; set -- ;
  5676.         [[ -z ${ZARGS[*]} ]] || set -- "${ZARGS[@]}" "$@";
  5677.         ttsf "$@" "$(escapef "$WHISPER_OUT")";
  5678.     fi
  5679. elif ((OPTZ)) && ((!MTURN))  #speech synthesis
  5680. then    [[ -z ${ZARGS[*]} ]] || set -- "${ZARGS[@]}" "$@";
  5681.     _ttsf "$@"
  5682. elif ((OPTII))     #image variations+edits
  5683. then    if ((${#}>1))
  5684.     then    sysmsgf 'Image Edits'
  5685.     else    sysmsgf 'Image Variations' ;fi
  5686.     if [[ $MOD_IMAGE = *dall-e*[3-9] ]]
  5687.     then    sysmsgf 'Image Size / Quality:' "${OPTS:-err} / ${OPTS_HD:-standard}${OPTI_STYLE:+ / $OPTI_STYLE}"
  5688.     else    sysmsgf 'Image Size:' "${OPTS:-err}"
  5689.     fi
  5690.     imgvarf "$@"
  5691. elif ((OPTI))      #image generations
  5692. then    sysmsgf 'Image Generations'
  5693.     sysmsgf 'Image Model:' "$MOD_IMAGE"
  5694.     if [[ $MOD_IMAGE = *dall-e*[3-9] ]]
  5695.     then    sysmsgf 'Image Size / Quality:' "${OPTS:-err} / ${OPTS_HD:-standard}${OPTI_STYLE:+ / $OPTI_STYLE}"
  5696.     else    sysmsgf 'Image Size:' "${OPTS:-err}"
  5697.     fi
  5698.     imggenf "$@"
  5699. elif ((OPTEMBED))  #embeds
  5700. then    [[ $MOD = *embed* ]] || [[ $MOD = *moderation* ]] \
  5701.     || _warmsgf "Warning:" "Not an embedding model -- $MOD"
  5702.     unset Q_TYPE A_TYPE OPTC OPTCMPL STREAM
  5703.     if ((!${#}))
  5704.     then    _clr_ttystf; echo 'Input:' >&2;
  5705.         read_mainf REPLY </dev/tty
  5706.         ((OPTCTRD)) && REPLY=$(trim_trailf "$REPLY" $'*([\r])')
  5707.         set -- "$REPLY"; echo >&2;
  5708.     fi
  5709.     if [[ $MOD = *embed* ]]
  5710.     then    embedf "$@"
  5711.     else    moderationf "$@" &&
  5712.         printf '%-22s: %s\n' flagged $(lastjsonf | jq -r '.results[].flagged') &&
  5713.         printf '%-22s: %.24f (%s)\n' $(lastjsonf | jq -r '.results[].categories|keys_unsorted[]' | while read -r; do    lastjsonf | jq -r "\"$REPLY \" + (.results[].category_scores.\"$REPLY\"|tostring//empty) + \" \" + (.results[].categories.\"$REPLY\"|tostring//empty)"; done)
  5714.     fi
  5715. else
  5716.     CHAT_ENV=1;
  5717.     ((OPTW)) && unset OPTX; ((OPTW)) && OPTW=1; ((OPTWW)) && OPTWW=1;
  5718.     ((OPTC+OPTCMPL)) && ((!OPTEXIT)) && test_dialogf;
  5719.  
  5720.     #custom / awesome prompts
  5721.     [[ -z $INSTRUCTION && $1 = [.,][!$IFS]* ]] && INSTRUCTION=$1 && shift;
  5722.     case "$INSTRUCTION" in
  5723.         [/%]*)  OPTAWE=1 ;((OPTC)) || OPTC=1 OPTCMPL=
  5724.             awesomef || case $? in  210|202|1) exit 1;;     *) unset INSTRUCTION;; esac;  #err
  5725.             _sysmsgf $'\nHist   File:' "${FILECHAT}"
  5726.             if ((OPTRESUME==1))
  5727.             then    unset OPTAWE
  5728.             elif ((!${#}))
  5729.             then    unset REPLY
  5730.                 printf '\nAwesome INSTRUCTION set!\a\nPress <enter> to request or append user prompt: ' >&2
  5731.                 var=$(read_charf)
  5732.                 case "$var" in  ?) SKIP=1 EDIT=1 OPTAWE= REPLY=$var;;   *) JUMP=1;; esac; unset var;
  5733.             fi; [[ $INSTRUCTION = *[!$IFS]* ]] || unset INSTRUCTION;
  5734.             ;;
  5735.         [.,]*) custom_prf "$@"
  5736.             case $? in
  5737.                 200)    set -- ;;  #create, read and clear pos args
  5738.                 1|202|201|[1-9]*)   exit 1; unset INSTRUCTION;;  #err
  5739.             esac; [[ $INSTRUCTION = *[!$IFS]* ]] || unset INSTRUCTION;
  5740.             ;;
  5741.     esac
  5742.  
  5743.     #text/chat completions
  5744.     if ((OPTC))
  5745.     then    sysmsgf 'Chat Completions'
  5746.         #chatbot must sound like a human, shouldnt be lobotomised
  5747.         #presencePenalty:0.6 temp:0.9 maxTkns:150
  5748.         #frequencyPenalty:0.5 temp:0.5 top_p:0.3 maxTkns:60 (Marv)
  5749.         ((NOVITAAI)) && [[ $MOD = sao10k/*euryale* ]] &&
  5750.           OPTT=${OPTT:-1.17} OPTA=${OPTA:-0} BLOCK_USR=${BLOCK_USR:-\"min_p\":0.075,\"repetition_penalty\":1.10}
  5751.         #https://huggingface.co/Sao10K/L3-70B-Euryale-v2.1
  5752.  
  5753.         #temperature
  5754.         OPTT="${OPTT:-0.8}";  #!#
  5755.  
  5756.         #presencePenalty may be incompatible with some models!
  5757.         ((MOD_REASON+ANTHROPICAI+MISTRALAI+GITHUBAI+LOCALAI+OLLAMA+xGROQAIxGOOGLEAI)) ||
  5758.         { ((${INSTRUCTION+1}0)) && ((!${#INSTRUCTION})) ;} || OPTA="${OPTA:-0.6}";
  5759.         ((GITHUBAI)) && unset OPTA OPTAA;
  5760.        
  5761.         #stop sequences
  5762.         ((ANTHROPICAI && EPN!=0)) ||  #anthropic skip
  5763.         { ((EPN==6)) && [[ -z ${RESTART:+1}${START:+1} ]] ;} ||  #option -cc conditional skip
  5764.           STOPS+=("${RESTART-$Q_TYPE}" "${START-$A_TYPE}")
  5765.     else
  5766.         sysmsgf 'Text Completions'
  5767.     fi
  5768.     ((MULTIMODAL)) ||
  5769.     if is_amodelf "$MOD"
  5770.     then    MULTIMODAL=2;
  5771.     elif is_visionf "$MOD"
  5772.     then    MULTIMODAL=1;
  5773.     fi;
  5774.     ((MULTIMODAL>1)) && OPTZ_FMT="pcm16";  #audio-model preview
  5775.     sysmsgf 'Language Model:' "$MOD$( ((MULTIMODAL)) && echo ' / multimodal')";
  5776.    
  5777.     restart_compf ;start_compf
  5778.     function unescape_stopsf
  5779.     {   typeset s
  5780.         for s in "${STOPS[@]}"
  5781.         do    set -- "$@" "$(unescapef "$s")"
  5782.         done ;STOPS=("$@")
  5783.     } ;((${#STOPS[@]})) && unescape_stopsf
  5784.  
  5785.     ((OPTCMPL+OPTSUFFIX)) || {
  5786.       ((OPTC && !${#RESTART})) && [[ -n ${RESTART+1} ]] && _warmsgf 'Restart Sequence:' 'Set but null';
  5787.       ((OPTC && !${#START})) && [[ -n ${START+1} ]] && _warmsgf 'Start Sequence:' 'Set but null' ;}
  5788.  
  5789.     #model instruction
  5790.     INSTRUCTION_OLD="$INSTRUCTION"
  5791.     if ((MTURN+OPTRESUME))
  5792.     then
  5793.         ((MULTIMODAL>1)) && [[ $INSTRUCTION != *[!$IFS]* ]] && [[ $INSTRUCTION_CHAT = "$INSTRUCTION_CHAT_DEF" ]] &&
  5794.           INSTRUCTION_CHAT="${INSTRUCTION_CHAT} Your voice and personality should be warm and engaging, with a lively and playful tone. If interacting in a non-English language, start by using the standard accent or dialect familiar to the user. Talk quickly.";
  5795.  
  5796.         [[ $INSTRUCTION = *[!$IFS]* ]] && INSTRUCTION=$(trim_leadf "$INSTRUCTION" "$SPC:$SPC")
  5797.         shell_histf "$INSTRUCTION"
  5798.         ((OPTC)) && INSTRUCTION="${INSTRUCTION-$INSTRUCTION_CHAT}"  #IPC#
  5799.         INSTRUCTION_OLD="$INSTRUCTION"
  5800.        
  5801.         if ((OPTC && OPTRESUME)) || ((OPTCMPL==1 || OPTRESUME==1))
  5802.         then    unset INSTRUCTION;
  5803.         else    break_sessionf;
  5804.         fi
  5805.     elif [[ $INSTRUCTION != *[!:$IFS]* ]]
  5806.     then    unset INSTRUCTION
  5807.     fi
  5808.     if [[ $INSTRUCTION = *[!:$IFS]* ]]
  5809.     then    _sysmsgf 'INSTRUCTION:' "$INSTRUCTION" 2>&1 | foldf >&2;
  5810.         ((GOOGLEAI)) && GINSTRUCTION=$INSTRUCTION INSTRUCTION=;
  5811.     fi
  5812.  
  5813.     if ((MTURN))  #chat mode (multi-turn, interactive)
  5814.     then    [[ -t 1 ]] && _printbf 'history_bash'; var=$SECONDS;  #only visible when large and slow
  5815.         history -c; history -r; history -w &  #prune & rewrite history file
  5816.         [[ -t 1 ]] && _printbf '            ';
  5817.         ((SECONDS-var>1)) && _warmsgf 'Warning:' "Bash history size -- $(duf "$HISTFILE")";
  5818.         if ((OPTRESUME)) && [[ -s $FILECHAT ]]
  5819.         then    REPLY_OLD=$(grep_usr_lastlinef);
  5820.         elif [[ -s $HISTFILE ]]
  5821.         then    case "$BASH_VERSION" in  #avoid bash4 hanging
  5822.                 [0-3]*|4.[01]*|4|'')    :;;
  5823.                 *)  REPLY_OLD=$(trim_leadf "$(fc -ln -1)" "*([$IFS])");;
  5824.             esac;
  5825.         fi
  5826.         shell_histf "$*";
  5827.     fi
  5828.    
  5829.     #session and chat cmds
  5830.     [[ $1 = [/!]. ]] && set -- '/fork current' "${@:2}";
  5831.     if [[ $1 = /?* ]] && [[ ! -f "$1" && ! -d "$1" ]]
  5832.     then    case "$1" in
  5833.             /?| //? | /?(/)@(session|list|ls|fork|sub|grep|copy|cp) )
  5834.                 session_mainf "$1" "${@:2:1}" && set -- "${@:3}";;
  5835.             *)  session_mainf "$1" && set -- "${@:2}";;
  5836.         esac
  5837.     elif cmd_runf "$@"
  5838.     then    set -- ; SKIP_SH_HIST=;
  5839.     else  #print session context?
  5840.         if ((OPTRESUME==1)) && ((OPTV<2)) && [[ -s $FILECHAT ]]
  5841.         then    OPTPRINT=1 session_sub_printf "$(tail -- "$FILECHAT" >"$FILEFIFO")$FILEFIFO" >/dev/null;
  5842.         fi
  5843.     fi
  5844.     ((ANTHROPICAI)) && ((EPN==6)) && INSTRUCTION=;
  5845.     ((OPTRESUME)) && fix_breakf "$FILECHAT";
  5846.  
  5847.     if ((${#})) && ((!(OPTE+OPTX) )) && [[ ! -e $1 && ! -e ${@:${#}} ]]
  5848.     then    token_prevf "${INSTRUCTION}${INSTRUCTION:+ }${*}"
  5849.         sysmsgf "Inst+Prompt:" "~$TKN_PREV tokens"
  5850.     fi
  5851.  
  5852.     #warnings and tips
  5853.     ((OPTCTRD)) && _warmsgf '*' '<Ctrl-V Ctrl-J> for newline * '
  5854.     ((OPTCTRD+CATPR)) && _warmsgf '*' '<Ctrl-D> to flush input * '
  5855.     echo >&2  #!#
  5856.    
  5857.     #option -e, edit first user input
  5858.     ((OPTE && ${#})) && {   REPLY=$* EDIT=1 SKIP= WSKIP=; set -- ;}
  5859.  
  5860.     while :
  5861.     do  ((MTURN+OPTRESUME)) && ((!OPTEXIT)) && CKSUM_OLD=$(cksumf "$FILECHAT");
  5862.         if ((REGEN>0))  #regen + edit prompt
  5863.         then    if ((REGEN==1))
  5864.                 then    ((OPTX)) && PSKIP=1;
  5865.                     set -- "${REPLY_OLD:-$@}"
  5866.             elif ((REGEN>1)) && ((OPTX))
  5867.                 then    PSKIP= ;
  5868.                     set -- "${REPLY_OLD:-$@}"
  5869.             fi;
  5870.             REGEN=-1; ((--MAIN_LOOP));
  5871.         fi; RET=;
  5872.         ((OPTAWE)) || {  #awesome 1st pass skip
  5873.  
  5874.         #prompter pass-through
  5875.         if ((PSKIP))
  5876.         then    [[ -z $* ]] && [[ -n ${REPLY:-$REPLY_OLD} ]] && set -- "${REPLY:-$REPLY_OLD}";
  5877.         elif ((OPTX))
  5878.         #text editor prompter
  5879.         then    ((EDIT)) || REPLY=""  #!#
  5880.             edf "${REPLY:-$@}"
  5881.             case $? in
  5882.                 179|180) :;;        #jumps
  5883.                 200)    set --; REPLY=;
  5884.                     REPLY_CMD_DUMP= REPLY_CMD_BLOCK= SKIP_SH_HIST= WSKIP= SKIP=;  #E#
  5885.                     continue;;  #redo
  5886.                 201)    set --; OPTX=; false;;   #abort
  5887.                 202)    exit 202;;  #exit
  5888.                 *)  while [[ -f $FILETXT ]] && REPLY=$(<"$FILETXT"); echo >&2;
  5889.                         (($(wc -l <<<"$REPLY") < LINES-1)) || echo '[..]' >&2;
  5890.                         printf "${BRED}${REPLY:+${NC}${BCYAN}}%s${NC}\\n" "${REPLY:-(EMPTY)}" | tail -n $((LINES-2))
  5891.                     do
  5892.                     ((!BAD_RES)) && {
  5893.                     ((OPTV)) || [[ $REPLY = :* ]] \
  5894.                     || [[ $REPLY != *[!$IFS]* ]] \
  5895.                     || { is_txturl "${REPLY: ind}" >/dev/null && ((!REPLY_CMD_BLOCK)) ;};
  5896.                     } || new_prompt_confirmf
  5897.                         case $? in
  5898.                             202)    exit 202;;  #exit
  5899.                             201)    set --; OPTX=; break 1;;  #abort
  5900.                             200)    set --; REPLY=;
  5901.                                 REPLY_CMD_DUMP= REPLY_CMD_BLOCK= SKIP_SH_HIST= WSKIP= SKIP=;  #E#
  5902.                                 continue 2;;  #redo
  5903.                             19[6789])   edf "${REPLY:-$*}" || break 1;;  #edit
  5904.                             195)    WSKIP=1 WAPPEND=1 REPLY_OLD=$REPLY; ((OPTW)) || cmd_runf -ww;
  5905.                                 set --; break;;  #whisper append (hidden option)
  5906.                             0)  set -- "$REPLY" ; break;;  #yes
  5907.                             *)  set -- ; break;;  #no
  5908.                         esac
  5909.                     done;
  5910.                     ((OPTX>1)) && OPTX=;
  5911.             esac
  5912.             #[[ ${REPLY: ${#REPLY}-1} = / ]] && _warmsgf 'Warning:' "Text editor mode doesn't support previewing!";
  5913.         fi; PSKIP= RET=;
  5914.  
  5915.         ((JUMP)) ||
  5916.         #defaults prompter
  5917.         if [[ "$* " = @("${Q_TYPE##$SPC1}"|"${RESTART##$SPC1}")$SPC ]] || [[ -z "$*" ]]
  5918.         then    ((OPTC)) && Q="${RESTART:-${Q_TYPE:->}}" || Q="${RESTART:->}"
  5919.             B=${Q:0:128} B=${B##*$'\n'} B=${B##*\\n} B=${B//?/\\b}  #backspaces
  5920.  
  5921.             while ((SKIP)) ||
  5922.                 printf "${CYAN}${Q}${B}${NC}${OPTW:+${PURPLE}VOICE: }${NC}" >&2
  5923.                 printf "${BCYAN}${OPTW:+${NC}${BPURPLE}}" >&2
  5924.             do
  5925.                 ((SKIP+OPTW+${#RESTART})) && echo >&2
  5926.                 if ((OPTW && !EDIT)) || ((RESUBW))
  5927.                 then    #auto sleep 3-6 words/sec
  5928.                     if ((OPTV)) && ((!WSKIP)) && ((!BAD_RES)) && ! is_amodelf "$MOD"
  5929.                     then
  5930.                         var=$((SLEEP_WORDS/3)) SLEEP_WORDS=;
  5931.                         _printbf "${var}s";
  5932.                         if read_charf -t "${var}" >/dev/null 2>&1
  5933.                         then    _clr_lineupf;
  5934.                         else    _printbf "${var//?/ } ";
  5935.                         fi
  5936.                     fi
  5937.                    
  5938.                     ((RESUBW)) || record_confirmf
  5939.                     case $? in
  5940.                     0)  ((BAD_RES)) ||
  5941.                         if ((RESUBW)) || recordf "$FILEINW"
  5942.                         then
  5943.                             is_amodelf "$MOD" && _sysmsgf $'\nWhisper:' 'Transcript generation..';
  5944.                             REPLY=$(
  5945.                                 set --;
  5946.                                 ((GITHUBAI)) && OPENAI_API_KEY=$OPENAI_API_KEY_DEF;
  5947.                                 ((GROQAI+WHISPER_GROQ)) && MOD_AUDIO=$MOD_AUDIO_GROQ;
  5948.                                 ((!GROQAI && WHISPER_GROQ)) && BASE_URL=${GROQ_BASE_URL:-$GROQ_BASE_URL_DEF};
  5949.                                 MOD=$MOD_AUDIO OPTT=${OPTTW:-0} JQCOL= JQCOL2= MULTIMODAL=;
  5950.                                
  5951.                                 [[ -z ${WARGS[*]} ]] || set -- "${WARGS[@]}" "$@";
  5952.                                 context="${WCHAT_C:-$(escapef "${INSTRUCTION:-${GINSTRUCTION:-$INSTRUCTION_OLD}}")}";
  5953.                                 ((${#context})) && set -- "$@" "$context";
  5954.                                
  5955.                                 whisperf "$FILEINW" "$@";
  5956.                             )
  5957.                             if is_amodelf "$MOD" && ((!WAPPEND))
  5958.                             then
  5959.                                 printf "${NC}${Color5}%s${NC}\n" "$REPLY" | foldf >&2;
  5960.                                 REPLY_TRANS=$REPLY REPLY=${FILEINW/"$HOME"/"~"};
  5961.                             fi
  5962.                             ((WAPPEND)) && REPLY=$REPLY_OLD${REPLY_OLD:+${REPLY:+ }}$REPLY WAPPEND= ;
  5963.                         else    case $? in
  5964.                                 196)    WSKIP= OPTW= REPLY=; continue 1;;
  5965.                                 199)    EDIT=1; continue 1;;
  5966.                                 202)    exit 202;;  #exit
  5967.                             esac;
  5968.                             echo '[record abort]' >&2;
  5969.                         fi; ((OPTW>1)) && OPTW=;;
  5970.                     202)    exit 202;;  #exit
  5971.                     201|196)    WSKIP= OPTW= REPLY=; continue 1;;  #whisper off
  5972.                     199)    EDIT=1; continue 1;;  #text edit
  5973.                     *)  REPLY=; continue 1;;
  5974.                     esac; unset RESUBW;
  5975.                     printf "\\n${NC}${BPURPLE}%s${NC}\\n" "${REPLY:-"(EMPTY)"}" | foldf >&2;
  5976.                 else
  5977.                     _clr_ttystf;
  5978.                     ((EDIT)) || REPLY=""  #!#
  5979.                     if ((CATPR)) && ((!EDIT))
  5980.                     then    REPLY=$(cat);
  5981.                     else    read_mainf ${REPLY:+-i "$REPLY"} REPLY;
  5982.                     fi </dev/tty
  5983.  
  5984.                     ((CATPR)) && echo >&2;
  5985.                     ((OPTCTRD+CATPR)) && REPLY=$(trim_trailf "$REPLY" $'*([\r])')
  5986.                 fi; printf "${NC}" >&2;
  5987.                
  5988.                 if [[ ${REPLY:0:128} = /cat*([$IFS]) ]]
  5989.                 then    ((CATPR)) || CATPR=2 ;REPLY= PSKIP= SKIP=1
  5990.                     ((CATPR==2)) && _cmdmsgf 'Cat Prompter' "one-shot"
  5991.                     set -- ;continue  #A#
  5992.                 elif var="$REPLY" RET=
  5993.                     cmd_runf "$REPLY"
  5994.                 then    ((SKIP_SH_HIST)) || shell_histf "$REPLY"; SKIP_SH_HIST=;
  5995.                     if ((REGEN>0))
  5996.                     then    ((MAIN_LOOP)) || [[ ! -s $FILECHAT ]] || REPLY_OLD=$(grep_usr_lastlinef);
  5997.                         REPLY="${REPLY_OLD:-$REPLY}"
  5998.                         ((REGEN!=1)) || ((OPTV)) || test_cmplsf || printf '\n%s\n' '--- regenerate ---' >&2;
  5999.                     else    ((SKIP+EDIT)) || REPLY=;
  6000.                     fi; RET= var=; set --; continue 2
  6001.                 elif ((${#REPLY}>320)) && ind=$((${#REPLY}-320)) || ind=0  #!#
  6002.                     [[ ${REPLY: ind} = */ ]]  #preview (mind no trailing spaces)
  6003.                 then
  6004.                     ((PREVIEW)) && [[ ${REPLY_CMD:-$REPLY_OLD} != "$REPLY" ]] &&
  6005.                       prev_tohistf "$(escapef "${REPLY_CMD:-$REPLY_OLD}")";
  6006.                    
  6007.                     #check whether last arg is url or directory
  6008.                     var=$(INDEX=64 trimf "${REPLY: ind}" "$SPC")
  6009.                     if [[ -s ${var//\\} ]]
  6010.                     then    :;
  6011.                     else    var=$(trim_leadf "$(trim_trailf "${REPLY: ind}" "$SPC")" $'*[!\\\\][ \t\n]');
  6012.                         [[ ${var:0:1} = [$IFS] ]] && var=${var:1};
  6013.                     fi  #C#
  6014.                     [[ $var = *\\* ]] && var=${var//\\};
  6015.                     case "$var" in \~\/*)   var="$HOME/${var:2}";; esac;
  6016.  
  6017.                     if { _is_linkf "$var" && ! _is_imagef "$var" && ! _is_videof "$var" && [[ $var != *\/\/ ]] ;} ||
  6018.                         { [[ -d $var ]] && [[ $var != \/ ]] ;}
  6019.                     then    ((PREVIEW)) && PREVIEW=2 BCYAN="${Color9}";
  6020.                     else    test_cmplsf || printf '\n%s\n' '--- preview ---' >&2;
  6021.                         PREVIEW=1;
  6022.                     fi
  6023.                    
  6024.                     #del trailing slashes, and set preview colour
  6025.                     ((PREVIEW==1)) && REPLY=$(INDEX=160 trim_trailf "$REPLY" $'*([ \t\n])/*([ \t\n/])') REPLY_OLD=$REPLY BCYAN=${Color8};
  6026.                     REPLY_CMD_DUMP= REPLY_CMD_BLOCK= SKIP_SH_HIST= WSKIP= SKIP=;  #E#
  6027.                 elif case "${REPLY: ind}" in  #cmd: //shell, //sh
  6028.                     *[$IFS][/!][/!]shell|*[$IFS][/!][/!]sh) var=/shell;;
  6029.                     *[$IFS][/!]shell|*[$IFS][/!]sh) var=shell;;
  6030.                     *)  false;; esac;
  6031.                 then
  6032.                     set -- "$(trim_trailf "$REPLY" "${SPC}[/!]@(/shell|shell|/sh|sh)")";
  6033.                     REPLY_CMD_DUMP=$(
  6034.                         REPLY=;
  6035.                         cmd_runf /${var:-shell};
  6036.                         trim_trailf "$REPLY" "[/!]@(/shell|shell|/sh|sh)"
  6037.                     )
  6038.                     if ((${#REPLY_CMD_DUMP}))
  6039.                     then    REPLY="${*} ${REPLY_CMD_DUMP}";
  6040.                         ((SKIP_SH_HIST)) || shell_histf "$REPLY";
  6041.                     fi
  6042.                         SKIP=1 EDIT=1 REPLY_CMD=;
  6043.                     set -- ; continue 2;
  6044.                 elif case "${REPLY: ind}" in  #cmd: /photo, /pick, /save
  6045.                     *[$IFS][/!]photo|*[$IFS][/!]photo[0-9]) var=photo;;
  6046.                     *[$IFS][/!]pick|*[$IFS][/!]p) var=pick;;
  6047.                     *[$IFS][/!]save|*[$IFS][/!]\#) var=save;;
  6048.                     *)  false;; esac;
  6049.                 then
  6050.                     set -- "$(trim_trailf "$REPLY" "${SPC}[/!]@(photo|pick|p|save|\#)")";
  6051.                     cmd_runf /${var:-pick} "$*";
  6052.                     ((SKIP_SH_HIST)) || shell_histf "$REPLY";
  6053.                     set --; continue 2;
  6054.                 elif ((${#REPLY}))
  6055.                 then    PSKIP=;
  6056.                     ((!BAD_RES)) && {
  6057.                     ((PREVIEW+OPTV)) || [[ $REPLY = :* ]] \
  6058.                     || [[ $REPLY != *[!$IFS]* ]] \
  6059.                     || { is_txturl "${REPLY: ind}" >/dev/null && ((!REPLY_CMD_BLOCK)) ;};
  6060.                     } || new_prompt_confirmf ed whisper
  6061.                     case $? in
  6062.                         202)    exit 202;;  #exit
  6063.                         201)    break 2;;   #abort
  6064.                         200)  #redo
  6065.                             REPLY=$REPLY_CMD;
  6066.                             REPLY_CMD_DUMP= REPLY_CMD_BLOCK= SKIP_SH_HIST= WSKIP= SKIP=;  #E#
  6067.                             printf '\n%s\n' '--- redo ---' >&2; set --; continue;;
  6068.                         199)  #edit
  6069.                             WSKIP=1 EDIT=1;
  6070.                             printf '\n%s\n' '--- edit ---' >&2; continue;;
  6071.                         198)  #editor one-shot
  6072.                             ((OPTX)) || OPTX=2; EDIT=1 SKIP=1;
  6073.                             ((OPTX==2)) &&
  6074.                             printf '\n%s\n' '--- text editor one-shot ---' >&2
  6075.                             set -- ;continue 2;;
  6076.                         197)  #multiline one-shot
  6077.                             EDIT=1 SKIP=1; ((OPTCTRD))||OPTCTRD=2
  6078.                             ((OPTCTRD==2)) && printf '\n%s\n' '--- prompter <ctr-d> one-shot ---' >&2
  6079.                             REPLY="$REPLY"$'\n'; set -- ;continue;;  #A#
  6080.                         196)  #whisper off
  6081.                             WSKIP=1 EDIT=1 OPTW= ; continue 2;;
  6082.                         195)  #whisper append
  6083.                             WSKIP=1 WAPPEND=1 REPLY_OLD=$REPLY EDIT=; ((OPTW)) || cmd_runf -ww;
  6084.                             printf '\n%s\n' '--- whisper append ---' >&2; continue;;
  6085.                         194)  #whisper retry request
  6086.                             cmd_runf /resubmit; set --; continue 2;;
  6087.                         0)  :;;  #yes
  6088.                         *)  REPLY=; set -- ;break;;  #no
  6089.                     esac
  6090.  
  6091.                     if ((PREVIEW))
  6092.                     then    case "$REPLY" in "$REPLY_OLD"|"$REPLY_CMD")
  6093.                             PREVIEW=2 BCYAN="${Color9}";;
  6094.                         *)  #record prev resp
  6095.                             prev_tohistf "$(escapef "${REPLY_CMD:-$REPLY_OLD}")";
  6096.                         esac; REPLY_OLD="$REPLY";
  6097.                     else    unset REPLY_CMD;
  6098.                     fi
  6099.                 else
  6100.                     set --; unset REPLY_CMD;
  6101.                 fi ;set -- "$REPLY"
  6102.                 ((OPTCTRD==1)) || unset OPTCTRD
  6103.                 ((CATPR==1)) || unset CATPR
  6104.                 WSKIP= PSKIP= SKIP= EDIT= B= Q= ind= var=
  6105.                 break
  6106.             done
  6107.         fi; RET=;
  6108.  
  6109.         if ((!(JUMP+OPTCMPL) )) && [[ $1 != *[!$IFS]* ]]
  6110.         then    _warmsgf "(empty)"
  6111.             set -- ; continue
  6112.         fi
  6113.         if ((!OPTCMPL)) && ((OPTC)) && [[ "${*}" = *[!$IFS]* ]]
  6114.         then    set -- "$(trimf "$*" "$SPC1")"  #!#
  6115.             REPLY="$*"
  6116.         fi
  6117.         ((${#REPLY_OLD})) || REPLY_OLD="${REPLY:-$*}";  #I# Avoid $REPLY_CMD!
  6118.        
  6119.         }  #awesome 1st pass skip end
  6120.  
  6121.         if ((MTURN+OPTRESUME)) && [[ -n "${*}" ]]
  6122.         then
  6123.             [[ -n $REPLY ]] || REPLY="${*}" #set buffer for EDIT
  6124.  
  6125.             if ((PREVIEW!=1))
  6126.             then    ((SKIP_SH_HIST)) || shell_histf "${REPLY_CMD:-$*}"; SKIP_SH_HIST=;
  6127.                 history -a
  6128.             fi
  6129.  
  6130.             #system/instruction?
  6131.             case "${1:0:32}${2:0:16}" in :*)
  6132.                 var=$(trim_leadf "$*" "$SPC:")
  6133.  
  6134.                 case "${var:0:32}" in :::*)     var=${var:1};; esac;  #[DEPRECATED]
  6135.                 case "${var:0:32}" in ::*)
  6136.                     ((${#INSTRUCTION}+${#GINSTRUCTION})) && v=added || v=set;
  6137.                     _sysmsgf "System Prompt $v";
  6138.                     if ((GOOGLEAI))
  6139.                     then    RINSERT=${RINSERT}${var:1}${NL}${NL};
  6140.                     else    INSTRUCTION_OLD=${INSTRUCTION:-$INSTRUCTION_OLD}
  6141.                         INSTRUCTION=${INSTRUCTION}${INSTRUCTION:+${NL}${NL}}${var:1}
  6142.                     fi;
  6143.                 ;;
  6144.                     *)
  6145.                     RINSERT=${RINSERT}${var}${NL};
  6146.                     _sysmsgf 'User Prompt added';
  6147.                 ;;
  6148.                 esac;
  6149.                 EDIT= PSKIP= SKIP= REPLY= REPLY_OLD= REPLY_CMD= REPLY_CMD_DUMP= var= v=;
  6150.                 set --; continue;
  6151.             ;;
  6152.             esac;
  6153.             ((${#RINSERT})) && {    set -- "${RINSERT}${*}"; REPLY=${RINSERT}${REPLY} RINSERT= ;}
  6154.             REC_OUT="${*}"
  6155.         fi
  6156.  
  6157.         #insert mode
  6158.         if ((OPTSUFFIX)) && [[ "$*" = *${I_TYPE}* ]]
  6159.         then    if ((EPN!=6))
  6160.             then    SUFFIX="${*##*${I_TYPE}}";  #slow in bash + big strings
  6161.                 PREFIX="${*%%${I_TYPE}*}"; set -- "${PREFIX}";
  6162.             else    _warmsgf "Err: Insert mode:" "wrong endpoint (chat cmpls)"
  6163.             fi;
  6164.             REC_OUT="${REC_OUT:0:${#REC_OUT}-${#SUFFIX}-${#I_TYPE_STR}}"
  6165.         #basic text and pdf file, and text url dumps
  6166.         elif ((!REPLY_CMD_BLOCK)) && var=$(is_txturl "$1")  #C#
  6167.         then    RET=;
  6168.             if ((!${#REPLY_CMD_DUMP}))
  6169.             then
  6170.               REPLY_CMD=$REPLY;
  6171.               cmd_runf /cat"$var";
  6172.               REPLY_CMD_DUMP=$REPLY REPLY=$REPLY_CMD SKIP_SH_HIST= REPLY_CMD_BLOCK=1;
  6173.             fi; PSKIP= var=;
  6174.             if ((${#REPLY_CMD_DUMP})) &&
  6175.                 ((!RET || (RET>180 && RET<220) ))  #!# our exit codes: >180 and <220
  6176.             then
  6177.               REPLY_CMD="${REPLY:-$REPLY_CMD}";  #!#
  6178.               ((RET==200)) || [[ "${REPLY:0:128}" = "${REPLY_CMD_DUMP:0:128}" ]] ||
  6179.                 REPLY="${REPLY}${NL}${NL}${REPLY_CMD_DUMP}";
  6180.               ((PREVIEW)) && REPLY_OLD="$REPLY";
  6181.               case "$RET" in
  6182.                 202) echo '[bye]' >&2; exit 202;;
  6183.                 201|200) EDIT=1 REPLY=$REPLY_CMD;
  6184.                  REPLY_CMD_DUMP= REPLY_CMD_BLOCK= SKIP_SH_HIST= WSKIP= SKIP=;  #E#
  6185.                      set --; continue 1;;  #redo / abort
  6186.                 199) SKIP=1 EDIT=1; set --; continue 1;;  #edit in bash readline
  6187.                 198) ((OPTX)) || OPTX=2; SKIP=1 EDIT=1; set --; continue 1;;  #edit in text editor
  6188.               esac
  6189.               set -- "${*}${NL}${NL}${REPLY_CMD_DUMP}";
  6190.               REC_OUT="${*}";
  6191.             else
  6192.               SKIP=1 EDIT=1 REPLY_CMD_DUMP= REPLY_CMD= REPLY_CMD_BLOCK=;
  6193.               set --; continue 1;  #edit orig input
  6194.             fi; RET=;
  6195.         #vision / audio-model
  6196.         elif is_visionf "$MOD" || is_amodelf "$MOD"
  6197.         then
  6198.             media_pathf "$1";
  6199.             ((MTURN)) &&
  6200.             for var in "${MEDIA_CMD[@]}"
  6201.             do  [[ $var = *\ * ]] && [[ $var != *\\\ * ]] && var=${var// /\\ };  #escape spaces
  6202.                 REC_OUT="$REC_OUT $var" REPLY="$REPLY $var";
  6203.                 set -- "$* $var";
  6204.             done; var=;
  6205.         else    unset SUFFIX PREFIX;
  6206.         fi
  6207.  
  6208.         set_optsf
  6209.  
  6210.         if ((PREVIEW<2))
  6211.         then
  6212.             #audio-models, record filepath for the transcript of it
  6213.             if ((OPTW)) && ((${#REPLY_TRANS} || REGEN<0)) && is_amodelf "$MOD"
  6214.             then
  6215.                 var=${FILEINW/"$HOME"/"~"};
  6216.                 ((OPTW)) &&  #IPC# Remove whisper audio filepath from user prompt
  6217.                 case \ "${MEDIA[*]}"\  in *\ "${var}"\ *|*\ "${FILEINW}"\ *)
  6218.                     case "${REC_OUT}" in
  6219.                         *"${var}")  REC_OUT=${REC_OUT:0:${#REC_OUT}-${#var}};
  6220.                                 set -- "${1:0:${#1}-${#var}}";;
  6221.                         *"${FILEINW}")  REC_OUT=${REC_OUT:0:${#REC_OUT}-${#FILEINW}};
  6222.                                 set -- "${1:0:${#1}-${#FILEINW}}";;
  6223.                         *"${var}"*|*"${FILEINW}"*)
  6224.                                 REC_OUT=$(sed -e "s/${var}/ /" -e "s/${FILEINW}/ /" <<<"$REC_OUT" || printf '%s' "$REC_OUT");;
  6225.                     esac;;
  6226.                 esac; unset var;
  6227.                 REPLY_OLD="${REPLY_TRANS}${REPLY_TRANS:+${REPLY:+ }}${REPLY}";
  6228.                 REC_OUT="${REPLY_TRANS}${REPLY_TRANS:+${REC_OUT:+ }}${REC_OUT}";
  6229.             fi
  6230.  
  6231.             ((MTURN+OPTRESUME)) &&
  6232.             if ((EPN==6));
  6233.             then    set_histf "${INSTRUCTION:-$GINSTRUCTION}${*}";
  6234.             else    set_histf "${INSTRUCTION:-$GINSTRUCTION}${Q_TYPE}${*}"; fi
  6235.             ((MAIN_LOOP||TOTAL_OLD)) || TOTAL_OLD=$(__tiktokenf "${INSTRUCTION:-${GINSTRUCTION:-${ANTHROPICAI:+$INSTRUCTION_OLD}}}")
  6236.             if ((OPTC)) || [[ -n "${RESTART}" ]]
  6237.             then    rest="${RESTART-$Q_TYPE}"
  6238.                 ((OPTC && EPN==0)) && [[ ${HIST:+x}$rest = \\n* ]] && rest=${rest:2}  #!#del \n at start of string
  6239.             fi
  6240.             ((JUMP)) && set -- && rest=;
  6241.             var="$(escapef "${INSTRUCTION:-$GINSTRUCTION}")${INSTRUCTION:+\\n\\n}${GINSTRUCTION:+\\n\\n}";
  6242.             ESC="${HIST}${HIST:+${var:+\\n\\n}}${var}${rest}$(escapef "${*}")";
  6243.             ESC=$(INDEX=32 trim_leadf "$ESC" "\\n");
  6244.            
  6245.             if ((EPN==6))
  6246.             then    #chat cmpls
  6247.                 [[ ${*} = *([$IFS]):* ]] && role=system || role=user
  6248.                 ((GOOGLEAI)) &&  [[ $MOD = *gemini*-pro-vision* && $MOD != *gemini*-1.5-* ]] &&  #gemini-1.0-pro-vision cannot take it multiturn
  6249.                 if { (( (REGEN<0 || PREVIEW) && MAIN_LOOP<1)) && ((${#INSTRUCTION_OLD})) ;} || is_visionf "$MOD"
  6250.                 then    HIST_G=${HIST}${HIST:+\\n\\n} HIST_C= ;
  6251.                     ((${#MEDIA[@]}+${#MEDIA_CMD[@]})) ||
  6252.                     MEDIA=("${MEDIA_IND[@]}") MEDIA_CMD=("${MEDIA_CMD_IND[@]}");
  6253.                 fi
  6254.                 var=$(unset MEDIA MEDIA_CMD; fmt_ccf "$(escapef "$INSTRUCTION")" system;) && var="${var}${INSTRUCTION:+,${NL}}";  #mind anthropic
  6255.                 set -- "${HIST_C}${HIST_C:+,${NL}}${var}$(
  6256.                     fmt_ccf "${HIST_G}$(escapef "${GINSTRUCTION}${GINSTRUCTION:+$NL$NL}${*}")" "$role")";
  6257.             else    #text cmpls
  6258.                 if {    ((OPTC)) || [[ -n "${START}" ]] ;} && ((JUMP<2))
  6259.                 then    set -- "${ESC}${START-$A_TYPE}"
  6260.                 else    set -- "${ESC}"
  6261.                 fi
  6262.             fi; rest= role=;
  6263.            
  6264.             for media in "${MEDIA_IND[@]}" "${MEDIA_CMD_IND[@]}"
  6265.             do  ((media_i++));
  6266.                 var=$(is_audiof "$media" && echo aud || echo img)
  6267.                 [[ -f $media ]] && media=$(duf "$media");
  6268.                 _sysmsgf "$var #${media_i}" "${media:0: COLUMNS-6-${#media_i}}$([[ -n ${media: COLUMNS-6-${#media_i}} ]] && printf '\b\b\b%s' ...)";
  6269.             done; media= media_i=;
  6270.         fi
  6271.  
  6272.         if ((EPN==6))
  6273.         then    set -- "$(sed -e '/^[[:space:]]*$/d' <<<"$*" | sed -e '$s/,[[:space:]]*$//')";
  6274.             if ((GOOGLEAI))
  6275.             then    BLOCK="\"contents\": [ ${*} ],";
  6276.             else    BLOCK="\"messages\": [ ${*} ],";
  6277.             fi
  6278.         else
  6279.             if ((GOOGLEAI))
  6280.             then    BLOCK="\"contents\": [{ \"parts\":[{ \"text\": \"${*}\" }] }],";
  6281.             else    BLOCK="\"prompt\": \"${*}\",";
  6282.             fi
  6283.         fi
  6284.  
  6285.         if ((GOOGLEAI))
  6286.         then
  6287.             BLOCK_SAFETY="\"safetySettings\": [
  6288.  {\"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\",
  6289.    \"threshold\": \"BLOCK_NONE\"},
  6290.  {\"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\",
  6291.    \"threshold\": \"BLOCK_NONE\"},
  6292.  {\"category\": \"HARM_CATEGORY_HATE_SPEECH\",
  6293.    \"threshold\": \"BLOCK_NONE\"},
  6294.  {\"category\": \"HARM_CATEGORY_HARASSMENT\",
  6295.    \"threshold\": \"BLOCK_NONE\"}
  6296.    ],"
  6297.             BLOCK="{
  6298. $BLOCK
  6299. $BLOCK_SAFETY
  6300. \"generationConfig\": {
  6301.    ${OPTSTOP/stop/stopSequences}
  6302.    ${OPTP_OPT/_p/P} ${OPTKK_OPT/_k/K}
  6303.    \"temperature\": $OPTT,
  6304.    \"maxOutputTokens\": $OPTMAX${BLOCK_USR:+,$NL}$BLOCK_USR
  6305.  }
  6306. }"
  6307. #PaLM: HARM_CATEGORY_UNSPECIFIED HARM_CATEGORY_DEROGATORY HARM_CATEGORY_TOXICITY HARM_CATEGORY_VIOLENCE HARM_CATEGORY_SEXUAL HARM_CATEGORY_MEDICAL HARM_CATEGORY_DANGEROUS
  6308.         elif ((OLLAMA))
  6309.         then
  6310.             BLOCK="{
  6311. $( ((EPN!=6)) && ollama_mediaf && printf '%s' ',')
  6312. $( ((EPN!=6)) && echo "\"system\": \"$(escapef "${INSTRUCTION:-$INSTRUCTION_OLD}")\"," )
  6313. $BLOCK
  6314. \"model\": \"$MOD\", $STREAM_OPT $OPT_KEEPALIVE_OPT
  6315. \"options\": {
  6316.  $( ((OPTMAX_NILL)) && "\"num_predict\": -2" || echo "\"num_predict\": $OPTMAX" ),
  6317.  \"temperature\": $OPTT, $OPTSEED_OPT
  6318.  $OPTA_OPT $OPTAA_OPT $OPTP_OPT $OPTKK_OPT
  6319.  $OPTB_OPT $OPTBB_OPT $OPTSTOP
  6320.  \"num_ctx\": $MODMAX${BLOCK_USR:+,$NL}$BLOCK_USR
  6321.  }
  6322. }"
  6323.         else
  6324.             BLOCK="{
  6325. $( ((ANTHROPICAI)) && ((EPN==6)) && ((${#INSTRUCTION_OLD})) && echo "\"system\": \"$(escapef "$INSTRUCTION_OLD")\"," )
  6326. $BLOCK $OPTSUFFIX_OPT
  6327. $(
  6328. ((ANTHROPICAI)) && ((EPN!=6)) && max="max_tokens_to_sample" || max="max_tokens"
  6329. case "$MOD" in o[1-9]*)     max="max_completion_tokens";; esac
  6330. ((OPTMAX_NILL && EPN==6)) || echo "\"${max:-max_tokens}\": $OPTMAX," )
  6331. $STREAM_OPT $OPTA_OPT $OPTAA_OPT $OPTP_OPT $OPTKK_OPT
  6332. $OPTB_OPT $OPTBB_OPT $OPTSTOP $OPTSEED_OPT
  6333. $(
  6334. is_amodelf "$MOD" &&
  6335. if ((OPTW+OPTZ))
  6336. then  printf '"modalities": ["text", "audio"], "audio": { "voice": "%s", "format": "%s" },' "${OPTZ_VOICE:-echo}" "${OPTZ_FMT:-pcm16}"
  6337. else  printf '"modalities": ["text"],'
  6338. fi ) \
  6339. $( ((MISTRALAI+GROQAI+ANTHROPICAI)) || echo "\"n\": $OPTN," ) \
  6340. $( ((MISTRALAI+LOCALAI+ANTHROPICAI+GITHUBAI)) || ((!STREAM)) || echo "\"stream_options\": {\"include_usage\": true}," )
  6341. \"model\": \"$MOD\", \"temperature\": $OPTT${BLOCK_USR:+,$NL}$BLOCK_USR
  6342. }"
  6343.         fi
  6344.  
  6345.         #response colours for jq
  6346.         if ((PREVIEW==1))
  6347.         then    ((OPTK)) || JQCOL2='def byellow: yellow;'
  6348.         else    unset JQCOL2
  6349.         fi; ((OPTC||(STURN && EPN==6) )) && echo >&2
  6350.  
  6351.         #request and response prompts
  6352.         SECONDS_REQ=${EPOCHREALTIME:-$SECONDS};
  6353.         ((${#START})) && printf "${YELLOW}%b\\n" "$START" >&2;
  6354.         ((OLLAMA)) && base_url=$BASE_URL BASE_URL=$OLLAMA_BASE_URL;
  6355.  
  6356.         #move cursor to the end of user input in previous line
  6357.         if test_cmplsf && ((!JUMP))
  6358.         then    if ((${#MEDIA_IND[@]}+${#MEDIA_CMD_IND[@]}+${#REPLY_CMD_DUMP}))
  6359.             then
  6360.               echo >&2;
  6361.             elif ((OPTSUFFIX && ${#SUFFIX}))
  6362.             then  #insert mode, print last line of reply
  6363.               _p_linerf "$PREFIX"; var=0;
  6364.             elif ! ((ANTHROPICAI && EPN==0))
  6365.             then
  6366.               buff=$(sed -n '$p' <<<$REPLY)
  6367.               printf "\\e[A\\e[$((${#buff} % COLUMNS))C" >&2;
  6368.               if ((OPTC))
  6369.               then  printf '%b' "${START-$A_TYPE}" >&2;
  6370.               else  printf '%b' "$START" >&2;
  6371.               fi; var=0;
  6372.             else  var=$OPTFOLD;
  6373.             fi;
  6374.         else    var=$OPTFOLD;
  6375.         fi
  6376.  
  6377.         if ((${#BLOCK}>96000))  #96KB
  6378.         then    buff="${FILE%.*}.block.json"
  6379.             printf '%s\n' "$BLOCK" >"$buff"
  6380.             BLOCK="@${buff}" OPTFOLD=$var promptf
  6381.         #https://stackoverflow.com/questions/19354870/bash-command-line-and-input-limit
  6382.         else    OPTFOLD=$var promptf
  6383.         fi; RET_PRF=$? RET=;
  6384.         ((OPTEXIT>1)) && exit $RET_PRF;
  6385.  
  6386.         ((OLLAMA)) && BASE_URL=$base_url;
  6387.         buff= base_url=;
  6388.  
  6389.         ((STREAM)) && ((MTURN || EPN==6)) && echo >&2;
  6390.         if (( (RET_PRF>120 && !STREAM) || (!RET_PRF && PREVIEW==1) ))
  6391.         then    ((${#REPLY_CMD})) && REPLY=$REPLY_CMD;
  6392.             PSKIP= JUMP= OPTE= SKIP=1 EDIT=1 RET_PRF= RET_APRF=; set --; continue;  #B#
  6393.         fi
  6394.         ((RET_PRF>120)) && INT_RES='#';
  6395.         ((RET_PRF)) || REPLY_OLD="${REPLY:-${REPLY_OLD:-$*}}";  #I#
  6396.  
  6397.         #record to hist file
  6398.         if  if ((STREAM))
  6399.             then    ans=$(prompt_pf -r -j "$FILE"; echo x) ans=${ans:0:${#ans}-1}
  6400.                 ans=$(escapef "$ans")
  6401.                 ((OLLAMA+LOCALAI)) ||  #OpenAI, MistralAI, and Groq
  6402.                 tkn=( $(
  6403.                     ind="-1" var="";
  6404.                     ((GOOGLEAI)) && FILE="$FILE_PRE";
  6405.                     ((GROQAI)) && var="x_groq";
  6406.                     ((ANTHROPICAI)) && ind="";
  6407.                     jq -rs ".[${ind}] | .${var}" "$FILE" | response_tknf;
  6408.                     ((GROQAI)) && { datef;
  6409.                         jq -rs '.[-1].x_groq.usage | (.completion_time,.completion_tokens/.completion_time)' "$FILE";
  6410.                     }  #tkn rate
  6411.                     ) )  #[0]input_tkn  [1]output_tkn  [2]reason_tkn  [3]time  [4]cmpl_time  [5]tkn_rate
  6412.                 ((tkn[0]&&tkn[1])) 2>/dev/null || ((OLLAMA)) || {
  6413.                   tkn_ans=$( ((EPN==6)) && A_TYPE=; __tiktokenf "${A_TYPE}${ans}");
  6414.                   ((tkn_ans+=TKN_ADJ)); ((MAX_PREV+=tkn_ans)); TOTAL_OLD=; tkn=();
  6415.                 };
  6416.             else
  6417.                 { ((ANTHROPICAI && EPN==0)) && tkn=(0 0) ;} ||
  6418.                 ((OLLAMA)) || tkn=( $(
  6419.                     jq "." "$FILE" | response_tknf;
  6420.                     ((GROQAI)) && jq -r '.usage | (.completion_time, .completion_tokens/.completion_time)' "$FILE";  #tkn rate
  6421.                     ) )
  6422.                 ans= buff= n=;
  6423.                 for ((n=0;n<OPTN;n++))  #multiple responses
  6424.                 do  buff=$(INDEX=$n prompt_pf "$FILE")
  6425.                     ((${#buff}>1)) && buff=${buff:1:${#buff}-2}  #del lead and trail ""
  6426.                     ans="${ans}"${ans:+${buff:+\\n---\\n}}"${buff}"
  6427.                 done
  6428.             fi
  6429.             if ((OLLAMA))
  6430.             then    tkn=($(jq -r -s '.[-1]|.prompt_eval_count//"0", .eval_count//"0", "0", .created_at//"0", (.eval_duration/1000000000)?, (.eval_count/(.eval_duration/1000000000)?)?' "$FILE") )
  6431.                 ((STREAM)) && ((MAX_PREV+=tkn[1]));
  6432.             fi
  6433.  
  6434.             #audio-model: audio only response
  6435.             if ((OPTZ)) && ((!${#ans})) && is_amodelf "$MOD"  #((!RET_APRF))
  6436.             then    read ans < <(jq -r '(.message|select(.audio.data != null)|.audio.id)//(.choices|.[]?|select(.delta.audio.data != null)|.delta.audio.id)//empty' "$FILE" 2>/dev/null);
  6437.                 #audio-id is not implemented, testing
  6438.             fi
  6439.  
  6440.             #print error msg and check for OpenAI response length-type error
  6441.             if ((!${#ans})) && ((RET_PRF<120))
  6442.             then
  6443.                 ((STREAM)) && file=$FILESTREAM || file=$FILE;
  6444.                 ((GOOGLEAI && STREAM)) && file=$FILE_PRE;
  6445.  
  6446.                 jq -e '.' "$file" >&2 2>/dev/null || cat -- "$file" >&2 ||
  6447.                 ((OPTCMPL)) || ! _warmsgf 'Err';
  6448.                 _warmsgf "(response empty)";
  6449.                 ((${#REPLY}<1640)) || read_charf -t 1.6 >/dev/null 2>&1;
  6450.  
  6451.                 ((!(LOCALAI+OLLAMA+GOOGLEAI+MISTRALAI+GROQAI+ANTHROPICAI+GITHUBAI) )) &&
  6452.                 if ((!OPTTIK)) && ((MTURN+OPTRESUME)) && ((HERR<=${HERR_DEF:=1}*5)) \
  6453.                     && var=$(jq -e '(.error.message?)//(.[]?|.error?)//empty' "$file" 2>/dev/null) \
  6454.                     && [[ $var = *[Cc]ontext\ length*[Rr]educe* ]] \
  6455.                     && [[ $ESC != "$ESC_OLD" ]]
  6456.                 then    #[0]modmax [1]resquested [2]prompt [3]cmpl
  6457.                     var=(${var//[!0-9$IFS]})
  6458.                     if ((${#var[@]}<2 || var[1]<=(var[0]*3)/2))
  6459.                     then    ESC_OLD=$ESC; ((OPTW)) && RESUBW=1;
  6460.                       ((HERR+=HERR_DEF*2)) ;BAD_RES=1 PSKIP=1; set --
  6461.                       _warmsgf "Adjusting Context:" -$((HERR_DEF+HERR))%
  6462.                       ((HERR<HERR_DEF*4)) && _sysmsgf '' "* Set \`option -y' to use Tiktoken! * "
  6463.                       sleep $(( (HERR/HERR_DEF)+1)) ;continue
  6464.                     fi
  6465.                 fi  #adjust context err (OpenAI only)
  6466.                 unset REPLY_CMD_DUMP file;  
  6467.             fi;
  6468.  
  6469.             BAD_RES= PSKIP= ESC_OLD=;
  6470.             ((${#tkn[@]}>1 || STREAM)) && ((${#ans})) && ((MTURN+OPTRESUME))
  6471.         then
  6472.             if CKSUM=$(cksumf "$FILECHAT") ;[[ $CKSUM != "${CKSUM_OLD:-$CKSUM}" ]]
  6473.             then    Color200=${NC} _warmsgf \
  6474.                 'Err: History file modified'$'\n' 'Fork session? Y/n/[i]gnore_all ' ''
  6475.                 case "$(read_charf)" in
  6476.                     [IiGg])     CKSUM= CKSUM_OLD=; function cksumf {    : ;};;
  6477.                     [AaNnOoQq]|$'\e') :;;
  6478.                     *)      session_mainf /copy "$FILECHAT" || break;;
  6479.                 esac
  6480.             fi
  6481.             if ((OPTB>1))  #best_of disables streaming response
  6482.             then    start_tiktokenf
  6483.                 tkn[1]=$(
  6484.                     ((EPN==6)) && A_TYPE=;
  6485.                     __tiktokenf "${A_TYPE}${ans}");
  6486.             fi
  6487.             ans="${A_TYPE##$SPC1}${ans}"
  6488.             ((${#SUFFIX})) && ans=${ans}${SUFFIX}
  6489.             ((BREAK_SET)) && _break_sessionf;
  6490.             if ((ANTHROPICAI && EPN==6 && BREAK_SET && ${#INSTRUCTION_OLD}))
  6491.             then
  6492.                 push_tohistf "$(escapef ":$INSTRUCTION_OLD")" $( ((MAIN_LOOP)) || echo $TOTAL_OLD )
  6493.             elif ((${#INSTRUCTION}+${#GINSTRUCTION}))
  6494.             then
  6495.                 push_tohistf "$(escapef ":${INSTRUCTION:-$GINSTRUCTION}")" $( ((MAIN_LOOP)) || echo $TOTAL_OLD )
  6496.             fi
  6497.             ((OPTAWE)) ||
  6498.             push_tohistf "$(escapef "${Q_TYPE##$SPC1}${REC_OUT}")" "$(( (tkn[0]-TOTAL_OLD)>0 ? (tkn[0]-TOTAL_OLD) : 0 ))" "${tkn[3]}"
  6499.             push_tohistf "$ans" "$((${tkn[1]:-${tkn_ans:-0}}-tkn[2]))" "${tkn[3]}" || OPTC= OPTRESUME= OPTCMPL= MTURN=;
  6500.  
  6501.             ((TOTAL_OLD=tkn[0]+tkn[1]-tkn[2])) && MAX_PREV=$TOTAL_OLD
  6502.             HIST_TIME= BREAK_SET= REPLY_CMD_BLOCK=;
  6503.         elif ((MTURN))
  6504.         then
  6505.             ((OPTW)) && RESUBW=1;
  6506.             ((PREVIEW)) && BCYAN="${Color9}";
  6507.             ((${#REPLY_CMD})) && REPLY=$REPLY_CMD;
  6508.             BAD_RES=1 SKIP=1 EDIT=1 CKSUM_OLD=;
  6509.             unset PSKIP JUMP REGEN PREVIEW REPLY_CMD REPLY_CMD_DUMP INT_RES MEDIA  MEDIA_IND  MEDIA_CMD_IND SUFFIX OPTE;
  6510.             ((OPTX)) && read_charf -t 6 >/dev/null
  6511.             set -- ;continue
  6512.         fi;
  6513.         ((MEDIA_IND_LAST = ${#MEDIA_IND[@]} + ${#MEDIA_CMD_IND[@]}));
  6514.         unset MEDIA MEDIA_CMD MEDIA_IND MEDIA_CMD_IND INT_RES GINSTRUCTION REGEN JUMP PSKIP OPTE;
  6515.         HIST_G= SKIP_SH_HIST=;
  6516.  
  6517.         ((OPTLOG)) && (usr_logf "$(unescapef "${ESC}\\n${ans}")" > "$USRLOG" &)
  6518.         ((RET_PRF>120)) && {    PSKIP= JUMP= SKIP=1 EDIT=1 RET_PRF= RET_APRF=; set --; continue ;}  #B# record whatever has been received by streaming
  6519.  
  6520.         #auto detect markdown in response
  6521.         if ((!NO_OPTMD_AUTO)) && ((!OPTMD)) && ((!OPTEXIT)) &&
  6522.             ((OPTC)) && ((MTURN)) && is_mdf "${ans}"
  6523.         then
  6524.             printf '\n%s\n' '--- markdown ---' >&2;
  6525.             cmd_runf /markdown; _cmdmsgf 'Markdown' "AUTO";
  6526.         fi
  6527.  
  6528.         if ((OLLAMA+GROQAI)) && ((${#tkn[@]}>=5))  #token generation rate
  6529.         then  #[0]tokens  [1]response_secs  [2]tkn_rate
  6530.             TKN_RATE=( "${tkn[1]}" "$(printf '%.2f' "${tkn[4]}")" "$(printf '%.2f' "${tkn[5]}")" )
  6531.         elif    [[ ${tkn[1]:-$tkn_ans} = *[1-9]* ]]
  6532.         then
  6533.             TKN_RATE=( "${tkn[1]:-$tkn_ans}" "$(bc <<<"scale=8; ${EPOCHREALTIME:-$SECONDS} - $SECONDS_REQ")"
  6534.             "$(bc <<<"scale=2; ${tkn[1]:-${tkn_ans:-0}} / (${EPOCHREALTIME:-$SECONDS}-$SECONDS_REQ)")" )
  6535.         fi
  6536.         SESSION_COST=$(
  6537.             cost=$(_model_costf "$MOD") || exit; set -- $cost;
  6538.             bc <<<"scale=8; ${SESSION_COST:-0} + $(costf "$( ((tkn[0])) && echo ${tkn[0]} || __tiktokenf "$REPLY" )" "$( ((tkn[1])) && echo ${tkn[1]} || __tiktokenf "$(unescapef "$ans")" )" $@)"
  6539.             )
  6540.  
  6541.         if ((OPTW)) && ((!OPTZ))
  6542.         then
  6543.             SLEEP_WORDS=$(wc -w <<<"${ans}");
  6544.             ((STREAM)) && ((SLEEP_WORDS=(SLEEP_WORDS*2)/3));
  6545.             ((++SLEEP_WORDS));
  6546.         elif ((OPTZ)) && is_amodelf "$MOD"
  6547.         then
  6548.             _sysmsgf 'TTS:' 'Wrapping PCM in WAV...';
  6549.             FILEOUT_TTS=$(
  6550.               ((MULTIMODAL>1)) && OPTZ_FMT="pcm";  #audio-model preview
  6551.               set_fnamef "${FILEOUT_TTS%.*}.${OPTZ_FMT}");
  6552.            
  6553.             jq -r '(.choices|.[0]?|.message.audio.data)//(.choices|.[0]?|.delta.audio.data)//empty' "$FILE" |
  6554.             {   base64 -d || base64 -D ;} >"$FILEOUT_TTS" || rm -vf "$FILEOUT_TTS" >&2;
  6555.            
  6556.             var="${FILEOUT_TTS%.*}.wav";
  6557.             if [[ -s $FILEOUT_TTS ]] && case "$OPTZ_FMT" in pcm16|pcm|raw)  :;; *)  false;; esac
  6558.             then
  6559.                 if command -v ffmpeg >/dev/null 2>&1
  6560.                 then    ffmpeg -hide_banner -f s16le -ar 24000 -ac 1 -i "$FILEOUT_TTS" "$var";
  6561.                 elif command -v sox >/dev/null 2>&1
  6562.                 then    sox -r 24000 -b 16 -e signed-integer -c 1 -L -t raw "$FILEOUT_TTS" "$var";
  6563.                 elif command -v lame >/dev/null 2>&1
  6564.                 then    lame --decode -r -m m -s 24 --bitwidth 16 --little-endian --signed "$FILEOUT_TTS" "$var";
  6565.                 #elif command -v ecasound >/dev/null 2>&1
  6566.                 #then   ecasound -f:s16_le,1,24000 -i "$FILEOUT_TTS" -o "$var";  #.raw only
  6567.                 else    false;
  6568.                 fi >/dev/null 2>&1 || ! _warmsgf 'Err:' 'ffmpeg/sox/lame -- pcm16 to wav';
  6569.                
  6570.                 [[ -s $var ]] && FILEOUT_TTS=$var;
  6571.                 [[ ! -e $var ]] || du -h "$var" >&2 2>/dev/null || _sysmsgf 'TTS File:' "${var/"$HOME"/"~"}";
  6572.             fi
  6573.             #Audio formats
  6574.             #    raw 16 bit PCM audio at 24kHz, 1 channel, little-endian
  6575.             #    G.711 at 8kHz (both u-law and a-law)
  6576.             #https://platform.openai.com/docs/guides/realtime#audio-formats
  6577.  
  6578.             ok=-1;
  6579.             for ((m=1;m<2;++m))
  6580.             do  ((++ok)); ((ok<10)) || break;
  6581.                 ((RET_APRF)) && var=8 || var=3;  #3+1 secs
  6582.                 _warmsgf $'\nReplay?' 'N/y/[w]ait ' '';  #!# #F#
  6583.                 for ((n=var;n>-1;n--))
  6584.                 do  printf '%s\b' "$n" >&2
  6585.                     if (( (!STREAM && !ok) || RET_APRF)) && {   RET_APRF= var="y"; printf 'y\n' >&2 ;} ||
  6586.                         var=$(NO_CLR=1 read_charf -t 1 </dev/tty)
  6587.                     then    case "$var" in
  6588.                         [Q])    echo '[bye]' >&2; exit 202;;
  6589.                         [RrYy]|$'\t') m=0; break 1;;
  6590.                         [PpWw]|[$' \e']) printf '%s' waiting.. >&2;
  6591.                             read_charf >/dev/null;
  6592.                             m=0; break 1;;  #wait key press
  6593.                         *)  n=-1 var=; break 1;;
  6594.                         esac;
  6595.                     fi; ((n)) || echo >&2;
  6596.                 done; _clr_lineupf 19;  #!#
  6597.                 ((n<0)) && [[ $var != *[!$IFS]* ]] && break;
  6598.  
  6599.                 if [[ -s $FILEOUT_TTS ]]
  6600.                 then    m=0; cmd_runf /replay;
  6601.                 else    rm -vf -- "$FILEOUT_TTS";
  6602.                     _warmsgf 'Err:' $'audio-model output\n';
  6603.                 fi
  6604.             done; unset RET_APRF ok var m n;
  6605.         elif ((OPTZ))
  6606.         then
  6607.             ans=${ans##"${A_TYPE##$SPC1}"};
  6608.             #detect and remove possible markdown from $ans to avoid some tts glitches
  6609.             if [[ OPTMD -gt 0 || \\n$ans = *\\n@(\#\ |[\*-]\ |\>\ )* ]]
  6610.             then    ans_tts=$(unescapef "$ans");
  6611.                 ans_tts=$(
  6612.                     unmarkdownf <<<"$ans_tts" || {
  6613.                     command -v pandoc >/dev/null 2>&1 && pandoc --from markdown --to plain <<<"$ans_tts" ;} ) 2>/dev/null;
  6614.                 ans_tts=$(escapef "$ans_tts");
  6615.             fi
  6616.  
  6617.             trap '' INT;
  6618.             ttsf "${ZARGS[@]}" "${ans_tts:-$ans}";
  6619.             trap 'exit' INT;
  6620.         fi
  6621.         if ((OPTW))
  6622.         then    #whisper auto context for better transcription / translation
  6623.             WCHAT_C="${WCHAT_C:-$(escapef "${INSTRUCTION:-${GINSTRUCTION:-$INSTRUCTION_OLD}}")}\\n\\n${REPLY:-$*}\\n\\n$(
  6624.               ((${#ans} > 245)) && ans=${ans: ${#ans}-245}; printf '%s' "${ans#*[$IFS]}" )";
  6625.             #trim right away, max 224 tkns, GPT-2 encoding
  6626.             ((${#WCHAT_C}>490)) && WCHAT_C=${WCHAT_C: ${#WCHAT_C}-490};
  6627.             WCHAT_C=$(SMALLEST=1 INDEX=64 trim_leadf "${WCHAT_C}" $'*[ \t\n]');
  6628.             #https://platform.openai.com/docs/guides/speech-to-text/improving-reliability
  6629.         fi
  6630.  
  6631.         ((++MAIN_LOOP)) ;set --
  6632.         role= rest= tkn_ans= ans_tts= ans= buff= glob= out= pid= s= n=;
  6633.         HIST_G= TKN_PREV= REC_OUT= HIST= HIST_C= REPLY= ESC= Q= STREAM_OPT= RET= RET_PRF= RET_APRF= WSKIP= PSKIP= SKIP= EDIT=;
  6634.         unset INSTRUCTION GINSTRUCTION REGEN OPTRESUME JUMP REPLY_CMD REPLY_CMD_DUMP REPLY_TRANS OPTA_OPT OPTAA_OPT OPTB_OPT OPTBB_OPT OPTP_OPT OPTKK_OPT OPTSUFFIX_OPT SUFFIX PREFIX OPTAWE PREVIEW BAD_RES INT_RES var tkn;
  6635.         ((MTURN && !OPTEXIT)) || break
  6636.     done
  6637. fi
  6638.  
  6639.  
  6640. #   &=== &   & &==== ===== &==== &==== =====    &==== &   &
  6641. #   %  % %   % %   %   %   %   % %   %   %      %   " %   %
  6642. #   %=   %===% %===%   %=  %=    %===%   %=     %==== %===%     ^    ^ .
  6643. #   %%   %%  % %%  %   %%  %% "% %%      %%        %% %%  %    /a\  /i\  
  6644. #   %%=% %%  % %%  %   %%  %%==% %%      %%  %% %==%% %%  %  ,<___><___>.
  6645.  
  6646. ## set -x; shopt -s extdebug; PS4='$EPOCHREALTIME:$LINENO: ';  # Debug performance by line
  6647. ## shellcheck -S warning -e SC2034,SC1007,SC2207,SC2199,SC2145,SC2027,SC1007,SC2254,SC2046,SC2124,SC2209,SC1090,SC2164,SC2053,SC1075,SC2068,SC2206,SC1078  ~/bin/chatgpt.sh
  6648.  
  6649. # vim=syntax sync minlines=800
Add Comment
Please, Sign In to add comment