[ { "role": "system", "content": "\n You are an Übersicht widget designer. Create Übersicht widgets when requested by the user.\n\n IMPORTANT: You have access to a tool called WriteUbersichtWidgetToFileSystem. When asked to create a widget, you MUST call this tool.\n\n ### Tool Usage:\n Call WriteUbersichtWidgetToFileSystem with complete JSX code that implements the Übersicht Widget API. Generate custom JSX based on the user's specific request - do not copy the example below.\n\n ### Übersicht Widget API (REQUIRED):\n Every Übersicht widget MUST export these 4 items:\n - export const command: The bash command to execute (string)\n - export const refreshFrequency: Refresh rate in milliseconds (number)\n - export const render: React component function that receives {output} prop (function)\n - export const className: CSS positioning for absolute placement (string)\n\n Example format (customize for each request):\n WriteUbersichtWidgetToFileSystem({jsxContent: `export const command = \"echo hello\"; export const refreshFrequency = 1000; export const render = ({output}) => { return <div>{output}</div>; }; export const className = \"top: 20px; left: 20px;\"`})\n\n ### Rules:\n - The terms \"ubersicht widget\", \"widget\", \"a widget\", \"the widget\" must all be interpreted as \"Übersicht widget\"\n - Generate complete, valid JSX code that follows the Übersicht widget API\n - When you generate a widget, don't just show JSON or code - you MUST call the WriteUbersichtWidgetToFileSystem tool\n - Report the results to the user after calling the tool\n\n ### Examples:\n - \"Generate a Übersicht widget\" → Use WriteUbersichtWidgetToFileSystem tool\n - \"Can you add a widget that shows the time\" → Use WriteUbersichtWidgetToFileSystem tool\n - \"Create a widget with a button\" → Use WriteUbersichtWidgetToFileSystem tool\n ", "tools": [ { "type": "function", "function": { "name": "WriteUbersichtWidgetToFileSystem", "description": "Writes an Übersicht Widget to the file system. Call this tool as the last step in processing a prompt that generates a widget.", "parameters": { "type": "object", "properties": { "jsxContent": { "type": "string", "description": "Complete JSX code for an Übersicht widget. This should include all required exports: command, refreshFrequency, render, and className. The JSX should be a complete, valid Übersicht widget file." } }, "required": [ "jsxContent" ] } } } ] }, { "role": "user", "content": "Create an Übersicht widget that elegantly displays the currently playing song from Apple Music or Spotify with album artwork and playback information. The widget should use React/JSX with a sophisticated music player interface featuring album covers, track details, progress bars, and smooth animations. Include multi-service support, adaptive colors from artwork, and customizable display options. Implement a premium design with proper music service integration and real-time updates." }, { "role": "assistant", "content": "I'll create that widget for you.", "tool_calls": [ { "id": "call_UeberPlayer", "type": "function", "function": { "name": "WriteUbersichtWidgetToFileSystem", "arguments": "{\"jsxContent\": \"// index.jsx\\nimport { styled, run } from \\\"uebersicht\\\";\\nimport getColors from './lib/getColors.js';\\nimport updateWidget from './lib/updater.js';\\nconst _version = '1.4.0';\\nlet _updateTimer = 8.5;\\n\\n/* CUSTOMIZATION (mess around here!)\\nYou may need to refresh the widget after changing these settings\\nFor more details about these settings: please visit https://github.com/aCluelessDanny/UeberPlayer#settings */\\n\\nconst options = {\\n /* Widget size! */\\n size: \\\"big\\\", // -> big (default) | medium | small | mini\\n\\n /* Widget position! */\\n verticalPosition: \\\"top\\\", // -> top (default) | center | bottom | \\\"\\\" | \\\"-\\\"\\n horizontalPosition: \\\"left\\\", // -> left (default) | center | right | \\\"\\\" | \\\"-\\\"\\n\\n /* Widget visibility! */\\n alwaysShow: 0, // -> 0 (default) | 1 | 2\\n\\n /* Adaptive colors! */\\n adaptiveColors: true, // -> true (default) | false\\n minContrast: 2.6, // -> 2.6 (default) | number\\n\\n /* Visual customization! */\\n dualProgressBar: false, // -> true | false (default)\\n miniArtwork: false, // -> true | false (default)\\n\\n /* Cache setting! */\\n cacheMaxDays: 15, // -> 15 (default) | \\n\\n /* Check for updates */\\n checkForUpdates: true // -> true (default) | false\\n}\\n\\n/* ROOT STYLING */\\n\\nexport const className = `\\n pointer-events: none;\\n position: absolute;\\n top: 0;\\n left: 0;\\n bottom: 0;\\n right: 0;\\n font-family: -apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", Roboto, Helvetica, Arial, sans-serif, \\\"Apple Color Emoji\\\", \\\"Segoe UI Emoji\\\", \\\"Segoe UI Symbol\\\";\\n color: white;\\n\\n * {\\n box-sizing: border-box;\\n padding: 0;\\n border: 0;\\n margin: 0;\\n }\\n`;\\n\\n/* EMOTION COMPONENTS */\\n\\nconst wrapperPos = ({ horizontal, vertical }) => {\\n if (horizontal === \\\"center\\\" && vertical === \\\"center\\\") {\\n return `\\n top: 50%;\\n left: 50%;\\n transform: translate(-50%, -50%);\\n `\\n }\\n\\n let hPos, vPos;\\n switch (horizontal) {\\n case \\\"left\\\": hPos = `left: 20px;`; break;\\n case \\\"center\\\": hPos = `left: 50%; transform: translateX(-50%);`; break;\\n case \\\"right\\\": hPos = `right: 20px;`; break;\\n default: hPos = horizontal.startsWith(\\\"-\\\") ? `right: ${parseInt(horizontal) * -1}px;` : `left: ${horizontal}px;`; break;\\n }\\n switch (vertical) {\\n case \\\"top\\\": vPos = `top: 20px;`; break;\\n case \\\"center\\\": vPos = `top: 50%; transform: translateY(-50%);`; break;\\n case \\\"bottom\\\": vPos = `bottom: 20px;`; break;\\n default: vPos = vertical.startsWith(\\\"-\\\") ? `bottom: ${parseInt(vertical) * -1}px;` : `top: ${vertical}px;`; break;\\n }\\n\\n return `${hPos} ${vPos}`;\\n}\\n\\nconst Wrapper = styled(\\\"div\\\")`\\n position: absolute;\\n border-radius: 6px;\\n overflow: hidden;\\n box-shadow: 0 16px 32px 9px #0005;\\n opacity: ${props => props.show ? 1 : 0};\\n background: ${props => (props.bg !== undefined) ? props.bg : \\\"#0004\\\"};\\n transition: all 0.6s cubic-bezier(0.22, 1, 0.36, 1);\\n ${wrapperPos}\\n\\n &::before {\\n content: \\\"\\\";\\n position: absolute;\\n top: 0;\\n left: 0;\\n bottom: 0;\\n right: 0;\\n border-radius: 6px;\\n z-index: -1;\\n }\\n\\n * {\\n transition: all 0.6s cubic-bezier(0.22, 1, 0.36, 1);\\n }\\n`;\\n\\nconst miniWrapperPos = ({ horizontal }) => {\\n switch (horizontal) {\\n case \\\"left\\\": return `text-align: left;`;\\n case \\\"center\\\": return `text-align: center;`;\\n case \\\"right\\\": return `text-align: right;`;\\n default: return horizontal.startsWith(\\\"-\\\") ? `text-align: right;` : `text-align: left;`;\\n }\\n}\\n\\nconst MiniWrapper = styled(Wrapper)`\\n border-radius: 0;\\n overflow: visible;\\n box-shadow: none;\\n background: transparent;\\n ${miniWrapperPos}\\n\\n &::before {\\n display: none;\\n }\\n`\\n\\nconst ErrorWrapper = styled('div')`\\n width: 360px;\\n padding: 1em;\\n text-align: center;\\n font-size: .9em;\\n\\n * + * {\\n margin-top: .4em;\\n }\\n\\n h3 {\\n font-style: italic;\\n color: #ddd;\\n }\\n`\\n\\nconst BigPlayer = styled(\\\"div\\\")`\\n display: flex;\\n flex-direction: column;\\n width: 240px;\\n`;\\n\\nconst MediumPlayer = styled(BigPlayer)`\\n width: 180px;\\n`\\n\\nconst SmallPlayer = styled(\\\"div\\\")`\\n position: relative;\\n display: flex;\\n height: 80px;\\n width: 340px;\\n`\\n\\nconst MiniPlayer = styled(\\\"div\\\")`\\n position: relative;\\n display: flex;\\n flex-direction: column;\\n width: 400px;\\n line-height: 1;\\n\\n * {\\n text-shadow: 0px 0px 4px #0004, 0px 2px 12px #0004;\\n }\\n`\\n\\nconst MiniInfo = styled(\\\"div\\\")`\\n display: flex;\\n align-items: center;\\n`\\n\\nconst MiniDetails = styled(\\\"div\\\")`\\n flex: 1;\\n\\n > * + * {\\n margin-top: .5em;\\n }\\n`\\n\\nconst ArtworkWrapper = styled(\\\"div\\\")`\\n position: relative;\\n width: 240px;\\n height: 240px;\\n\\n &.medium {\\n width: 180px;\\n height: 180px;\\n }\\n\\n &.small {\\n width: 80px;\\n height: 80px;\\n }\\n\\n &.mini {\\n width: 65px;\\n height: 65px;\\n border-radius: 4px;\\n margin-right: 10px;\\n overflow: hidden;\\n }\\n\\n &::before {\\n position: absolute;\\n content: \\\"\\\";\\n top: 0;\\n left: 0;\\n bottom: 0;\\n right: 0;\\n border-radius: 6px 6px 0 0;\\n background: #fff1;\\n backdrop-filter: blur(8px) brightness(90%) contrast(80%) saturate(140%);\\n z-index: -1;\\n }\\n\\n &.mini::before {\\n border-radius: 0;\\n }\\n`\\n\\nconst Artwork = styled(\\\"img\\\")`\\n position: absolute;\\n top: 0;\\n left: 0;\\n bottom: 0;\\n right: 0;\\n height: 100%;\\n width: 100%;\\n object-fit: cover;\\n opacity: ${props => props.show ? 1 : 0};\\n transition: opacity .5s linear;\\n`;\\n\\nconst Information = styled(\\\"div\\\")`\\n position: relative;\\n padding: .5em .75em;\\n line-height: 1.3;\\n border-radius: 0 0 6px 6px;\\n backdrop-filter: ${options.adaptiveColors ? \\\"blur(8px)\\\" : \\\"blur(8px) brightness(90%) contrast(80%) saturate(140%)\\\"};\\n\\n > p {\\n text-align: center;\\n white-space: nowrap;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n }\\n\\n &.small {\\n flex: 1;\\n width: 0;\\n display: flex;\\n flex-direction: column;\\n justify-content: center;\\n border-radius: 0 6px 6px 0;\\n line-height: 1.4;\\n }\\n\\n &.small > p {\\n text-align: left;\\n }\\n`\\n\\nconst Progress = styled(\\\"div\\\")`\\n position: absolute;\\n top: 0;\\n left: 0;\\n right: 0;\\n height: 2px;\\n background: ${props => options.dualProgressBar && props.emptyColor ? (props.emptyColor + \\\"80\\\") : \\\"transparent\\\"};\\n\\n &::after {\\n content: \\\"\\\";\\n position: absolute;\\n top: 0;\\n left: 0;\\n bottom: 0;\\n width: ${props => props.percent}%;\\n background: ${props => props.progressColor ? props.progressColor : \\\"white\\\"};\\n transition: width .6s cubic-bezier(0.22, 1, 0.36, 1);\\n }\\n\\n &.small {\\n top: initial;\\n bottom: 0;\\n }\\n\\n &.mini {\\n position: relative;\\n height: 4px;\\n border-radius: 2px;\\n background: ${props => options.dualProgressBar && props.emptyColor ? (props.emptyColor + \\\"60\\\") : \\\"#0005\\\"};\\n box-shadow: 0 3px 5px -1px #0003;\\n overflow: hidden;\\n margin-top: 10px;\\n }\\n`\\n\\nconst Track = styled(\\\"p\\\")`\\n font-weight: bold;\\n font-size: .7em;\\n color: ${props => props.color ? props.color : \\\"inherit\\\"};\\n\\n &::after {\\n content: '';\\n display: inline-block;\\n width: 0;\\n }\\n\\n &.small {\\n font-size: .65em;\\n }\\n\\n &.mini {\\n font-size: 1.2em;\\n }\\n`\\n\\nconst Artist = styled(\\\"p\\\")`\\n font-size: .7em;\\n color: ${props => props.color ? props.color : \\\"inherit\\\"};\\n\\n &::after {\\n content: '';\\n display: inline-block;\\n width: 0;\\n }\\n\\n &.small {\\n font-size: .65em;\\n }\\n\\n &.mini {\\n font-size: 1em;\\n }\\n`\\n\\nconst Album = styled(\\\"p\\\")`\\n font-size: .65em;\\n color: ${props => props.color ? props.color : \\\"inherit\\\"};\\n opacity: .75;\\n\\n &::after {\\n content: '';\\n display: inline-block;\\n width: 0;\\n }\\n\\n &.small {\\n font-size: .55em;\\n }\\n`\\n\\nconst UpdateNotifWrapper = styled(\\\"div\\\")`\\n position: absolute;\\n top: 0;\\n width: 100%;\\n padding: 1em;\\n min-height: 30%;\\n background-image: linear-gradient(to bottom, #000b, transparent);\\n`\\n\\nconst UpdateText = styled(\\\"p\\\")`\\n font-size: .7em;\\n text-align: center !important;\\n pointer-events: auto;\\n cursor: pointer;\\n\\n span {\\n color: inherit;\\n text-decoration: underline;\\n }\\n`\\n\\n/* UEBER-SPECIFIC STUFF */\\n\\nexport const init = (dispatch) => {\\n // Initialize and clear cache of old artwork\\n run(\\n `mkdir -p UeberPlayer.widget/cache &&\\\\\\n find UeberPlayer.widget/cache -mindepth 1 -type f -mtime +${options.cacheMaxDays} -delete &&\\\\\\n osascript UeberPlayer.widget/lib/init.scpt`\\n );\\n // Check for updates when enabled\\n if (options.checkForUpdates) {\\n checkForUpdate(dispatch);\\n setInterval(checkForUpdate, 86400000, dispatch);\\n }\\n};\\n\\nexport const command = \\\"osascript UeberPlayer.widget/lib/getTrack.scpt\\\";\\n\\nexport const initialState = {\\n app: \\\"\\\", // Current music software being used\\n playing: false, // If currently playing a soundtrack\\n appleMusicError: false, // If online music is being played on Apple Music\\n songChange: false, // If the song changed\\n primaryColor: undefined, // Primary color from artwork\\n secondaryColor: undefined, // Secondary color from artwork\\n tercaryColor: undefined, // Tercary color from artwork\\n artwork: { // Artwork source URL to be used\\n art1: \\\"UeberPlayer.widget/default.png\\\", // Artwork to alternate with\\n art2: \\\"UeberPlayer.widget/default.png\\\", // Same as above\\n alternate: true // Flag to pick which artwork to display (for smooth transitions)\\n },\\n song: { // Current song data\\n track: \\\"\\\", // Name of soundtrack\\n artist: \\\"\\\", // Name of artist\\n album: \\\"\\\", // Name of album\\n localArtwork: \\\"\\\", // Locally stored url for album artwork\\n onlineArtwork: \\\"\\\", // Online url for album artwork\\n duration: 0, // Total duration of soundtrack in seconds\\n elapsed: 0 // Total time elapsed in seconds\\n },\\n updateAvailable: false, // Flag for when an update's available\\n updatePending: false, // Flag for when an update's about to happen\\n};\\n\\n// Update state\\nexport const updateState = ({ type, output, error }, previousState) => {\\n switch (type) {\\n case 'UB/COMMAND_RAN':\\n return updateSongData(output, error, previousState);\\n case 'GET_ART':\\n if (options.adaptiveColors) {\\n return getColors(output, previousState, options);\\n } else {\\n const { art1, art2, alternate } = previousState.artwork;\\n return {\\n ...previousState,\\n songChange: false,\\n primaryColor: undefined,\\n secondaryColor: undefined,\\n tercaryColor: undefined,\\n artwork: {\\n art1: alternate ? art1 : output.img.src,\\n art2: !alternate ? art2 : output.img.src,\\n alternate: !alternate\\n }\\n }\\n }\\n case 'DEFAULT_ART':\\n const { art1, art2, alternate } = previousState.artwork;\\n return {\\n ...previousState,\\n songChange: false,\\n primaryColor: undefined,\\n secondaryColor: undefined,\\n tercaryColor: undefined,\\n artwork: {\\n art1: alternate ? art1 : \\\"UeberPlayer.widget/default.png\\\",\\n art2: !alternate ? art2 : \\\"UeberPlayer.widget/default.png\\\",\\n alternate: !alternate\\n }\\n }\\n case 'UPDATE_AVAILABLE':\\n return { ...previousState, updateAvailable: true }\\n case 'PREPARE_UPDATE':\\n console.log('Preparing update!');\\n const updateCountdown = setInterval(() => {\\n if (--_updateTimer <= 0) {\\n updateWidget(options);\\n window.clearInterval(updateCountdown);\\n }\\n }, 1000);\\n return { ...previousState, updatePending: true }\\n default:\\n console.error(\\\"Invalid dispatch type?\\\");\\n return previousState;\\n }\\n}\\n\\n/* FUNCTIONS */\\n\\n// Check for update\\nconst checkForUpdate = async (dispatch) => {\\n let resp = await fetch('https://raw.githubusercontent.com/aCluelessDanny/UeberPlayer/master/widget.json');\\n if (!resp.ok) { throw Error(\\\"Unable to check for update!\\\") }\\n\\n let data = await resp.json();\\n if (_version !== data.version) {\\n console.log(`There's an update available! -> ${data.version}`);\\n dispatch({ type: \\\"UPDATE_AVAILABLE\\\" });\\n }\\n}\\n\\n// Update song metadata\\nconst updateSongData = (output, error, previousState) => {\\n // Check for errors\\n if (error) {\\n const err_output = { ...previousState, error: error };\\n console.error(err_output);\\n return err_output;\\n }\\n\\n // Extract & parse applescript output\\n let [\\n playing,\\n app,\\n track,\\n artist,\\n album,\\n artworkURL,\\n artworkFilename,\\n duration,\\n elapsed,\\n appleMusicError\\n ] = output.trim().split(\\\" @@ \\\");\\n\\n playing = (playing === \\\"true\\\");\\n appleMusicError = (appleMusicError === \\\"true\\\");\\n duration = Math.floor(parseFloat(duration));\\n elapsed = Math.floor(parseFloat(elapsed));\\n\\n // State controller\\n if (!playing || appleMusicError) { // If player is paused or if online music is being played on Apple Music\\n return { ...previousState, app, playing, appleMusicError };\\n } else if (track !== previousState.song.track || album !== previousState.song.album) { // Song change\\n return {\\n ...previousState,\\n app,\\n playing,\\n appleMusicError,\\n songChange: true,\\n song: {\\n track,\\n artist,\\n album,\\n localArtwork: `UeberPlayer.widget/cache/${artworkFilename}`,\\n onlineArtwork: artworkURL,\\n duration,\\n elapsed\\n }\\n }\\n } else { // Currently playing\\n return {\\n ...previousState,\\n app,\\n playing,\\n appleMusicError,\\n song: {\\n ...previousState.song,\\n elapsed\\n }\\n };\\n }\\n}\\n\\n// Prepare artwork\\nconst prepareArtwork = (dispatch, song) => {\\n // Use a dummy image to test images beforehand\\n const img = new Image();\\n\\n // Attempts images in this order: Local -> Online -> Default\\n img.onload = () => { dispatch({ type: \\\"GET_ART\\\", output: { img }})};\\n img.onerror = () => {\\n if (song.onlineArtwork !== \\\"missing value\\\" && img.src !== song.onlineArtwork) {\\n img.crossOrigin = 'anonymous';\\n img.src = song.onlineArtwork;\\n } else {\\n dispatch({ type: \\\"DEFAULT_ART\\\" });\\n }\\n }\\n\\n img.crossOrigin = 'anonymous';\\n img.src = song.localArtwork;\\n}\\n\\n// RENDERING //\\n// Artwork image\\nconst ArtworkImage = ({ artwork: { art1, art2, alternate }, wrapperClass }) => (\\n \\n \\n \\n \\n)\\n\\n// Update notification component\\nconst UpdateNotif = ({ dispatch, updatePending, useBackground, color = 'inherit' }) => {\\n const UpdateLink = (\\n \\n {updatePending ? (\\n <span style="{{"><b>Updating!</b> You might need to restart Ubersicht when finished... ({Math.floor(_updateTimer)})</span>\\n ) : (\\n <span> dispatch({ type: \\\"PREPARE_UPDATE\\\" })}>An update is available!</span>\\n )}\\n \\n );\\n\\n return useBackground ? (\\n \\n \\n \\n ) : (\\n \\n )\\n}\\n\\n// Big player component\\nconst Big = ({ state, dispatch }) => {\\n const {\\n song: { track, artist, album, elapsed, duration },\\n secondaryColor,\\n tercaryColor,\\n artwork,\\n updateAvailable,\\n updatePending\\n } = state;\\n\\n return (\\n \\n \\n {updateAvailable && }\\n \\n <progress>\\n <track color="{secondaryColor}">{track}\\n {artist}\\n {album}\\n </progress>\\n \\n )\\n}\\n\\n// Medium player component\\nconst Medium = ({ state, dispatch }) => {\\n const {\\n song: { track, artist, elapsed, duration },\\n secondaryColor,\\n tercaryColor,\\n artwork,\\n updateAvailable,\\n updatePending\\n } = state;\\n\\n return (\\n \\n \\n {updateAvailable && }\\n \\n <progress>\\n <track color="{secondaryColor}">{track}\\n {artist}\\n </progress>\\n \\n )\\n}\\n\\n// Small player component\\nconst Small = ({ state, dispatch }) => {\\n const {\\n song: { track, artist, album, elapsed, duration },\\n secondaryColor,\\n tercaryColor,\\n artwork,\\n updateAvailable,\\n updatePending\\n } = state;\\n\\n return (\\n \\n \\n \\n <track color="{secondaryColor}">{track}\\n {artist}\\n {album}\\n {updateAvailable && }\\n <progress>\\n </progress>\\n \\n )\\n}\\n\\n// Mini player component\\nconst Mini = ({ state, dispatch }) => {\\n const {\\n song: { track, artist, elapsed, duration },\\n primaryColor,\\n secondaryColor,\\n artwork,\\n updateAvailable,\\n updatePending\\n } = state;\\n\\n return (\\n \\n \\n {options.miniArtwork && }\\n \\n <track>{track}\\n {artist}\\n \\n \\n <progress>\\n {updateAvailable && }\\n </progress>\\n )\\n}\\n\\n// Render function\\nexport const render = (state, dispatch) => {\\n const { size, horizontalPosition, verticalPosition, alwaysShow } = options;\\n const { app, playing, appleMusicError, songChange, primaryColor, secondaryColor, tercaryColor, artwork, song, updateAvailable, updatePending } = state;\\n\\n // Determine widget visability\\n const showWidget = playing || (alwaysShow === 1 && app) || alwaysShow === 2;\\n\\n // When song changes, prepare artwork\\n if (songChange) {\\n prepareArtwork(dispatch, song);\\n }\\n\\n // If Apple Music is playing online music, warn the user that it cannot get information for it\\n if (appleMusicError) {\\n return (\\n \\n \\n <h3>Online Apple Music detected</h3>\\n <p>Unfortunately, the widget is unable to access songs being streamed online through Apple Music.</p>\\n <p>To fix this, please download your music to your library and play it locally from there.</p>\\n \\n \\n )\\n }\\n\\n // Render\\n return (size === \\\"mini\\\") ? (\\n \\n \\n \\n ) : (\\n \\n {size === 'big' && <big>}\\n {size === 'medium' && }\\n {size === 'small' && <small>}\\n </small></big><big><small>\\n )\\n};\"}" } } ] } ]</small></big>